diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8979aabf8b..fdca003877 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -8,7 +8,7 @@ updates:
commit-message:
prefix: ⬆
# Python
- - package-ecosystem: "pip"
+ - package-ecosystem: "uv"
directory: "/"
schedule:
interval: "monthly"
diff --git a/.github/labeler.yml b/.github/labeler.yml
index c5b1f84f3d..cdaefbf2d8 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -17,6 +17,7 @@ lang-all:
- docs/*/docs/**
- all-globs-to-all-files:
- '!docs/en/docs/**'
+ - '!docs/*/**/_*.md'
- '!fastapi/**'
- '!pyproject.toml'
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
index f78b6730ee..77bce055c6 100644
--- a/.github/workflows/build-docs.yml
+++ b/.github/workflows/build-docs.yml
@@ -8,9 +8,6 @@ on:
- opened
- synchronize
-env:
- UV_SYSTEM_PYTHON: 1
-
jobs:
changes:
runs-on: ubuntu-latest
@@ -21,7 +18,7 @@ jobs:
outputs:
docs: ${{ steps.filter.outputs.docs }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
# For pull requests it's not necessary to checkout the code but for the main branch it is
- uses: dorny/paths-filter@v3
id: filter
@@ -31,13 +28,10 @@ jobs:
- README.md
- docs/**
- docs_src/**
- - requirements-docs.txt
- - requirements-docs-insiders.txt
- pyproject.toml
+ - uv.lock
- mkdocs.yml
- - mkdocs.insiders.yml
- - mkdocs.maybe-insiders.yml
- - mkdocs.no-insiders.yml
+ - mkdocs.env.yml
- .github/workflows/build-docs.yml
- .github/workflows/deploy-docs.yml
- scripts/mkdocs_hooks.py
@@ -48,33 +42,24 @@ jobs:
outputs:
langs: ${{ steps.show-langs.outputs.langs }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install docs extras
- run: uv pip install -r requirements-docs.txt
- # Install MkDocs Material Insiders here just to put it in the cache for the rest of the steps
- - name: Install Material for MkDocs Insiders
- if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' )
- run: uv pip install -r requirements-docs-insiders.txt
- env:
- TOKEN: ${{ secrets.FASTAPI_MKDOCS_MATERIAL_INSIDERS }}
- - name: Verify Docs
- run: python ./scripts/docs.py verify-docs
+ run: uv sync --locked --no-dev --group docs
- name: Export Language Codes
id: show-langs
run: |
- echo "langs=$(python ./scripts/docs.py langs-json)" >> $GITHUB_OUTPUT
+ echo "langs=$(uv run ./scripts/docs.py langs-json)" >> $GITHUB_OUTPUT
build-docs:
needs:
@@ -90,35 +75,29 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install docs extras
- run: uv pip install -r requirements-docs.txt
- - name: Install Material for MkDocs Insiders
- if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' )
- run: uv pip install -r requirements-docs-insiders.txt
- env:
- TOKEN: ${{ secrets.FASTAPI_MKDOCS_MATERIAL_INSIDERS }}
+ run: uv sync --locked --no-dev --group docs
- name: Update Languages
- run: python ./scripts/docs.py update-languages
- - uses: actions/cache@v4
+ run: uv run ./scripts/docs.py update-languages
+ - uses: actions/cache@v5
with:
key: mkdocs-cards-${{ matrix.lang }}-${{ github.ref }}
path: docs/${{ matrix.lang }}/.cache
- name: Build Docs
- run: python ./scripts/docs.py build-lang ${{ matrix.lang }}
- - uses: actions/upload-artifact@v5
+ run: uv run ./scripts/docs.py build-lang ${{ matrix.lang }}
+ - uses: actions/upload-artifact@v6
with:
name: docs-site-${{ matrix.lang }}
path: ./site/**
diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml
index 7d5449c6a8..f3ced6aa30 100644
--- a/.github/workflows/contributors.yml
+++ b/.github/workflows/contributors.yml
@@ -10,9 +10,6 @@ on:
required: false
default: "false"
-env:
- UV_SYSTEM_PYTHON: 1
-
jobs:
job:
if: github.repository_owner == 'fastapi'
@@ -24,21 +21,20 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install Dependencies
- run: uv pip install -r requirements-github-actions.txt
+ run: uv sync --locked --no-dev --group github-actions
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
@@ -48,6 +44,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}
- name: FastAPI People Contributors
- run: python ./scripts/contributors.py
+ run: uv run ./scripts/contributors.py
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index aa4fd6b657..734fc244d3 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -12,9 +12,6 @@ permissions:
pull-requests: write
statuses: write
-env:
- UV_SYSTEM_PYTHON: 1
-
jobs:
deploy-docs:
runs-on: ubuntu-latest
@@ -23,23 +20,22 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install GitHub Actions dependencies
- run: uv pip install -r requirements-github-actions.txt
+ run: uv sync --locked --no-dev --group github-actions
- name: Deploy Docs Status Pending
- run: python ./scripts/deploy_docs_status.py
+ run: uv run ./scripts/deploy_docs_status.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
@@ -49,7 +45,7 @@ jobs:
run: |
rm -rf ./site
mkdir ./site
- - uses: actions/download-artifact@v6
+ - uses: actions/download-artifact@v7
with:
path: ./site/
pattern: docs-site-*
@@ -70,14 +66,14 @@ jobs:
command: pages deploy ./site --project-name=${{ env.PROJECT_NAME }} --branch=${{ env.BRANCH }}
- name: Deploy Docs Status Error
if: failure()
- run: python ./scripts/deploy_docs_status.py
+ run: uv run ./scripts/deploy_docs_status.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
RUN_ID: ${{ github.run_id }}
STATE: "error"
- name: Comment Deploy
- run: python ./scripts/deploy_docs_status.py
+ run: uv run ./scripts/deploy_docs_status.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }}
diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml
index f40ec4dc47..2ae588da13 100644
--- a/.github/workflows/issue-manager.yml
+++ b/.github/workflows/issue-manager.yml
@@ -41,11 +41,15 @@ jobs:
"message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR.",
"reminder": {
"before": "P3D",
- "message": "Heads-up: this will be closed in 3 days unless there’s new activity."
+ "message": "Heads-up: this will be closed in 3 days unless there's new activity."
}
},
"invalid": {
"delay": 0,
"message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details."
+ },
+ "maybe-ai": {
+ "delay": 0,
+ "message": "This was marked as potentially AI generated and will be closed now. If this is an error, please provide additional details, make sure to read the docs about contributing and AI."
}
}
diff --git a/.github/workflows/label-approved.yml b/.github/workflows/label-approved.yml
index e6ae3d9636..1307fb8c23 100644
--- a/.github/workflows/label-approved.yml
+++ b/.github/workflows/label-approved.yml
@@ -8,9 +8,6 @@ on:
permissions:
pull-requests: write
-env:
- UV_SYSTEM_PYTHON: 1
-
jobs:
label-approved:
if: github.repository_owner == 'fastapi'
@@ -20,23 +17,22 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install GitHub Actions dependencies
- run: uv pip install -r requirements-github-actions.txt
+ run: uv sync --locked --no-dev --group github-actions
- name: Label Approved
- run: python ./scripts/label_approved.py
+ run: uv run ./scripts/label_approved.py
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONFIG: >
diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml
index 2fa832fab5..b9e45ea629 100644
--- a/.github/workflows/latest-changes.yml
+++ b/.github/workflows/latest-changes.yml
@@ -24,6 +24,8 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
+ # pin to actions/checkout@v5 for compatibility with latest-changes
+ # Ref: https://github.com/actions/checkout/issues/2313
- uses: actions/checkout@v5
with:
# To allow latest-changes to commit to the main branch
@@ -34,7 +36,7 @@ jobs:
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- - uses: tiangolo/latest-changes@0.4.0
+ - uses: tiangolo/latest-changes@0.4.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
latest_changes_file: docs/en/docs/release-notes.md
diff --git a/.github/workflows/notify-translations.yml b/.github/workflows/notify-translations.yml
index 04beeb64e9..31f3eb4021 100644
--- a/.github/workflows/notify-translations.yml
+++ b/.github/workflows/notify-translations.yml
@@ -15,9 +15,6 @@ on:
required: false
default: 'false'
-env:
- UV_SYSTEM_PYTHON: 1
-
jobs:
job:
runs-on: ubuntu-latest
@@ -28,21 +25,20 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install Dependencies
- run: uv pip install -r requirements-github-actions.txt
+ run: uv sync --locked --no-dev --group github-actions
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
@@ -52,7 +48,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Notify Translations
- run: python ./scripts/notify_translations.py
+ run: uv run ./scripts/notify_translations.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NUMBER: ${{ github.event.inputs.number || null }}
diff --git a/.github/workflows/people.yml b/.github/workflows/people.yml
index f15b921371..cb3b742788 100644
--- a/.github/workflows/people.yml
+++ b/.github/workflows/people.yml
@@ -10,9 +10,6 @@ on:
required: false
default: "false"
-env:
- UV_SYSTEM_PYTHON: 1
-
jobs:
job:
if: github.repository_owner == 'fastapi'
@@ -24,21 +21,20 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install Dependencies
- run: uv pip install -r requirements-github-actions.txt
+ run: uv sync --locked --no-dev --group github-actions
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
@@ -48,7 +44,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }}
- name: FastAPI People Experts
- run: python ./scripts/people.py
+ run: uv run ./scripts/people.py
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }}
SLEEP_INTERVAL: ${{ vars.PEOPLE_SLEEP_INTERVAL }}
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
index 0000000000..f027140ed7
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,90 @@
+name: pre-commit
+
+on:
+ pull_request:
+ types:
+ - opened
+ - synchronize
+
+env:
+ # Forks and Dependabot don't have access to secrets
+ HAS_SECRETS: ${{ secrets.PRE_COMMIT != '' }}
+
+jobs:
+ pre-commit:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Dump GitHub context
+ env:
+ GITHUB_CONTEXT: ${{ toJson(github) }}
+ run: echo "$GITHUB_CONTEXT"
+ - uses: actions/checkout@v5
+ name: Checkout PR for own repo
+ if: env.HAS_SECRETS == 'true'
+ with:
+ # To be able to commit it needs to fetch the head of the branch, not the
+ # merge commit
+ ref: ${{ github.head_ref }}
+ # And it needs the full history to be able to compute diffs
+ fetch-depth: 0
+ # A token other than the default GITHUB_TOKEN is needed to be able to trigger CI
+ token: ${{ secrets.PRE_COMMIT }}
+ # pre-commit lite ci needs the default checkout configs to work
+ - uses: actions/checkout@v5
+ name: Checkout PR for fork
+ if: env.HAS_SECRETS == 'false'
+ with:
+ # To be able to commit it needs the head branch of the PR, the remote one
+ ref: ${{ github.event.pull_request.head.sha }}
+ fetch-depth: 0
+ - name: Set up Python
+ uses: actions/setup-python@v6
+ with:
+ python-version-file: ".python-version"
+ - name: Setup uv
+ uses: astral-sh/setup-uv@v7
+ with:
+ cache-dependency-glob: |
+ pyproject.toml
+ uv.lock
+ - name: Install Dependencies
+ run: uv sync --locked --extra all
+ - name: Run prek - pre-commit
+ id: precommit
+ run: uvx prek run --from-ref origin/${GITHUB_BASE_REF} --to-ref HEAD --show-diff-on-failure
+ continue-on-error: true
+ - name: Commit and push changes
+ if: env.HAS_SECRETS == 'true'
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add -A
+ if git diff --staged --quiet; then
+ echo "No changes to commit"
+ else
+ git commit -m "🎨 Auto format"
+ git push
+ fi
+ - uses: pre-commit-ci/lite-action@v1.1.0
+ if: env.HAS_SECRETS == 'false'
+ with:
+ msg: 🎨 Auto format
+ - name: Error out on pre-commit errors
+ if: steps.precommit.outcome == 'failure'
+ run: exit 1
+
+ # https://github.com/marketplace/actions/alls-green#why
+ pre-commit-alls-green: # This job does nothing and is only used for the branch protection
+ if: always()
+ needs:
+ - pre-commit
+ runs-on: ubuntu-latest
+ steps:
+ - name: Dump GitHub context
+ env:
+ GITHUB_CONTEXT: ${{ toJson(github) }}
+ run: echo "$GITHUB_CONTEXT"
+ - name: Decide whether the needed jobs succeeded or failed
+ uses: re-actors/alls-green@release/v1
+ with:
+ jobs: ${{ toJSON(needs) }}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 441eb45608..2232498cb1 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -15,28 +15,25 @@ jobs:
- fastapi-slim
permissions:
id-token: write
+ contents: read
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.10"
+ python-version-file: ".python-version"
# Issue ref: https://github.com/actions/setup-python/issues/436
# cache: "pip"
# cache-dependency-path: pyproject.toml
- - name: Install build dependencies
- run: pip install build
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
- name: Build distribution
+ run: uv build
env:
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
- run: python -m build
- name: Publish
- uses: pypa/gh-action-pypi-publish@v1.13.0
- - name: Dump GitHub context
- env:
- GITHUB_CONTEXT: ${{ toJson(github) }}
- run: echo "$GITHUB_CONTEXT"
+ run: uv publish
diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml
index eed5fbec0e..f23b962b70 100644
--- a/.github/workflows/smokeshow.yml
+++ b/.github/workflows/smokeshow.yml
@@ -8,12 +8,8 @@ on:
permissions:
statuses: write
-env:
- UV_SYSTEM_PYTHON: 1
-
jobs:
smokeshow:
- if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
@@ -21,20 +17,18 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
- python-version: '3.9'
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
- enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
- - run: uv pip install -r requirements-github-actions.txt
- - uses: actions/download-artifact@v6
+ uv.lock
+ - run: uv sync --locked --no-dev --group github-actions
+ - uses: actions/download-artifact@v7
with:
name: coverage-html
path: htmlcov
@@ -44,7 +38,7 @@ jobs:
- name: Upload coverage to Smokeshow
run: |
for i in 1 2 3 4 5; do
- if smokeshow upload htmlcov; then
+ if uv run smokeshow upload htmlcov; then
echo "Smokeshow upload success!"
break
fi
diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yml
index 7d29469a52..88590ffa6c 100644
--- a/.github/workflows/sponsors.yml
+++ b/.github/workflows/sponsors.yml
@@ -10,9 +10,6 @@ on:
required: false
default: "false"
-env:
- UV_SYSTEM_PYTHON: 1
-
jobs:
job:
if: github.repository_owner == 'fastapi'
@@ -24,21 +21,20 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install Dependencies
- run: uv pip install -r requirements-github-actions.txt
+ run: uv sync --locked --no-dev --group github-actions
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
@@ -46,7 +42,7 @@ jobs:
with:
limit-access-to-actor: true
- name: FastAPI People Sponsors
- run: python ./scripts/sponsors.py
+ run: uv run ./scripts/sponsors.py
env:
SPONSORS_TOKEN: ${{ secrets.SPONSORS_TOKEN }}
PR_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}
diff --git a/.github/workflows/test-redistribute.yml b/.github/workflows/test-redistribute.yml
index a44f0b6815..0491d33ba7 100644
--- a/.github/workflows/test-redistribute.yml
+++ b/.github/workflows/test-redistribute.yml
@@ -22,11 +22,11 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.10"
+ python-version-file: ".python-version"
- name: Install build dependencies
run: pip install build
- name: Build source distribution
@@ -40,7 +40,7 @@ jobs:
- name: Install test dependencies
run: |
cd dist/fastapi*/
- pip install -r requirements-tests.txt
+ pip install --group tests --editable .[all]
env:
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
- name: Run source distribution tests
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 9c3e2218b9..891f767175 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,59 +13,45 @@ on:
- cron: "0 0 * * 1"
env:
- UV_SYSTEM_PYTHON: 1
+ UV_NO_SYNC: true
jobs:
- lint:
- runs-on: ubuntu-latest
- steps:
- - name: Dump GitHub context
- env:
- GITHUB_CONTEXT: ${{ toJson(github) }}
- run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
- - name: Set up Python
- uses: actions/setup-python@v6
- with:
- python-version: "3.11"
- - name: Setup uv
- uses: astral-sh/setup-uv@v7
- with:
- version: "0.4.15"
- enable-cache: true
- cache-dependency-glob: |
- requirements**.txt
- pyproject.toml
- - name: Install Dependencies
- run: uv pip install -r requirements-tests.txt
- - name: Install Pydantic v2
- run: uv pip install --upgrade "pydantic>=2.0.2,<3.0.0"
- - name: Lint
- run: bash scripts/lint.sh
-
test:
- runs-on: ubuntu-latest
strategy:
matrix:
- python-version:
- - "3.14"
- - "3.13"
- - "3.12"
- - "3.11"
- - "3.10"
- - "3.9"
- - "3.8"
- pydantic-version: ["pydantic-v1", "pydantic-v2"]
- exclude:
- - python-version: "3.14"
- pydantic-version: "pydantic-v1"
+ os: [ windows-latest, macos-latest ]
+ python-version: [ "3.14" ]
+ include:
+ - os: ubuntu-latest
+ python-version: "3.9"
+ coverage: coverage
+ - os: macos-latest
+ python-version: "3.10"
+ coverage: coverage
+ - os: windows-latest
+ python-version: "3.12"
+ coverage: coverage
+ - os: ubuntu-latest
+ python-version: "3.13"
+ coverage: coverage
+ # Ubuntu with 3.13 needs coverage for CodSpeed benchmarks
+ - os: ubuntu-latest
+ python-version: "3.13"
+ coverage: coverage
+ codspeed: codspeed
+ - os: ubuntu-latest
+ python-version: "3.14"
+ coverage: coverage
fail-fast: false
+ runs-on: ${{ matrix.os }}
+ env:
+ UV_PYTHON: ${{ matrix.python-version }}
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
@@ -73,33 +59,34 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install Dependencies
- run: uv pip install -r requirements-tests.txt
- - name: Install Pydantic v1
- if: matrix.pydantic-version == 'pydantic-v1'
- run: uv pip install "pydantic>=1.10.0,<2.0.0"
- - name: Install Pydantic v2
- if: matrix.pydantic-version == 'pydantic-v2'
- run: uv pip install --upgrade "pydantic>=2.0.2,<3.0.0"
- # TODO: Remove this once Python 3.8 is no longer supported
- - name: Install older AnyIO in Python 3.8
- if: matrix.python-version == '3.8'
- run: uv pip install "anyio[trio]<4.0.0"
+ run: uv sync --locked --no-dev --group tests --extra all
- run: mkdir coverage
- name: Test
- run: bash scripts/test.sh
+ if: matrix.codspeed != 'codspeed'
+ run: uv run bash scripts/test.sh
env:
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
- - name: Store coverage files
- uses: actions/upload-artifact@v5
+ - name: CodSpeed benchmarks
+ if: matrix.codspeed == 'codspeed'
+ uses: CodSpeedHQ/action@v4
+ env:
+ COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
+ CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
with:
- name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }}
+ mode: simulation
+ run: uv run coverage run -m pytest tests/ --codspeed
+ # Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow
+ - name: Store coverage files
+ if: matrix.coverage == 'coverage'
+ uses: actions/upload-artifact@v6
+ with:
+ name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/coverage/.coverage.*') }}
path: coverage
include-hidden-files: true
@@ -111,36 +98,35 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
- python-version: '3.8'
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install Dependencies
- run: uv pip install -r requirements-tests.txt
+ run: uv sync --locked --no-dev --group tests --extra all
- name: Get coverage files
- uses: actions/download-artifact@v6
+ uses: actions/download-artifact@v7
with:
pattern: coverage-*
path: coverage
merge-multiple: true
- run: ls -la coverage
- - run: coverage combine coverage
- - run: coverage report
- - run: coverage html --title "Coverage for ${{ github.sha }}"
+ - run: uv run coverage combine coverage
+ - run: uv run coverage html --title "Coverage for ${{ github.sha }}"
- name: Store coverage HTML
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v6
with:
name: coverage-html
path: htmlcov
include-hidden-files: true
+ - run: uv run coverage report --fail-under=100
# https://github.com/marketplace/actions/alls-green#why
check: # This job does nothing and is only used for the branch protection
diff --git a/.github/workflows/topic-repos.yml b/.github/workflows/topic-repos.yml
index 22b37d59d7..46f6d60847 100644
--- a/.github/workflows/topic-repos.yml
+++ b/.github/workflows/topic-repos.yml
@@ -5,9 +5,6 @@ on:
- cron: "0 12 1 * *"
workflow_dispatch:
-env:
- UV_SYSTEM_PYTHON: 1
-
jobs:
topic-repos:
if: github.repository_owner == 'fastapi'
@@ -19,22 +16,21 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install GitHub Actions dependencies
- run: uv pip install -r requirements-github-actions.txt
+ run: uv sync --locked --no-dev --group github-actions
- name: Update Topic Repos
- run: python ./scripts/topic_repos.py
+ run: uv run ./scripts/topic_repos.py
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}
diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml
index a7fcf84df1..83518614b0 100644
--- a/.github/workflows/translate.yml
+++ b/.github/workflows/translate.yml
@@ -1,6 +1,9 @@
name: Translate
on:
+ # schedule:
+ # - cron: "0 5 15 * *" # Run at 05:00 on the 15 of every month
+
workflow_dispatch:
inputs:
debug_enabled:
@@ -16,7 +19,7 @@ on:
- update-outdated
- add-missing
- update-and-add
- - remove-all-removable
+ - remove-removable
language:
description: Language to translate to as a letter code (e.g. "es" for Spanish)
type: string
@@ -27,14 +30,49 @@ on:
type: string
required: false
default: ""
-
-env:
- UV_SYSTEM_PYTHON: 1
+ commit_in_place:
+ description: Commit changes directly instead of making a PR
+ type: boolean
+ required: false
+ default: false
jobs:
- job:
- if: github.repository_owner == 'fastapi'
+ langs:
runs-on: ubuntu-latest
+ outputs:
+ langs: ${{ steps.show-langs.outputs.langs }}
+ commands: ${{ steps.show-langs.outputs.commands }}
+ steps:
+ - uses: actions/checkout@v6
+ - name: Set up Python
+ uses: actions/setup-python@v6
+ with:
+ python-version-file: ".python-version"
+ - name: Setup uv
+ uses: astral-sh/setup-uv@v7
+ with:
+ cache-dependency-glob: |
+ pyproject.toml
+ uv.lock
+ - name: Install Dependencies
+ run: uv sync --locked --no-dev --group github-actions --group translations
+ - name: Export Language Codes
+ id: show-langs
+ run: |
+ echo "langs=$(uv run ./scripts/translate.py llm-translatable-json)" >> $GITHUB_OUTPUT
+ echo "commands=$(uv run ./scripts/translate.py commands-json)" >> $GITHUB_OUTPUT
+ env:
+ LANGUAGE: ${{ github.event.inputs.language }}
+ COMMAND: ${{ github.event.inputs.command }}
+
+ translate:
+ if: github.repository_owner == 'fastapi'
+ needs: langs
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ lang: ${{ fromJson(needs.langs.outputs.langs) }}
+ command: ${{ fromJson(needs.langs.outputs.commands) }}
permissions:
contents: write
steps:
@@ -42,21 +80,21 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
with:
- python-version: "3.11"
+ python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
- version: "0.4.15"
- enable-cache: true
cache-dependency-glob: |
- requirements**.txt
pyproject.toml
+ uv.lock
- name: Install Dependencies
- run: uv pip install -r requirements-github-actions.txt -r requirements-translations.txt
+ run: uv sync --locked --no-dev --group github-actions --group translations
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
@@ -68,10 +106,12 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: FastAPI Translate
run: |
- python ./scripts/translate.py ${{ github.event.inputs.command }}
- python ./scripts/translate.py make-pr
+ uv run ./scripts/translate.py ${{ matrix.command }}
+ uv run ./scripts/translate.py make-pr
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_TRANSLATIONS }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- LANGUAGE: ${{ github.event.inputs.language }}
+ LANGUAGE: ${{ matrix.lang }}
EN_PATH: ${{ github.event.inputs.en_path }}
+ COMMAND: ${{ matrix.command }}
+ COMMIT_IN_PLACE: ${{ github.event.inputs.commit_in_place }}
diff --git a/.gitignore b/.gitignore
index ef6364a9a7..243cdb93a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,5 @@ archive.zip
# macOS
.DS_Store
+
+.codspeed
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8e5eba4c4b..d88b70b7b6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,25 +1,66 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
-default_language_version:
- python: python3.10
repos:
-- repo: https://github.com/pre-commit/pre-commit-hooks
+ - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- - id: check-added-large-files
- - id: check-toml
- - id: check-yaml
+ - id: check-added-large-files
+ args: ['--maxkb=750']
+ exclude: ^uv.lock$
+ - id: check-toml
+ - id: check-yaml
args:
- - --unsafe
- - id: end-of-file-fixer
- - id: trailing-whitespace
-- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.14.3
+ - --unsafe
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+
+ - repo: local
hooks:
- - id: ruff
+ - id: local-ruff-check
+ name: ruff check
+ entry: uv run ruff check --force-exclude --fix --exit-non-zero-on-fix
+ require_serial: true
+ language: unsupported
+ types: [python]
+
+ - id: local-ruff-format
+ name: ruff format
+ entry: uv run ruff format --force-exclude --exit-non-zero-on-format
+ require_serial: true
+ language: unsupported
+ types: [python]
+
+ - id: add-permalinks-pages
+ language: unsupported
+ name: add-permalinks-pages
+ entry: uv run ./scripts/docs.py add-permalinks-pages
args:
- - --fix
- - id: ruff-format
-ci:
- autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
- autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate
+ - --update-existing
+ files: ^docs/en/docs/.*\.md$
+
+ - id: generate-readme
+ language: unsupported
+ name: generate README.md from index.md
+ entry: uv run ./scripts/docs.py generate-readme
+ files: ^docs/en/docs/index\.md|docs/en/data/sponsors\.yml|scripts/docs\.py$
+ pass_filenames: false
+
+ - id: update-languages
+ language: unsupported
+ name: update languages
+ entry: uv run ./scripts/docs.py update-languages
+ files: ^docs/.*|scripts/docs\.py$
+ pass_filenames: false
+
+ - id: ensure-non-translated
+ language: unsupported
+ name: ensure non-translated files are not modified
+ entry: uv run ./scripts/docs.py ensure-non-translated
+ files: ^docs/(?!en/).*|^scripts/docs\.py$
+ pass_filenames: false
+
+ - id: fix-translations
+ language: unsupported
+ name: fix translations
+ entry: uv run ./scripts/translation_fixer.py fix-pages
+ files: ^docs/(?!en/).*/docs/.*\.md$
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000000..2c0733315e
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.11
diff --git a/README.md b/README.md
index 09cd38da16..1057b86942 100644
--- a/README.md
+++ b/README.md
@@ -40,11 +40,16 @@ The key features are:
* **Robust**: Get production-ready code. With automatic interactive documentation.
* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema.
-* estimation based on tests on an internal development team, building production applications.
+* estimation based on tests conducted by an internal development team, building production applications.
## Sponsors
+### Keystone Sponsor
+
+
+
+### Gold and Silver Sponsors
@@ -56,6 +61,7 @@ The key features are:
+
@@ -114,6 +120,12 @@ The key features are:
---
+## FastAPI mini documentary
+
+There's a FastAPI mini documentary released at the end of 2025, you can watch it online:
+
+
+
## **Typer**, the FastAPI of CLIs
@@ -447,6 +459,58 @@ For a more complete example including more features, see the FastAPI Cloud, go and join the waiting list if you haven't. 🚀
+
+If you already have a **FastAPI Cloud** account (we invited you from the waiting list 😉), you can deploy your application with one command.
+
+Before deploying, make sure you are logged in:
+
+
+
## **Typer**, das FastAPI der CLIs { #typer-the-fastapi-of-clis }
fastapi dev main.py macht ...fastapi dev main.py ...
@@ -225,21 +193,7 @@ Und trotzdem weiß der Editor, dass es sich um ein `str` handelt, und bietet ent
Das Gleiche gilt für die Deklaration eines Tupels – `tuple` – und einer Menge – `set`:
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial007_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial007.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
Das bedeutet:
@@ -254,21 +208,7 @@ Der erste Typ-Parameter ist für die Schlüssel des `dict`.
Der zweite Typ-Parameter ist für die Werte des `dict`:
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
Das bedeutet:
@@ -282,7 +222,7 @@ Sie können deklarieren, dass eine Variable einer von **verschiedenen Typen** se
In Python 3.6 und höher (inklusive Python 3.10) können Sie den `Union`-Typ von `typing` verwenden und die möglichen Typen innerhalb der eckigen Klammern auflisten.
-In Python 3.10 gibt es zusätzlich eine **neue Syntax**, die es erlaubt, die möglichen Typen getrennt von einem vertikalen Balken (`|`) aufzulisten.
+In Python 3.10 gibt es zusätzlich eine **neue Syntax**, die es erlaubt, die möglichen Typen getrennt von einem vertikalen Balken (`|`) aufzulisten.
//// tab | Python 3.10+
@@ -292,10 +232,10 @@ In Python 3.10 gibt es zusätzlich eine **neue Syntax**, die es erlaubt, die mö
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008b.py!}
+{!> ../../docs_src/python_types/tutorial008b_py39.py!}
```
////
@@ -309,7 +249,7 @@ Sie können deklarieren, dass ein Wert ein `str`, aber vielleicht auch `None` se
In Python 3.6 und darüber (inklusive Python 3.10) können Sie das deklarieren, indem Sie `Optional` vom `typing` Modul importieren und verwenden.
```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
+{!../../docs_src/python_types/tutorial009_py39.py!}
```
Wenn Sie `Optional[str]` anstelle von nur `str` verwenden, wird Ihr Editor Ihnen dabei helfen, Fehler zu erkennen, bei denen Sie annehmen könnten, dass ein Wert immer eine String (`str`) ist, obwohl er auch `None` sein könnte.
@@ -326,18 +266,18 @@ Das bedeutet auch, dass Sie in Python 3.10 `Something | None` verwenden können:
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009.py!}
+{!> ../../docs_src/python_types/tutorial009_py39.py!}
```
////
-//// tab | Python 3.8+ Alternative
+//// tab | Python 3.9+ Alternative
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009b.py!}
+{!> ../../docs_src/python_types/tutorial009b_py39.py!}
```
////
@@ -353,11 +293,11 @@ Beide sind äquivalent und im Hintergrund dasselbe, aber ich empfehle `Union` st
Ich denke, `Union[SomeType, None]` ist expliziter bezüglich seiner Bedeutung.
-Es geht nur um Wörter und Namen. Aber diese Worte können beeinflussen, wie Sie und Ihre Teamkollegen über den Code denken.
+Es geht nur um Worte und Namen. Aber diese Worte können beeinflussen, wie Sie und Ihre Teamkollegen über den Code denken.
Nehmen wir zum Beispiel diese Funktion:
-{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *}
+{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
Der Parameter `name` ist definiert als `Optional[str]`, aber er ist **nicht optional**, Sie können die Funktion nicht ohne diesen Parameter aufrufen:
@@ -390,13 +330,13 @@ Sie können die eingebauten Typen als Generics verwenden (mit eckigen Klammern u
* `set`
* `dict`
-Verwenden Sie für den Rest, wie unter Python 3.8, das `typing`-Modul:
+Und ebenso wie bei früheren Python-Versionen, aus dem `typing`-Modul:
* `Union`
-* `Optional` (so wie unter Python 3.8)
+* `Optional`
* ... und andere.
-In Python 3.10 können Sie als Alternative zu den Generics `Union` und `Optional` den vertikalen Balken (`|`) verwenden, um Vereinigungen von Typen zu deklarieren, das ist besser und einfacher.
+In Python 3.10 können Sie als Alternative zu den Generics `Union` und `Optional` den vertikalen Balken (`|`) verwenden, um Vereinigungen von Typen zu deklarieren, das ist besser und einfacher.
////
@@ -409,7 +349,7 @@ Sie können die eingebauten Typen als Generics verwenden (mit eckigen Klammern u
* `set`
* `dict`
-Verwenden Sie für den Rest, wie unter Python 3.8, das `typing`-Modul:
+Und Generics aus dem `typing`-Modul:
* `Union`
* `Optional`
@@ -417,29 +357,17 @@ Verwenden Sie für den Rest, wie unter Python 3.8, das `typing`-Modul:
////
-//// tab | Python 3.8+
-
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Union`
-* `Optional`
-* ... und andere.
-
-////
-
### Klassen als Typen { #classes-as-types }
Sie können auch eine Klasse als Typ einer Variablen deklarieren.
Nehmen wir an, Sie haben eine Klasse `Person`, mit einem Namen:
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
+{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
Dann können Sie eine Variable vom Typ `Person` deklarieren:
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
+{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
Und wiederum bekommen Sie die volle Editor-Unterstützung:
@@ -463,29 +391,7 @@ Und Sie erhalten volle Editor-Unterstützung für dieses Objekt.
Ein Beispiel aus der offiziellen Pydantic Dokumentation:
-//// tab | Python 3.10+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py310.py!}
-```
-
-////
-
-//// tab | Python 3.9+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial011_py310.py *}
/// info | Info
@@ -507,27 +413,9 @@ Pydantic verhält sich speziell, wenn Sie `Optional` oder `Union[Something, None
Python bietet auch die Möglichkeit, **zusätzliche Metadaten** in Typhinweisen unterzubringen, mittels `Annotated`.
-//// tab | Python 3.9+
+Seit Python 3.9 ist `Annotated` ein Teil der Standardbibliothek, Sie können es von `typing` importieren.
-In Python 3.9 ist `Annotated` ein Teil der Standardbibliothek, Sie können es von `typing` importieren.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-In Versionen niedriger als Python 3.9 importieren Sie `Annotated` von `typing_extensions`.
-
-Es wird bereits mit **FastAPI** installiert sein.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
Python selbst macht nichts mit `Annotated`. Für Editoren und andere Tools ist der Typ immer noch `str`.
diff --git a/docs/de/docs/resources/index.md b/docs/de/docs/resources/index.md
index 2c5046c731..52b32b1480 100644
--- a/docs/de/docs/resources/index.md
+++ b/docs/de/docs/resources/index.md
@@ -1,3 +1,3 @@
# Ressourcen { #resources }
-Zusätzliche Ressourcen, externe Links, Artikel und mehr. ✈️
+Zusätzliche Ressourcen, externe Links und mehr. ✈️
diff --git a/docs/de/docs/tutorial/background-tasks.md b/docs/de/docs/tutorial/background-tasks.md
index 2c381ccfac..1d34430dc6 100644
--- a/docs/de/docs/tutorial/background-tasks.md
+++ b/docs/de/docs/tutorial/background-tasks.md
@@ -15,7 +15,7 @@ Hierzu zählen beispielsweise:
Importieren Sie zunächst `BackgroundTasks` und definieren Sie einen Parameter in Ihrer *Pfadoperation-Funktion* mit der Typdeklaration `BackgroundTasks`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
**FastAPI** erstellt für Sie das Objekt vom Typ `BackgroundTasks` und übergibt es als diesen Parameter.
@@ -31,13 +31,13 @@ In diesem Fall schreibt die Taskfunktion in eine Datei (den Versand einer E-Mail
Und da der Schreibvorgang nicht `async` und `await` verwendet, definieren wir die Funktion mit normalem `def`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
## Den Hintergrundtask hinzufügen { #add-the-background-task }
Übergeben Sie innerhalb Ihrer *Pfadoperation-Funktion* Ihre Taskfunktion mit der Methode `.add_task()` an das *Hintergrundtasks*-Objekt:
-{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
`.add_task()` erhält als Argumente:
diff --git a/docs/de/docs/tutorial/bigger-applications.md b/docs/de/docs/tutorial/bigger-applications.md
index 928d50adff..963baf44d5 100644
--- a/docs/de/docs/tutorial/bigger-applications.md
+++ b/docs/de/docs/tutorial/bigger-applications.md
@@ -85,9 +85,7 @@ Sie können die *Pfadoperationen* für dieses Modul mit `APIRouter` erstellen.
Sie importieren ihn und erstellen eine „Instanz“ auf die gleiche Weise wie mit der Klasse `FastAPI`:
-```Python hl_lines="1 3" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
### *Pfadoperationen* mit `APIRouter` { #path-operations-with-apirouter }
@@ -95,9 +93,7 @@ Und dann verwenden Sie ihn, um Ihre *Pfadoperationen* zu deklarieren.
Verwenden Sie ihn auf die gleiche Weise wie die Klasse `FastAPI`:
-```Python hl_lines="6 11 16" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
Sie können sich `APIRouter` als eine „Mini-`FastAPI`“-Klasse vorstellen.
@@ -121,35 +117,7 @@ Also fügen wir sie in ihr eigenes `dependencies`-Modul (`app/dependencies.py`)
Wir werden nun eine einfache Abhängigkeit verwenden, um einen benutzerdefinierten `X-Token`-Header zu lesen:
-//// tab | Python 3.9+
-
-```Python hl_lines="3 6-8" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 5-7" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app_an/dependencies.py!}
-```
-
-////
-
-//// tab | Python 3.8+ nicht annotiert
-
-/// tip | Tipp
-
-Bevorzugen Sie die `Annotated`-Version, falls möglich.
-
-///
-
-```Python hl_lines="1 4-6" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app/dependencies.py!}
-```
-
-////
+{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
/// tip | Tipp
@@ -181,9 +149,7 @@ Wir wissen, dass alle *Pfadoperationen* in diesem Modul folgendes haben:
Anstatt also alles zu jeder *Pfadoperation* hinzuzufügen, können wir es dem `APIRouter` hinzufügen.
-```Python hl_lines="5-10 16 21" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
Da der Pfad jeder *Pfadoperation* mit `/` beginnen muss, wie in:
@@ -242,9 +208,7 @@ Und wir müssen die Abhängigkeitsfunktion aus dem Modul `app.dependencies` impo
Daher verwenden wir einen relativen Import mit `..` für die Abhängigkeiten:
-```Python hl_lines="3" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
#### Wie relative Importe funktionieren { #how-relative-imports-work }
@@ -315,9 +279,7 @@ Wir fügen weder das Präfix `/items` noch `tags=["items"]` zu jeder *Pfadoperat
Aber wir können immer noch _mehr_ `tags` hinzufügen, die auf eine bestimmte *Pfadoperation* angewendet werden, sowie einige zusätzliche `responses`, die speziell für diese *Pfadoperation* gelten:
-```Python hl_lines="30-31" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *}
/// tip | Tipp
@@ -343,17 +305,13 @@ Sie importieren und erstellen wie gewohnt eine `FastAPI`-Klasse.
Und wir können sogar [globale Abhängigkeiten](dependencies/global-dependencies.md){.internal-link target=_blank} deklarieren, die mit den Abhängigkeiten für jeden `APIRouter` kombiniert werden:
-```Python hl_lines="1 3 7" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
### Den `APIRouter` importieren { #import-the-apirouter }
Jetzt importieren wir die anderen Submodule, die `APIRouter` haben:
-```Python hl_lines="4-5" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
Da es sich bei den Dateien `app/routers/users.py` und `app/routers/items.py` um Submodule handelt, die Teil desselben Python-Packages `app` sind, können wir einen einzelnen Punkt `.` verwenden, um sie mit „relativen Imports“ zu importieren.
@@ -416,17 +374,13 @@ würde der `router` von `users` den von `items` überschreiben und wir könnten
Um also beide in derselben Datei verwenden zu können, importieren wir die Submodule direkt:
-```Python hl_lines="5" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *}
### Die `APIRouter` für `users` und `items` inkludieren { #include-the-apirouters-for-users-and-items }
Inkludieren wir nun die `router` aus diesen Submodulen `users` und `items`:
-```Python hl_lines="10-11" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *}
/// info | Info
@@ -466,17 +420,13 @@ Sie enthält einen `APIRouter` mit einigen administrativen *Pfadoperationen*, di
In diesem Beispiel wird es ganz einfach sein. Nehmen wir jedoch an, dass wir, da sie mit anderen Projekten in der Organisation geteilt wird, sie nicht ändern und kein `prefix`, `dependencies`, `tags`, usw. direkt zum `APIRouter` hinzufügen können:
-```Python hl_lines="3" title="app/internal/admin.py"
-{!../../docs_src/bigger_applications/app/internal/admin.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
Aber wir möchten immer noch ein benutzerdefiniertes `prefix` festlegen, wenn wir den `APIRouter` einbinden, sodass alle seine *Pfadoperationen* mit `/admin` beginnen, wir möchten es mit den `dependencies` sichern, die wir bereits für dieses Projekt haben, und wir möchten `tags` und `responses` hinzufügen.
Wir können das alles deklarieren, ohne den ursprünglichen `APIRouter` ändern zu müssen, indem wir diese Parameter an `app.include_router()` übergeben:
-```Python hl_lines="14-17" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *}
Auf diese Weise bleibt der ursprüngliche `APIRouter` unverändert, sodass wir dieselbe `app/internal/admin.py`-Datei weiterhin mit anderen Projekten in der Organisation teilen können.
@@ -497,9 +447,7 @@ Wir können *Pfadoperationen* auch direkt zur `FastAPI`-App hinzufügen.
Hier machen wir es ... nur um zu zeigen, dass wir es können 🤷:
-```Python hl_lines="21-23" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *}
und es wird korrekt funktionieren, zusammen mit allen anderen *Pfadoperationen*, die mit `app.include_router()` hinzugefügt wurden.
diff --git a/docs/de/docs/tutorial/body-nested-models.md b/docs/de/docs/tutorial/body-nested-models.md
index 324d31928d..65a5d7c1de 100644
--- a/docs/de/docs/tutorial/body-nested-models.md
+++ b/docs/de/docs/tutorial/body-nested-models.md
@@ -14,35 +14,14 @@ Das bewirkt, dass `tags` eine Liste ist, wenngleich es nichts über den Typ der
Aber Python erlaubt es, Listen mit inneren Typen, auch „Typ-Parameter“ genannt, zu deklarieren.
-### `List` von `typing` importieren { #import-typings-list }
-
-In Python 3.9 oder darüber können Sie einfach `list` verwenden, um diese Typannotationen zu deklarieren, wie wir unten sehen werden. 💡
-
-In Python-Versionen vor 3.9 (3.6 und darüber), müssen Sie zuerst `List` von Pythons Standardmodul `typing` importieren.
-
-{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
-
### Eine `list` mit einem Typ-Parameter deklarieren { #declare-a-list-with-a-type-parameter }
-Um Typen wie `list`, `dict`, `tuple` mit inneren Typ-Parametern (inneren Typen) zu deklarieren:
-
-* Wenn Sie eine Python-Version kleiner als 3.9 verwenden, importieren Sie das Äquivalent zum entsprechenden Typ vom `typing`-Modul
-* Überreichen Sie den/die inneren Typ(en) von eckigen Klammern umschlossen, `[` und `]`, als „Typ-Parameter“
-
-In Python 3.9 wäre das:
+Um Typen zu deklarieren, die Typ-Parameter (innere Typen) haben, wie `list`, `dict`, `tuple`, übergeben Sie den/die inneren Typ(en) als „Typ-Parameter“ in eckigen Klammern: `[` und `]`
```Python
my_list: list[str]
```
-Und in Python-Versionen vor 3.9:
-
-```Python
-from typing import List
-
-my_list: List[str]
-```
-
Das ist alles Standard-Python-Syntax für Typdeklarationen.
Verwenden Sie dieselbe Standardsyntax für Modellattribute mit inneren Typen.
@@ -178,12 +157,6 @@ Beachten Sie, wie `Offer` eine Liste von `Item`s hat, die ihrerseits eine option
Wenn das äußerste Element des JSON-Bodys, das Sie erwarten, ein JSON-`array` (eine Python-`list`) ist, können Sie den Typ im Funktionsparameter deklarieren, mit der gleichen Syntax wie in Pydantic-Modellen:
-```Python
-images: List[Image]
-```
-
-oder in Python 3.9 und darüber:
-
```Python
images: list[Image]
```
diff --git a/docs/de/docs/tutorial/body-updates.md b/docs/de/docs/tutorial/body-updates.md
index aa62199feb..d260998e91 100644
--- a/docs/de/docs/tutorial/body-updates.md
+++ b/docs/de/docs/tutorial/body-updates.md
@@ -50,14 +50,6 @@ Wenn Sie Teil-Aktualisierungen entgegennehmen, ist der `exclude_unset`-Parameter
Wie in `item.model_dump(exclude_unset=True)`.
-/// info | Info
-
-In Pydantic v1 hieß diese Methode `.dict()`, in Pydantic v2 wurde sie deprecatet (aber immer noch unterstützt) und in `.model_dump()` umbenannt.
-
-Die Beispiele hier verwenden `.dict()` für die Kompatibilität mit Pydantic v1, Sie sollten jedoch stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden können.
-
-///
-
Das wird ein `dict` erstellen, mit nur den Daten, die gesetzt wurden, als das `item`-Modell erstellt wurde, Defaultwerte ausgeschlossen.
Sie können das verwenden, um ein `dict` zu erstellen, das nur die (im Request) gesendeten Daten enthält, ohne Defaultwerte:
@@ -68,14 +60,6 @@ Sie können das verwenden, um ein `dict` zu erstellen, das nur die (im deprecatet (aber immer noch unterstützt) und in `.model_copy()` umbenannt.
-
-Die Beispiele hier verwenden `.copy()` für die Kompatibilität mit Pydantic v1, Sie sollten jedoch stattdessen `.model_copy()` verwenden, wenn Sie Pydantic v2 verwenden können.
-
-///
-
Wie in `stored_item_model.model_copy(update=update_data)`:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
diff --git a/docs/de/docs/tutorial/body.md b/docs/de/docs/tutorial/body.md
index 1e6382b6f2..cdf3122f2f 100644
--- a/docs/de/docs/tutorial/body.md
+++ b/docs/de/docs/tutorial/body.md
@@ -127,14 +127,6 @@ Innerhalb der Funktion können Sie alle Attribute des Modellobjekts direkt verwe
{* ../../docs_src/body/tutorial002_py310.py *}
-/// info | Info
-
-In Pydantic v1 hieß die Methode `.dict()`, sie wurde in Pydantic v2 deprecatet (aber weiterhin unterstützt) und in `.model_dump()` umbenannt.
-
-Die Beispiele hier verwenden `.dict()` zur Kompatibilität mit Pydantic v1, aber Sie sollten stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 nutzen können.
-
-///
-
## Requestbody- + Pfad-Parameter { #request-body-path-parameters }
Sie können Pfad-Parameter und den Requestbody gleichzeitig deklarieren.
@@ -162,7 +154,7 @@ Die Funktionsparameter werden wie folgt erkannt:
FastAPI weiß, dass der Wert von `q` nicht erforderlich ist, aufgrund des definierten Defaultwertes `= None`.
-Das `str | None` (Python 3.10+) oder `Union` in `Union[str, None]` (Python 3.8+) wird von FastAPI nicht verwendet, um zu bestimmen, dass der Wert nicht erforderlich ist. FastAPI weiß, dass er nicht erforderlich ist, weil er einen Defaultwert von `= None` hat.
+Das `str | None` (Python 3.10+) oder `Union` in `Union[str, None]` (Python 3.9+) wird von FastAPI nicht verwendet, um zu bestimmen, dass der Wert nicht erforderlich ist. FastAPI weiß, dass er nicht erforderlich ist, weil er einen Defaultwert von `= None` hat.
Das Hinzufügen der Typannotationen ermöglicht jedoch Ihrem Editor, Ihnen eine bessere Unterstützung zu bieten und Fehler zu erkennen.
diff --git a/docs/de/docs/tutorial/cookie-param-models.md b/docs/de/docs/tutorial/cookie-param-models.md
index 2baf3d70dd..25718bd33a 100644
--- a/docs/de/docs/tutorial/cookie-param-models.md
+++ b/docs/de/docs/tutorial/cookie-param-models.md
@@ -50,7 +50,7 @@ Ihre API hat jetzt die Macht, ihre eigene Response**.
diff --git a/docs/de/docs/tutorial/cors.md b/docs/de/docs/tutorial/cors.md
index 191a7b4ef3..81f0f36051 100644
--- a/docs/de/docs/tutorial/cors.md
+++ b/docs/de/docs/tutorial/cors.md
@@ -46,7 +46,7 @@ Sie können auch angeben, ob Ihr Backend erlaubt:
* Bestimmte HTTP-Methoden (`POST`, `PUT`) oder alle mit der Wildcard `"*"`.
* Bestimmte HTTP-Header oder alle mit der Wildcard `"*"`.
-{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
+{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *}
Die von der `CORSMiddleware`-Implementierung verwendeten Defaultparameter sind standardmäßig restriktiv, daher müssen Sie bestimmte Origins, Methoden oder Header ausdrücklich aktivieren, damit Browser sie in einem Cross-Domain-Kontext verwenden dürfen.
diff --git a/docs/de/docs/tutorial/debugging.md b/docs/de/docs/tutorial/debugging.md
index 0a31f86536..0d12877c10 100644
--- a/docs/de/docs/tutorial/debugging.md
+++ b/docs/de/docs/tutorial/debugging.md
@@ -6,7 +6,7 @@ Sie können den Debugger in Ihrem Editor verbinden, zum Beispiel mit Visual Stud
Importieren und führen Sie `uvicorn` direkt in Ihrer FastAPI-Anwendung aus:
-{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
+{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *}
### Über `__name__ == "__main__"` { #about-name-main }
diff --git a/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md
index 3d4493f353..7df0842eb1 100644
--- a/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md
+++ b/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md
@@ -101,7 +101,7 @@ Jetzt können Sie Ihre Abhängigkeit mithilfe dieser Klasse deklarieren.
Beachten Sie, wie wir `CommonQueryParams` im obigen Code zweimal schreiben:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ nicht annotiert
+//// tab | Python 3.9+ nicht annotiert
/// tip | Tipp
@@ -137,7 +137,7 @@ Aus diesem extrahiert FastAPI die deklarierten Parameter, und dieses ist es, was
In diesem Fall hat das erste `CommonQueryParams` in:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, ...
@@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ...
////
-//// tab | Python 3.8+ nicht annotiert
+//// tab | Python 3.9+ nicht annotiert
/// tip | Tipp
@@ -163,7 +163,7 @@ commons: CommonQueryParams ...
Sie könnten tatsächlich einfach schreiben:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[Any, Depends(CommonQueryParams)]
@@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ nicht annotiert
+//// tab | Python 3.9+ nicht annotiert
/// tip | Tipp
@@ -197,7 +197,7 @@ Es wird jedoch empfohlen, den Typ zu deklarieren, da Ihr Editor so weiß, was al
Aber Sie sehen, dass wir hier etwas Codeduplizierung haben, indem wir `CommonQueryParams` zweimal schreiben:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ nicht annotiert
+//// tab | Python 3.9+ nicht annotiert
/// tip | Tipp
@@ -225,7 +225,7 @@ In diesem speziellen Fall können Sie Folgendes tun:
Anstatt zu schreiben:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ nicht annotiert
+//// tab | Python 3.9+ nicht annotiert
/// tip | Tipp
@@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
... schreiben Sie:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends()]
@@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()]
////
-//// tab | Python 3.8 nicht annotiert
+//// tab | Python 3.9+ nicht annotiert
/// tip | Tipp
diff --git a/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md
index e65b073a2e..0083e7e7ea 100644
--- a/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md
+++ b/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md
@@ -29,15 +29,15 @@ Sie könnten damit beispielsweise eine Datenbanksession erstellen und diese nach
Nur der Code vor und einschließlich der `yield`-Anweisung wird ausgeführt, bevor eine Response erzeugt wird:
-{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *}
Der ge`yield`ete Wert ist das, was in *Pfadoperationen* und andere Abhängigkeiten eingefügt wird:
-{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *}
Der auf die `yield`-Anweisung folgende Code wird nach der Response ausgeführt:
-{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *}
/// tip | Tipp
@@ -57,7 +57,7 @@ Sie können also mit `except SomeException` diese bestimmte Exception innerhalb
Auf die gleiche Weise können Sie `finally` verwenden, um sicherzustellen, dass die Exit-Schritte ausgeführt werden, unabhängig davon, ob eine Exception geworfen wurde oder nicht.
-{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *}
## Unterabhängigkeiten mit `yield` { #sub-dependencies-with-yield }
@@ -184,6 +184,51 @@ Wenn Sie in dem Code der *Pfadoperation-Funktion* irgendeine Exception auslösen
///
+## Frühes Beenden und `scope` { #early-exit-and-scope }
+
+Normalerweise wird der Exit-Code von Abhängigkeiten mit `yield` ausgeführt **nachdem die Response** an den Client gesendet wurde.
+
+Wenn Sie aber wissen, dass Sie die Abhängigkeit nach der Rückkehr aus der *Pfadoperation-Funktion* nicht mehr benötigen, können Sie `Depends(scope="function")` verwenden, um FastAPI mitzuteilen, dass es die Abhängigkeit nach der Rückkehr aus der *Pfadoperation-Funktion* schließen soll, jedoch **bevor** die **Response gesendet wird**.
+
+{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *}
+
+`Depends()` erhält einen `scope`-Parameter, der sein kann:
+
+* `"function"`: startet die Abhängigkeit vor der *Pfadoperation-Funktion*, die den Request bearbeitet, beendet die Abhängigkeit nach dem Ende der *Pfadoperation-Funktion*, aber **bevor** die Response an den Client zurückgesendet wird. Die Abhängigkeitsfunktion wird also **um** die *Pfadoperation-**Funktion*** **herum** ausgeführt.
+* `"request"`: startet die Abhängigkeit vor der *Pfadoperation-Funktion*, die den Request bearbeitet (ähnlich wie bei `"function"`), beendet sie jedoch **nachdem** die Response an den Client zurückgesendet wurde. Die Abhängigkeitsfunktion wird also **um** den **Request**- und Response-Zyklus **herum** ausgeführt.
+
+Wenn nicht angegeben und die Abhängigkeit `yield` hat, hat sie standardmäßig einen `scope` von `"request"`.
+
+### `scope` für Unterabhängigkeiten { #scope-for-sub-dependencies }
+
+Wenn Sie eine Abhängigkeit mit `scope="request"` (dem Default) deklarieren, muss jede Unterabhängigkeit ebenfalls einen `scope` von `"request"` haben.
+
+Eine Abhängigkeit mit `scope` von `"function"` kann jedoch Abhängigkeiten mit `scope` von `"function"` und `scope` von `"request"` haben.
+
+Das liegt daran, dass jede Abhängigkeit in der Lage sein muss, ihren Exit-Code vor den Unterabhängigkeiten auszuführen, da sie diese während ihres Exit-Codes möglicherweise noch verwenden muss.
+
+```mermaid
+sequenceDiagram
+
+participant client as Client
+participant dep_req as Abhängigkeit scope="request"
+participant dep_func as Abhängigkeit scope="function"
+participant operation as Pfadoperation
+
+ client ->> dep_req: Startet den Request
+ Note over dep_req: Führt den Code bis zum yield aus
+ dep_req ->> dep_func: Reicht Abhängigkeit weiter
+ Note over dep_func: Führt den Code bis zum yield aus
+ dep_func ->> operation: Führt Pfadoperation mit Abhängigkeit aus
+ operation ->> dep_func: Kehrt aus Pfadoperation zurück
+ Note over dep_func: Führt Code nach yield aus
+ Note over dep_func: ✅ Abhängigkeit geschlossen
+ dep_func ->> client: Sendet Response an Client
+ Note over client: Response gesendet
+ Note over dep_req: Führt Code nach yield aus
+ Note over dep_req: ✅ Abhängigkeit geschlossen
+```
+
## Abhängigkeiten mit `yield`, `HTTPException`, `except` und Hintergrundtasks { #dependencies-with-yield-httpexception-except-and-background-tasks }
Abhängigkeiten mit `yield` haben sich im Laufe der Zeit weiterentwickelt, um verschiedene Anwendungsfälle abzudecken und einige Probleme zu beheben.
@@ -223,7 +268,7 @@ In Python können Sie Kontextmanager erstellen, indem Sie deprecatet (aber weiterhin unterstützt) und in `.model_dump()` umbenannt.
-
-Die Beispiele hier verwenden `.dict()` für die Kompatibilität mit Pydantic v1, aber Sie sollten `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden können.
-
-///
-
-### Über `**user_in.dict()` { #about-user-in-dict }
-
-#### Die `.dict()`-Methode von Pydantic { #pydantics-dict }
+#### Pydantics `.model_dump()` { #pydantics-model-dump }
`user_in` ist ein Pydantic-Modell der Klasse `UserIn`.
-Pydantic-Modelle haben eine `.dict()`-Methode, die ein `dict` mit den Daten des Modells zurückgibt.
+Pydantic-Modelle haben eine `.model_dump()`-Methode, die ein `dict` mit den Daten des Modells zurückgibt.
Wenn wir also ein Pydantic-Objekt `user_in` erstellen, etwa so:
@@ -47,7 +39,7 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com
und dann aufrufen:
```Python
-user_dict = user_in.dict()
+user_dict = user_in.model_dump()
```
haben wir jetzt ein `dict` mit den Daten in der Variablen `user_dict` (es ist ein `dict` statt eines Pydantic-Modellobjekts).
@@ -103,20 +95,20 @@ UserInDB(
#### Ein Pydantic-Modell aus dem Inhalt eines anderen { #a-pydantic-model-from-the-contents-of-another }
-Da wir im obigen Beispiel `user_dict` von `user_in.dict()` bekommen haben, wäre dieser Code:
+Da wir im obigen Beispiel `user_dict` von `user_in.model_dump()` bekommen haben, wäre dieser Code:
```Python
-user_dict = user_in.dict()
+user_dict = user_in.model_dump()
UserInDB(**user_dict)
```
gleichwertig zu:
```Python
-UserInDB(**user_in.dict())
+UserInDB(**user_in.model_dump())
```
-... weil `user_in.dict()` ein `dict` ist, und dann lassen wir Python es „entpacken“, indem wir es an `UserInDB` mit vorangestelltem `**` übergeben.
+... weil `user_in.model_dump()` ein `dict` ist, und dann lassen wir Python es „entpacken“, indem wir es an `UserInDB` mit vorangestelltem `**` übergeben.
Auf diese Weise erhalten wir ein Pydantic-Modell aus den Daten eines anderen Pydantic-Modells.
@@ -125,7 +117,7 @@ Auf diese Weise erhalten wir ein Pydantic-Modell aus den Daten eines anderen Pyd
Und dann fügen wir das zusätzliche Schlüsselwort-Argument `hashed_password=hashed_password` hinzu, wie in:
```Python
-UserInDB(**user_in.dict(), hashed_password=hashed_password)
+UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
```
... was so ist wie:
@@ -180,7 +172,6 @@ Wenn Sie eine FastAPI Cloud deployen, treten Sie der Warteliste bei, falls Sie es noch nicht getan haben. 🚀
+
+Wenn Sie bereits ein **FastAPI Cloud**-Konto haben (wir haben Sie von der Warteliste eingeladen 😉), können Sie Ihre Anwendung mit einem Befehl deployen.
+
+Vor dem Deployen, stellen Sie sicher, dass Sie eingeloggt sind:
+
+kwargs. Selbst wenn diese keinen Defaultwert haben.
-{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
### Besser mit `Annotated` { #better-with-annotated }
diff --git a/docs/de/docs/tutorial/path-params.md b/docs/de/docs/tutorial/path-params.md
index 1db288fb87..1de4973155 100644
--- a/docs/de/docs/tutorial/path-params.md
+++ b/docs/de/docs/tutorial/path-params.md
@@ -2,7 +2,7 @@
Sie können Pfad-„Parameter“ oder -„Variablen“ mit der gleichen Syntax deklarieren, welche in Python-Formatstrings verwendet wird:
-{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
+{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
Der Wert des Pfad-Parameters `item_id` wird Ihrer Funktion als das Argument `item_id` übergeben.
@@ -16,7 +16,7 @@ Wenn Sie dieses Beispiel ausführen und auf Enumerationen (oder Enums) gibt es in Python seit Version 3.4.
-
-///
/// tip | Tipp
@@ -158,7 +153,7 @@ Falls Sie sich fragen, was „AlexNet“, „ResNet“ und „LeNet“ ist, das
Dann erstellen Sie einen *Pfad-Parameter*, der als Typ die gerade erstellte Enum-Klasse hat (`ModelName`):
-{* ../../docs_src/path_params/tutorial005.py hl[16] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
### Die API-Dokumentation testen { #check-the-docs }
@@ -174,13 +169,13 @@ Der *Pfad-Parameter* wird ein *Query ist die Menge von Schlüssel-Wert-Paaren, die nach dem `?` in einer URL folgen und durch `&`-Zeichen getrennt sind.
@@ -127,7 +127,7 @@ Wenn Sie keinen spezifischen Wert haben wollen, sondern der Parameter einfach op
Aber wenn Sie wollen, dass ein Query-Parameter erforderlich ist, vergeben Sie einfach keinen Defaultwert:
-{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
+{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
Hier ist `needy` ein erforderlicher Query-Parameter vom Typ `str`.
diff --git a/docs/de/docs/tutorial/response-model.md b/docs/de/docs/tutorial/response-model.md
index 7b77125cb5..f759bb2571 100644
--- a/docs/de/docs/tutorial/response-model.md
+++ b/docs/de/docs/tutorial/response-model.md
@@ -183,7 +183,7 @@ Es kann Fälle geben, bei denen Sie etwas zurückgeben, das kein gültiges Pydan
Der häufigste Anwendungsfall ist, wenn Sie [eine Response direkt zurückgeben, wie es später im Handbuch für fortgeschrittene Benutzer erläutert wird](../advanced/response-directly.md){.internal-link target=_blank}.
-{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
+{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
Dieser einfache Anwendungsfall wird automatisch von FastAPI gehandhabt, weil die Annotation des Rückgabetyps die Klasse (oder eine Unterklasse von) `Response` ist.
@@ -193,7 +193,7 @@ Und Tools werden auch glücklich sein, weil sowohl `RedirectResponse` als auch `
Sie können auch eine Unterklasse von `Response` in der Typannotation verwenden.
-{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
+{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
Das wird ebenfalls funktionieren, weil `RedirectResponse` eine Unterklasse von `Response` ist, und FastAPI sich um diesen einfachen Anwendungsfall automatisch kümmert.
@@ -252,20 +252,6 @@ Wenn Sie also den Artikel mit der ID `foo` bei der *Pfadoperation* anfragen, wir
/// info | Info
-In Pydantic v1 hieß diese Methode `.dict()`, in Pydantic v2 wurde sie deprecatet (aber immer noch unterstützt) und in `.model_dump()` umbenannt.
-
-Die Beispiele hier verwenden `.dict()` für die Kompatibilität mit Pydantic v1, Sie sollten jedoch stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden können.
-
-///
-
-/// info | Info
-
-FastAPI verwendet `.dict()` von Pydantic Modellen, mit dessen `exclude_unset`-Parameter, um das zu erreichen.
-
-///
-
-/// info | Info
-
Sie können auch:
* `response_model_exclude_defaults=True`
diff --git a/docs/de/docs/tutorial/response-status-code.md b/docs/de/docs/tutorial/response-status-code.md
index 928003c3fc..fd17c99336 100644
--- a/docs/de/docs/tutorial/response-status-code.md
+++ b/docs/de/docs/tutorial/response-status-code.md
@@ -8,7 +8,7 @@ Genauso wie Sie ein Responsemodell angeben können, können Sie auch den HTTP-St
* `@app.delete()`
* usw.
-{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
+{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
/// note | Hinweis
@@ -74,7 +74,7 @@ Um mehr über die einzelnen Statuscodes zu erfahren und welcher wofür verwendet
Lassen Sie uns das vorherige Beispiel noch einmal anschauen:
-{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
+{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
`201` ist der Statuscode für „Created“ („Erzeugt“).
@@ -82,7 +82,7 @@ Aber Sie müssen sich nicht merken, was jeder dieser Codes bedeutet.
Sie können die Annehmlichkeit von Variablen aus `fastapi.status` nutzen.
-{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
+{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *}
Diese sind nur eine Annehmlichkeit, sie enthalten dieselbe Zahl, aber so können Sie die Autovervollständigung Ihres Editors verwenden, um sie zu finden:
diff --git a/docs/de/docs/tutorial/schema-extra-example.md b/docs/de/docs/tutorial/schema-extra-example.md
index e2ffed292e..07fe8c5d92 100644
--- a/docs/de/docs/tutorial/schema-extra-example.md
+++ b/docs/de/docs/tutorial/schema-extra-example.md
@@ -8,36 +8,14 @@ Hier sind mehrere Möglichkeiten, das zu tun.
Sie können `examples` („Beispiele“) für ein Pydantic-Modell deklarieren, welche dem generierten JSON-Schema hinzugefügt werden.
-//// tab | Pydantic v2
-
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
-////
-
-//// tab | Pydantic v1
-
-{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
-
-////
-
Diese zusätzlichen Informationen werden unverändert zum für dieses Modell ausgegebenen **JSON-Schema** hinzugefügt und in der API-Dokumentation verwendet.
-//// tab | Pydantic v2
-
-In Pydantic Version 2 würden Sie das Attribut `model_config` verwenden, das ein `dict` akzeptiert, wie beschrieben in Pydantic-Dokumentation: Configuration.
+Sie können das Attribut `model_config` verwenden, das ein `dict` akzeptiert, wie beschrieben in Pydantic-Dokumentation: Configuration.
Sie können `json_schema_extra` setzen, mit einem `dict`, das alle zusätzlichen Daten enthält, die im generierten JSON-Schema angezeigt werden sollen, einschließlich `examples`.
-////
-
-//// tab | Pydantic v1
-
-In Pydantic Version 1 würden Sie eine interne Klasse `Config` und `schema_extra` verwenden, wie beschrieben in Pydantic-Dokumentation: Schema customization.
-
-Sie können `schema_extra` setzen, mit einem `dict`, das alle zusätzlichen Daten enthält, die im generierten JSON-Schema angezeigt werden sollen, einschließlich `examples`.
-
-////
-
/// tip | Tipp
Mit derselben Technik können Sie das JSON-Schema erweitern und Ihre eigenen benutzerdefinierten Zusatzinformationen hinzufügen.
diff --git a/docs/de/docs/tutorial/sql-databases.md b/docs/de/docs/tutorial/sql-databases.md
index cf9731aee6..3af4ecdfc6 100644
--- a/docs/de/docs/tutorial/sql-databases.md
+++ b/docs/de/docs/tutorial/sql-databases.md
@@ -65,7 +65,7 @@ Es gibt ein paar Unterschiede:
* `Field(primary_key=True)` sagt SQLModel, dass die `id` der **Primärschlüssel** in der SQL-Datenbank ist (Sie können mehr über SQL-Primärschlüssel in der SQLModel-Dokumentation erfahren).
- Durch das Festlegen des Typs als `int | None` wird SQLModel wissen, dass diese Spalte ein `INTEGER` in der SQL-Datenbank sein sollte und dass sie `NULLABLE` sein sollte.
+ **Hinweis:** Wir verwenden für das Primärschlüsselfeld `int | None`, damit wir im Python-Code *ein Objekt ohne `id` erstellen* können (`id=None`), in der Annahme, dass die Datenbank sie *beim Speichern generiert*. SQLModel versteht, dass die Datenbank die `id` bereitstellt, und *definiert die Spalte im Datenbankschema als ein Nicht-Null-`INTEGER`*. Siehe die SQLModel-Dokumentation zu Primärschlüsseln für Details.
* `Field(index=True)` sagt SQLModel, dass es einen **SQL-Index** für diese Spalte erstellen soll, was schnelleres Suchen in der Datenbank ermöglicht, wenn Daten mittels dieser Spalte gefiltert werden.
diff --git a/docs/de/docs/tutorial/static-files.md b/docs/de/docs/tutorial/static-files.md
index 0c4e7c8abd..9ba2501756 100644
--- a/docs/de/docs/tutorial/static-files.md
+++ b/docs/de/docs/tutorial/static-files.md
@@ -7,7 +7,7 @@ Mit `StaticFiles` können Sie statische Dateien aus einem Verzeichnis automatisc
* Importieren Sie `StaticFiles`.
* „Mounten“ Sie eine `StaticFiles()`-Instanz in einem bestimmten Pfad.
-{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
+{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *}
/// note | Technische Details
diff --git a/docs/de/docs/tutorial/testing.md b/docs/de/docs/tutorial/testing.md
index 9c28a2a223..d889b1e1fb 100644
--- a/docs/de/docs/tutorial/testing.md
+++ b/docs/de/docs/tutorial/testing.md
@@ -30,7 +30,7 @@ Verwenden Sie das `TestClient`-Objekt auf die gleiche Weise wie `httpx`.
Schreiben Sie einfache `assert`-Anweisungen mit den Standard-Python-Ausdrücken, die Sie überprüfen müssen (wiederum, Standard-`pytest`).
-{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
+{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *}
/// tip | Tipp
@@ -76,7 +76,7 @@ Nehmen wir an, Sie haben eine Dateistruktur wie in [Größere Anwendungen](bigge
In der Datei `main.py` haben Sie Ihre **FastAPI**-Anwendung:
-{* ../../docs_src/app_testing/main.py *}
+{* ../../docs_src/app_testing/app_a_py39/main.py *}
### Testdatei { #testing-file }
@@ -93,7 +93,7 @@ Dann könnten Sie eine Datei `test_main.py` mit Ihren Tests haben. Sie könnte s
Da sich diese Datei im selben Package befindet, können Sie relative Importe verwenden, um das Objekt `app` aus dem `main`-Modul (`main.py`) zu importieren:
-{* ../../docs_src/app_testing/test_main.py hl[3] *}
+{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *}
... und haben den Code für die Tests wie zuvor.
@@ -122,63 +122,13 @@ Sie verfügt über eine `POST`-Operation, die mehrere Fehler zurückgeben könnt
Beide *Pfadoperationen* erfordern einen `X-Token`-Header.
-//// tab | Python 3.10+
-
-```Python
-{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
-```
-
-////
-
-//// tab | Python 3.9+
-
-```Python
-{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python
-{!> ../../docs_src/app_testing/app_b_an/main.py!}
-```
-
-////
-
-//// tab | Python 3.10+ nicht annotiert
-
-/// tip | Tipp
-
-Bevorzugen Sie die `Annotated`-Version, falls möglich.
-
-///
-
-```Python
-{!> ../../docs_src/app_testing/app_b_py310/main.py!}
-```
-
-////
-
-//// tab | Python 3.8+ nicht annotiert
-
-/// tip | Tipp
-
-Bevorzugen Sie die `Annotated`-Version, falls möglich.
-
-///
-
-```Python
-{!> ../../docs_src/app_testing/app_b/main.py!}
-```
-
-////
+{* ../../docs_src/app_testing/app_b_an_py310/main.py *}
### Erweiterte Testdatei { #extended-testing-file }
Anschließend könnten Sie `test_main.py` mit den erweiterten Tests aktualisieren:
-{* ../../docs_src/app_testing/app_b/test_main.py *}
+{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *}
Wenn Sie möchten, dass der Client Informationen im Request übergibt und Sie nicht wissen, wie das geht, können Sie suchen (googeln), wie es mit `httpx` gemacht wird, oder sogar, wie es mit `requests` gemacht wird, da das Design von HTTPX auf dem Design von Requests basiert.
diff --git a/docs/de/docs/virtual-environments.md b/docs/de/docs/virtual-environments.md
index 497f1b44db..11da496c54 100644
--- a/docs/de/docs/virtual-environments.md
+++ b/docs/de/docs/virtual-environments.md
@@ -242,6 +242,26 @@ $ python -m pip install --upgrade pip
+/// tip | Tipp
+
+Manchmal kann beim Versuch, `pip` zu aktualisieren, der Fehler **`No module named pip`** auftreten.
+
+Wenn das passiert, installieren und aktualisieren Sie `pip` mit dem folgenden Befehl:
+
+
-
-## 🌀 🔢 📨 & 🛃 🕐
-
-👆 💪 💚 ✔️ 🔁 📨 👈 ✔ 📚 *➡ 🛠️*, ✋️ 👆 💚 🌀 👫 ⏮️ 🛃 📨 💚 🔠 *➡ 🛠️*.
-
-📚 💼, 👆 💪 ⚙️ 🐍 ⚒ "🏗" `dict` ⏮️ `**dict_to_unpack`:
-
-```Python
-old_dict = {
- "old key": "old value",
- "second old key": "second old value",
-}
-new_dict = {**old_dict, "new key": "new value"}
-```
-
-📥, `new_dict` 🔜 🔌 🌐 🔑-💲 👫 ⚪️➡️ `old_dict` ➕ 🆕 🔑-💲 👫:
-
-```Python
-{
- "old key": "old value",
- "second old key": "second old value",
- "new key": "new value",
-}
-```
-
-👆 💪 ⚙️ 👈 ⚒ 🏤-⚙️ 🔢 📨 👆 *➡ 🛠️* & 🌀 👫 ⏮️ 🌖 🛃 🕐.
-
-🖼:
-
-{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
-
-## 🌖 ℹ 🔃 🗄 📨
-
-👀 ⚫️❔ ⚫️❔ 👆 💪 🔌 📨, 👆 💪 ✅ 👉 📄 🗄 🔧:
-
-* 🗄 📨 🎚, ⚫️ 🔌 `Response Object`.
-* 🗄 📨 🎚, 👆 💪 🔌 🕳 ⚪️➡️ 👉 🔗 🔠 📨 🔘 👆 `responses` 🔢. ✅ `description`, `headers`, `content` (🔘 👉 👈 👆 📣 🎏 🔉 🆎 & 🎻 🔗), & `links`.
diff --git a/docs/em/docs/advanced/additional-status-codes.md b/docs/em/docs/advanced/additional-status-codes.md
deleted file mode 100644
index 907c7e68ee..0000000000
--- a/docs/em/docs/advanced/additional-status-codes.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# 🌖 👔 📟
-
-🔢, **FastAPI** 🔜 📨 📨 ⚙️ `JSONResponse`, 🚮 🎚 👆 📨 ⚪️➡️ 👆 *➡ 🛠️* 🔘 👈 `JSONResponse`.
-
-⚫️ 🔜 ⚙️ 🔢 👔 📟 ⚖️ 1️⃣ 👆 ⚒ 👆 *➡ 🛠️*.
-
-## 🌖 👔 📟
-
-🚥 👆 💚 📨 🌖 👔 📟 ↖️ ⚪️➡️ 👑 1️⃣, 👆 💪 👈 🛬 `Response` 🔗, 💖 `JSONResponse`, & ⚒ 🌖 👔 📟 🔗.
-
-🖼, ➡️ 💬 👈 👆 💚 ✔️ *➡ 🛠️* 👈 ✔ ℹ 🏬, & 📨 🇺🇸🔍 👔 📟 2️⃣0️⃣0️⃣ "👌" 🕐❔ 🏆.
-
-✋️ 👆 💚 ⚫️ 🚫 🆕 🏬. & 🕐❔ 🏬 🚫 🔀 ⏭, ⚫️ ✍ 👫, & 📨 🇺🇸🔍 👔 📟 2️⃣0️⃣1️⃣ "✍".
-
-🏆 👈, 🗄 `JSONResponse`, & 📨 👆 🎚 📤 🔗, ⚒ `status_code` 👈 👆 💚:
-
-{* ../../docs_src/additional_status_codes/tutorial001.py hl[4,25] *}
-
-/// warning
-
-🕐❔ 👆 📨 `Response` 🔗, 💖 🖼 🔛, ⚫️ 🔜 📨 🔗.
-
-⚫️ 🏆 🚫 🎻 ⏮️ 🏷, ♒️.
-
-⚒ 💭 ⚫️ ✔️ 📊 👆 💚 ⚫️ ✔️, & 👈 💲 ☑ 🎻 (🚥 👆 ⚙️ `JSONResponse`).
-
-///
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.responses import JSONResponse`.
-
-**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. 🎏 ⏮️ `status`.
-
-///
-
-## 🗄 & 🛠️ 🩺
-
-🚥 👆 📨 🌖 👔 📟 & 📨 🔗, 👫 🏆 🚫 🔌 🗄 🔗 (🛠️ 🩺), ↩️ FastAPI 🚫 ✔️ 🌌 💭 ⏪ ⚫️❔ 👆 🚶 📨.
-
-✋️ 👆 💪 📄 👈 👆 📟, ⚙️: [🌖 📨](additional-responses.md){.internal-link target=_blank}.
diff --git a/docs/em/docs/advanced/advanced-dependencies.md b/docs/em/docs/advanced/advanced-dependencies.md
deleted file mode 100644
index 3404c26872..0000000000
--- a/docs/em/docs/advanced/advanced-dependencies.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# 🏧 🔗
-
-## 🔗 🔗
-
-🌐 🔗 👥 ✔️ 👀 🔧 🔢 ⚖️ 🎓.
-
-✋️ 📤 💪 💼 🌐❔ 👆 💚 💪 ⚒ 🔢 🔛 🔗, 🍵 ✔️ 📣 📚 🎏 🔢 ⚖️ 🎓.
-
-➡️ 🌈 👈 👥 💚 ✔️ 🔗 👈 ✅ 🚥 🔢 🔢 `q` 🔌 🔧 🎚.
-
-✋️ 👥 💚 💪 🔗 👈 🔧 🎚.
-
-## "🇧🇲" 👐
-
-🐍 📤 🌌 ⚒ 👐 🎓 "🇧🇲".
-
-🚫 🎓 ⚫️ (❔ ⏪ 🇧🇲), ✋️ 👐 👈 🎓.
-
-👈, 👥 📣 👩🔬 `__call__`:
-
-{* ../../docs_src/dependencies/tutorial011.py hl[10] *}
-
-👉 💼, 👉 `__call__` ⚫️❔ **FastAPI** 🔜 ⚙️ ✅ 🌖 🔢 & 🎧-🔗, & 👉 ⚫️❔ 🔜 🤙 🚶♀️ 💲 🔢 👆 *➡ 🛠️ 🔢* ⏪.
-
-## 🔗 👐
-
-& 🔜, 👥 💪 ⚙️ `__init__` 📣 🔢 👐 👈 👥 💪 ⚙️ "🔗" 🔗:
-
-{* ../../docs_src/dependencies/tutorial011.py hl[7] *}
-
-👉 💼, **FastAPI** 🏆 🚫 ⏱ 👆 ⚖️ 💅 🔃 `__init__`, 👥 🔜 ⚙️ ⚫️ 🔗 👆 📟.
-
-## ✍ 👐
-
-👥 💪 ✍ 👐 👉 🎓 ⏮️:
-
-{* ../../docs_src/dependencies/tutorial011.py hl[16] *}
-
-& 👈 🌌 👥 💪 "🔗" 👆 🔗, 👈 🔜 ✔️ `"bar"` 🔘 ⚫️, 🔢 `checker.fixed_content`.
-
-## ⚙️ 👐 🔗
-
-⤴️, 👥 💪 ⚙️ 👉 `checker` `Depends(checker)`, ↩️ `Depends(FixedContentQueryChecker)`, ↩️ 🔗 👐, `checker`, 🚫 🎓 ⚫️.
-
-& 🕐❔ ❎ 🔗, **FastAPI** 🔜 🤙 👉 `checker` 💖:
-
-```Python
-checker(q="somequery")
-```
-
-...& 🚶♀️ ⚫️❔ 👈 📨 💲 🔗 👆 *➡ 🛠️ 🔢* 🔢 `fixed_content_included`:
-
-{* ../../docs_src/dependencies/tutorial011.py hl[20] *}
-
-/// tip
-
-🌐 👉 💪 😑 🎭. & ⚫️ 💪 🚫 📶 🆑 ❔ ⚫️ ⚠.
-
-👫 🖼 😫 🙅, ✋️ 🎦 ❔ ⚫️ 🌐 👷.
-
-📃 🔃 💂♂, 📤 🚙 🔢 👈 🛠️ 👉 🎏 🌌.
-
-🚥 👆 🤔 🌐 👉, 👆 ⏪ 💭 ❔ 👈 🚙 🧰 💂♂ 👷 🔘.
-
-///
diff --git a/docs/em/docs/advanced/async-tests.md b/docs/em/docs/advanced/async-tests.md
deleted file mode 100644
index 283d4aa097..0000000000
--- a/docs/em/docs/advanced/async-tests.md
+++ /dev/null
@@ -1,93 +0,0 @@
-# 🔁 💯
-
-👆 ✔️ ⏪ 👀 ❔ 💯 👆 **FastAPI** 🈸 ⚙️ 🚚 `TestClient`. 🆙 🔜, 👆 ✔️ 🕴 👀 ❔ ✍ 🔁 💯, 🍵 ⚙️ `async` 🔢.
-
-➖ 💪 ⚙️ 🔁 🔢 👆 💯 💪 ⚠, 🖼, 🕐❔ 👆 🔬 👆 💽 🔁. 🌈 👆 💚 💯 📨 📨 👆 FastAPI 🈸 & ⤴️ ✔ 👈 👆 👩💻 ⏪ ✍ ☑ 💽 💽, ⏪ ⚙️ 🔁 💽 🗃.
-
-➡️ 👀 ❔ 👥 💪 ⚒ 👈 👷.
-
-## pytest.mark.anyio
-
-🚥 👥 💚 🤙 🔁 🔢 👆 💯, 👆 💯 🔢 ✔️ 🔁. AnyIO 🚚 👌 📁 👉, 👈 ✔ 👥 ✔ 👈 💯 🔢 🤙 🔁.
-
-## 🇸🇲
-
-🚥 👆 **FastAPI** 🈸 ⚙️ 😐 `def` 🔢 ↩️ `async def`, ⚫️ `async` 🈸 🔘.
-
-`TestClient` 🔨 🎱 🔘 🤙 🔁 FastAPI 🈸 👆 😐 `def` 💯 🔢, ⚙️ 🐩 ✳. ✋️ 👈 🎱 🚫 👷 🚫🔜 🕐❔ 👥 ⚙️ ⚫️ 🔘 🔁 🔢. 🏃 👆 💯 🔁, 👥 💪 🙅♂ 📏 ⚙️ `TestClient` 🔘 👆 💯 🔢.
-
-`TestClient` ⚓️ 🔛 🇸🇲, & ↩️, 👥 💪 ⚙️ ⚫️ 🔗 💯 🛠️.
-
-## 🖼
-
-🙅 🖼, ➡️ 🤔 📁 📊 🎏 1️⃣ 🔬 [🦏 🈸](../tutorial/bigger-applications.md){.internal-link target=_blank} & [🔬](../tutorial/testing.md){.internal-link target=_blank}:
-
-```
-.
-├── app
-│ ├── __init__.py
-│ ├── main.py
-│ └── test_main.py
-```
-
-📁 `main.py` 🔜 ✔️:
-
-{* ../../docs_src/async_tests/main.py *}
-
-📁 `test_main.py` 🔜 ✔️ 💯 `main.py`, ⚫️ 💪 👀 💖 👉 🔜:
-
-{* ../../docs_src/async_tests/test_main.py *}
-
-## 🏃 ⚫️
-
-👆 💪 🏃 👆 💯 🐌 📨:
-
-
-
-✋️ 🚥 👥 🔐 🩺 🎚 "🛂" 📛 ⚙️ 🗳 ⏮️ ⛴ `9999`, `/api/v1/docs`, ⚫️ 👷 ☑ ❗ 👶
-
-👆 💪 ✅ ⚫️ http://127.0.0.1:9999/api/v1/docs:
-
-
-
-▶️️ 👥 💚 ⚫️. 👶 👶
-
-👉 ↩️ FastAPI ⚙️ 👉 `root_path` ✍ 🔢 `server` 🗄 ⏮️ 📛 🚚 `root_path`.
-
-## 🌖 💽
-
-/// warning
-
-👉 🌅 🏧 ⚙️ 💼. 💭 🆓 🚶 ⚫️.
-
-///
-
-🔢, **FastAPI** 🔜 ✍ `server` 🗄 🔗 ⏮️ 📛 `root_path`.
-
-✋️ 👆 💪 🚚 🎏 🎛 `servers`, 🖼 🚥 👆 💚 *🎏* 🩺 🎚 🔗 ⏮️ 🏗 & 🏭 🌐.
-
-🚥 👆 🚶♀️ 🛃 📇 `servers` & 📤 `root_path` (↩️ 👆 🛠️ 👨❤👨 ⛅ 🗳), **FastAPI** 🔜 📩 "💽" ⏮️ 👉 `root_path` ▶️ 📇.
-
-🖼:
-
-{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *}
-
-🔜 🏗 🗄 🔗 💖:
-
-```JSON hl_lines="5-7"
-{
- "openapi": "3.0.2",
- // More stuff here
- "servers": [
- {
- "url": "/api/v1"
- },
- {
- "url": "https://stag.example.com",
- "description": "Staging environment"
- },
- {
- "url": "https://prod.example.com",
- "description": "Production environment"
- }
- ],
- "paths": {
- // More stuff here
- }
-}
-```
-
-/// tip
-
-👀 🚘-🏗 💽 ⏮️ `url` 💲 `/api/v1`, ✊ ⚪️➡️ `root_path`.
-
-///
-
-🩺 🎚 http://127.0.0.1:9999/api/v1/docs ⚫️ 🔜 👀 💖:
-
-
-
-/// tip
-
-🩺 🎚 🔜 🔗 ⏮️ 💽 👈 👆 🖊.
-
-///
-
-### ❎ 🏧 💽 ⚪️➡️ `root_path`
-
-🚥 👆 🚫 💚 **FastAPI** 🔌 🏧 💽 ⚙️ `root_path`, 👆 💪 ⚙️ 🔢 `root_path_in_servers=False`:
-
-{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *}
-
-& ⤴️ ⚫️ 🏆 🚫 🔌 ⚫️ 🗄 🔗.
-
-## 🗜 🎧-🈸
-
-🚥 👆 💪 🗻 🎧-🈸 (🔬 [🎧 🈸 - 🗻](sub-applications.md){.internal-link target=_blank}) ⏪ ⚙️ 🗳 ⏮️ `root_path`, 👆 💪 ⚫️ 🛎, 👆 🔜 ⌛.
-
-FastAPI 🔜 🔘 ⚙️ `root_path` 🎆, ⚫️ 🔜 👷. 👶
diff --git a/docs/em/docs/advanced/custom-response.md b/docs/em/docs/advanced/custom-response.md
deleted file mode 100644
index ab95b3e7be..0000000000
--- a/docs/em/docs/advanced/custom-response.md
+++ /dev/null
@@ -1,303 +0,0 @@
-# 🛃 📨 - 🕸, 🎏, 📁, 🎏
-
-🔢, **FastAPI** 🔜 📨 📨 ⚙️ `JSONResponse`.
-
-👆 💪 🔐 ⚫️ 🛬 `Response` 🔗 👀 [📨 📨 🔗](response-directly.md){.internal-link target=_blank}.
-
-✋️ 🚥 👆 📨 `Response` 🔗, 📊 🏆 🚫 🔁 🗜, & 🧾 🏆 🚫 🔁 🏗 (🖼, 🔌 🎯 "📻 🆎", 🇺🇸🔍 🎚 `Content-Type` 🍕 🏗 🗄).
-
-✋️ 👆 💪 📣 `Response` 👈 👆 💚 ⚙️, *➡ 🛠️ 👨🎨*.
-
-🎚 👈 👆 📨 ⚪️➡️ 👆 *➡ 🛠️ 🔢* 🔜 🚮 🔘 👈 `Response`.
-
-& 🚥 👈 `Response` ✔️ 🎻 📻 🆎 (`application/json`), 💖 💼 ⏮️ `JSONResponse` & `UJSONResponse`, 💽 👆 📨 🔜 🔁 🗜 (& ⛽) ⏮️ 🙆 Pydantic `response_model` 👈 👆 📣 *➡ 🛠️ 👨🎨*.
-
-/// note
-
-🚥 👆 ⚙️ 📨 🎓 ⏮️ 🙅♂ 📻 🆎, FastAPI 🔜 ⌛ 👆 📨 ✔️ 🙅♂ 🎚, ⚫️ 🔜 🚫 📄 📨 📁 🚮 🏗 🗄 🩺.
-
-///
-
-## ⚙️ `ORJSONResponse`
-
-🖼, 🚥 👆 ✊ 🎭, 👆 💪 ❎ & ⚙️ `orjson` & ⚒ 📨 `ORJSONResponse`.
-
-🗄 `Response` 🎓 (🎧-🎓) 👆 💚 ⚙️ & 📣 ⚫️ *➡ 🛠️ 👨🎨*.
-
-⭕ 📨, 📨 `Response` 🔗 🌅 ⏩ 🌘 🛬 📖.
-
-👉 ↩️ 🔢, FastAPI 🔜 ✔ 🔠 🏬 🔘 & ⚒ 💭 ⚫️ 🎻 ⏮️ 🎻, ⚙️ 🎏 [🎻 🔗 🔢](../tutorial/encoder.md){.internal-link target=_blank} 🔬 🔰. 👉 ⚫️❔ ✔ 👆 📨 **❌ 🎚**, 🖼 💽 🏷.
-
-✋️ 🚥 👆 🎯 👈 🎚 👈 👆 🛬 **🎻 ⏮️ 🎻**, 👆 💪 🚶♀️ ⚫️ 🔗 📨 🎓 & ❎ ➕ 🌥 👈 FastAPI 🔜 ✔️ 🚶♀️ 👆 📨 🎚 🔘 `jsonable_encoder` ⏭ 🚶♀️ ⚫️ 📨 🎓.
-
-{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *}
-
-/// info
-
-🔢 `response_class` 🔜 ⚙️ 🔬 "📻 🆎" 📨.
-
-👉 💼, 🇺🇸🔍 🎚 `Content-Type` 🔜 ⚒ `application/json`.
-
- & ⚫️ 🔜 📄 ✅ 🗄.
-
-///
-
-/// tip
-
-`ORJSONResponse` ⏳ 🕴 💪 FastAPI, 🚫 💃.
-
-///
-
-## 🕸 📨
-
-📨 📨 ⏮️ 🕸 🔗 ⚪️➡️ **FastAPI**, ⚙️ `HTMLResponse`.
-
-* 🗄 `HTMLResponse`.
-* 🚶♀️ `HTMLResponse` 🔢 `response_class` 👆 *➡ 🛠️ 👨🎨*.
-
-{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *}
-
-/// info
-
-🔢 `response_class` 🔜 ⚙️ 🔬 "📻 🆎" 📨.
-
-👉 💼, 🇺🇸🔍 🎚 `Content-Type` 🔜 ⚒ `text/html`.
-
- & ⚫️ 🔜 📄 ✅ 🗄.
-
-///
-
-### 📨 `Response`
-
-👀 [📨 📨 🔗](response-directly.md){.internal-link target=_blank}, 👆 💪 🔐 📨 🔗 👆 *➡ 🛠️*, 🛬 ⚫️.
-
-🎏 🖼 ⚪️➡️ 🔛, 🛬 `HTMLResponse`, 💪 👀 💖:
-
-{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *}
-
-/// warning
-
-`Response` 📨 🔗 👆 *➡ 🛠️ 🔢* 🏆 🚫 📄 🗄 (🖼, `Content-Type` 🏆 🚫 📄) & 🏆 🚫 ⭐ 🏧 🎓 🩺.
-
-///
-
-/// info
-
-↗️, ☑ `Content-Type` 🎚, 👔 📟, ♒️, 🔜 👟 ⚪️➡️ `Response` 🎚 👆 📨.
-
-///
-
-### 📄 🗄 & 🔐 `Response`
-
-🚥 👆 💚 🔐 📨 ⚪️➡️ 🔘 🔢 ✋️ 🎏 🕰 📄 "📻 🆎" 🗄, 👆 💪 ⚙️ `response_class` 🔢 & 📨 `Response` 🎚.
-
-`response_class` 🔜 ⤴️ ⚙️ 🕴 📄 🗄 *➡ 🛠️*, ✋️ 👆 `Response` 🔜 ⚙️.
-
-#### 📨 `HTMLResponse` 🔗
-
-🖼, ⚫️ 💪 🕳 💖:
-
-{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *}
-
-👉 🖼, 🔢 `generate_html_response()` ⏪ 🏗 & 📨 `Response` ↩️ 🛬 🕸 `str`.
-
-🛬 🏁 🤙 `generate_html_response()`, 👆 ⏪ 🛬 `Response` 👈 🔜 🔐 🔢 **FastAPI** 🎭.
-
-✋️ 👆 🚶♀️ `HTMLResponse` `response_class` 💁♂️, **FastAPI** 🔜 💭 ❔ 📄 ⚫️ 🗄 & 🎓 🩺 🕸 ⏮️ `text/html`:
-
-
-
-## 💪 📨
-
-📥 💪 📨.
-
-✔️ 🤯 👈 👆 💪 ⚙️ `Response` 📨 🕳 🙆, ⚖️ ✍ 🛃 🎧-🎓.
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.responses import HTMLResponse`.
-
-**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃.
-
-///
-
-### `Response`
-
-👑 `Response` 🎓, 🌐 🎏 📨 😖 ⚪️➡️ ⚫️.
-
-👆 💪 📨 ⚫️ 🔗.
-
-⚫️ 🚫 📄 🔢:
-
-* `content` - `str` ⚖️ `bytes`.
-* `status_code` - `int` 🇺🇸🔍 👔 📟.
-* `headers` - `dict` 🎻.
-* `media_type` - `str` 🤝 📻 🆎. 🤶 Ⓜ. `"text/html"`.
-
-FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 🎚, ⚓️ 🔛 = & 🔁 = ✍ 🆎.
-
-{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
-
-### `HTMLResponse`
-
-✊ ✍ ⚖️ 🔢 & 📨 🕸 📨, 👆 ✍ 🔛.
-
-### `PlainTextResponse`
-
-✊ ✍ ⚖️ 🔢 & 📨 ✅ ✍ 📨.
-
-{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
-
-### `JSONResponse`
-
-✊ 💽 & 📨 `application/json` 🗜 📨.
-
-👉 🔢 📨 ⚙️ **FastAPI**, 👆 ✍ 🔛.
-
-### `ORJSONResponse`
-
-⏩ 🎛 🎻 📨 ⚙️ `orjson`, 👆 ✍ 🔛.
-
-### `UJSONResponse`
-
-🎛 🎻 📨 ⚙️ `ujson`.
-
-/// warning
-
-`ujson` 🌘 💛 🌘 🐍 🏗-🛠️ ❔ ⚫️ 🍵 📐-💼.
-
-///
-
-{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
-
-/// tip
-
-⚫️ 💪 👈 `ORJSONResponse` 💪 ⏩ 🎛.
-
-///
-
-### `RedirectResponse`
-
-📨 🇺🇸🔍 ❎. ⚙️ 3️⃣0️⃣7️⃣ 👔 📟 (🍕 ❎) 🔢.
-
-👆 💪 📨 `RedirectResponse` 🔗:
-
-{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
-
----
-
-⚖️ 👆 💪 ⚙️ ⚫️ `response_class` 🔢:
-
-
-{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *}
-
-🚥 👆 👈, ⤴️ 👆 💪 📨 📛 🔗 ⚪️➡️ 👆 *➡ 🛠️* 🔢.
-
-👉 💼, `status_code` ⚙️ 🔜 🔢 1️⃣ `RedirectResponse`, ❔ `307`.
-
----
-
-👆 💪 ⚙️ `status_code` 🔢 🌀 ⏮️ `response_class` 🔢:
-
-{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *}
-
-### `StreamingResponse`
-
-✊ 🔁 🚂 ⚖️ 😐 🚂/🎻 & 🎏 📨 💪.
-
-{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
-
-#### ⚙️ `StreamingResponse` ⏮️ 📁-💖 🎚
-
-🚥 👆 ✔️ 📁-💖 🎚 (✅ 🎚 📨 `open()`), 👆 💪 ✍ 🚂 🔢 🔁 🤭 👈 📁-💖 🎚.
-
-👈 🌌, 👆 🚫 ✔️ ✍ ⚫️ 🌐 🥇 💾, & 👆 💪 🚶♀️ 👈 🚂 🔢 `StreamingResponse`, & 📨 ⚫️.
-
-👉 🔌 📚 🗃 🔗 ⏮️ ☁ 💾, 📹 🏭, & 🎏.
-
-```{ .python .annotate hl_lines="2 10-12 14" }
-{!../../docs_src/custom_response/tutorial008.py!}
-```
-
-1️⃣. 👉 🚂 🔢. ⚫️ "🚂 🔢" ↩️ ⚫️ 🔌 `yield` 📄 🔘.
-2️⃣. ⚙️ `with` 🍫, 👥 ⚒ 💭 👈 📁-💖 🎚 📪 ⏮️ 🚂 🔢 🔨. , ⏮️ ⚫️ 🏁 📨 📨.
-3️⃣. 👉 `yield from` 💬 🔢 🔁 🤭 👈 👜 🌟 `file_like`. & ⤴️, 🔠 🍕 🔁, 🌾 👈 🍕 👟 ⚪️➡️ 👉 🚂 🔢.
-
- , ⚫️ 🚂 🔢 👈 📨 "🏭" 👷 🕳 🙆 🔘.
-
- 🔨 ⚫️ 👉 🌌, 👥 💪 🚮 ⚫️ `with` 🍫, & 👈 🌌, 🚚 👈 ⚫️ 📪 ⏮️ 🏁.
-
-/// tip
-
-👀 👈 📥 👥 ⚙️ 🐩 `open()` 👈 🚫 🐕🦺 `async` & `await`, 👥 📣 ➡ 🛠️ ⏮️ 😐 `def`.
-
-///
-
-### `FileResponse`
-
-🔁 🎏 📁 📨.
-
-✊ 🎏 ⚒ ❌ 🔗 🌘 🎏 📨 🆎:
-
-* `path` - 📁 📁 🎏.
-* `headers` - 🙆 🛃 🎚 🔌, 📖.
-* `media_type` - 🎻 🤝 📻 🆎. 🚥 🔢, 📁 ⚖️ ➡ 🔜 ⚙️ 🔑 📻 🆎.
-* `filename` - 🚥 ⚒, 👉 🔜 🔌 📨 `Content-Disposition`.
-
-📁 📨 🔜 🔌 ☑ `Content-Length`, `Last-Modified` & `ETag` 🎚.
-
-{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
-
-👆 💪 ⚙️ `response_class` 🔢:
-
-{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *}
-
-👉 💼, 👆 💪 📨 📁 ➡ 🔗 ⚪️➡️ 👆 *➡ 🛠️* 🔢.
-
-## 🛃 📨 🎓
-
-👆 💪 ✍ 👆 👍 🛃 📨 🎓, 😖 ⚪️➡️ `Response` & ⚙️ ⚫️.
-
-🖼, ➡️ 💬 👈 👆 💚 ⚙️ `orjson`, ✋️ ⏮️ 🛃 ⚒ 🚫 ⚙️ 🔌 `ORJSONResponse` 🎓.
-
-➡️ 💬 👆 💚 ⚫️ 📨 🔂 & 📁 🎻, 👆 💚 ⚙️ Orjson 🎛 `orjson.OPT_INDENT_2`.
-
-👆 💪 ✍ `CustomORJSONResponse`. 👑 👜 👆 ✔️ ✍ `Response.render(content)` 👩🔬 👈 📨 🎚 `bytes`:
-
-{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *}
-
-🔜 ↩️ 🛬:
-
-```json
-{"message": "Hello World"}
-```
-
-...👉 📨 🔜 📨:
-
-```json
-{
- "message": "Hello World"
-}
-```
-
-↗️, 👆 🔜 🎲 🔎 🌅 👍 🌌 ✊ 📈 👉 🌘 ❕ 🎻. 👶
-
-## 🔢 📨 🎓
-
-🕐❔ 🏗 **FastAPI** 🎓 👐 ⚖️ `APIRouter` 👆 💪 ✔ ❔ 📨 🎓 ⚙️ 🔢.
-
-🔢 👈 🔬 👉 `default_response_class`.
-
-🖼 🔛, **FastAPI** 🔜 ⚙️ `ORJSONResponse` 🔢, 🌐 *➡ 🛠️*, ↩️ `JSONResponse`.
-
-{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *}
-
-/// tip
-
-👆 💪 🔐 `response_class` *➡ 🛠️* ⏭.
-
-///
-
-## 🌖 🧾
-
-👆 💪 📣 📻 🆎 & 📚 🎏 ℹ 🗄 ⚙️ `responses`: [🌖 📨 🗄](additional-responses.md){.internal-link target=_blank}.
diff --git a/docs/em/docs/advanced/dataclasses.md b/docs/em/docs/advanced/dataclasses.md
deleted file mode 100644
index 4dd597262a..0000000000
--- a/docs/em/docs/advanced/dataclasses.md
+++ /dev/null
@@ -1,97 +0,0 @@
-# ⚙️ 🎻
-
-FastAPI 🏗 🔛 🔝 **Pydantic**, & 👤 ✔️ 🌏 👆 ❔ ⚙️ Pydantic 🏷 📣 📨 & 📨.
-
-✋️ FastAPI 🐕🦺 ⚙️ `dataclasses` 🎏 🌌:
-
-{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *}
-
-👉 🐕🦺 👏 **Pydantic**, ⚫️ ✔️ 🔗 🐕🦺 `dataclasses`.
-
-, ⏮️ 📟 🔛 👈 🚫 ⚙️ Pydantic 🎯, FastAPI ⚙️ Pydantic 🗜 📚 🐩 🎻 Pydantic 👍 🍛 🎻.
-
-& ↗️, ⚫️ 🐕🦺 🎏:
-
-* 💽 🔬
-* 💽 🛠️
-* 💽 🧾, ♒️.
-
-👉 👷 🎏 🌌 ⏮️ Pydantic 🏷. & ⚫️ 🤙 🏆 🎏 🌌 🔘, ⚙️ Pydantic.
-
-/// info
-
-✔️ 🤯 👈 🎻 💪 🚫 🌐 Pydantic 🏷 💪.
-
-, 👆 5️⃣📆 💪 ⚙️ Pydantic 🏷.
-
-✋️ 🚥 👆 ✔️ 📚 🎻 🤥 🤭, 👉 👌 🎱 ⚙️ 👫 🏋️ 🕸 🛠️ ⚙️ FastAPI. 👶
-
-///
-
-## 🎻 `response_model`
-
-👆 💪 ⚙️ `dataclasses` `response_model` 🔢:
-
-{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
-
-🎻 🔜 🔁 🗜 Pydantic 🎻.
-
-👉 🌌, 🚮 🔗 🔜 🎦 🆙 🛠️ 🩺 👩💻 🔢:
-
-
-
-## 🎻 🔁 📊 📊
-
-👆 💪 🌀 `dataclasses` ⏮️ 🎏 🆎 ✍ ⚒ 🐦 📊 📊.
-
-💼, 👆 💪 ✔️ ⚙️ Pydantic ⏬ `dataclasses`. 🖼, 🚥 👆 ✔️ ❌ ⏮️ 🔁 🏗 🛠️ 🧾.
-
-👈 💼, 👆 💪 🎯 💱 🐩 `dataclasses` ⏮️ `pydantic.dataclasses`, ❔ 💧-♻:
-
-```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" }
-{!../../docs_src/dataclasses/tutorial003.py!}
-```
-
-1️⃣. 👥 🗄 `field` ⚪️➡️ 🐩 `dataclasses`.
-
-2️⃣. `pydantic.dataclasses` 💧-♻ `dataclasses`.
-
-3️⃣. `Author` 🎻 🔌 📇 `Item` 🎻.
-
-4️⃣. `Author` 🎻 ⚙️ `response_model` 🔢.
-
-5️⃣. 👆 💪 ⚙️ 🎏 🐩 🆎 ✍ ⏮️ 🎻 📨 💪.
-
- 👉 💼, ⚫️ 📇 `Item` 🎻.
-
-6️⃣. 📥 👥 🛬 📖 👈 🔌 `items` ❔ 📇 🎻.
-
- FastAPI 🎯 ✍ 💽 🎻.
-
-7️⃣. 📥 `response_model` ⚙️ 🆎 ✍ 📇 `Author` 🎻.
-
- 🔄, 👆 💪 🌀 `dataclasses` ⏮️ 🐩 🆎 ✍.
-
-8️⃣. 👀 👈 👉 *➡ 🛠️ 🔢* ⚙️ 🥔 `def` ↩️ `async def`.
-
- 🕧, FastAPI 👆 💪 🌀 `def` & `async def` 💪.
-
- 🚥 👆 💪 ↗️ 🔃 🕐❔ ⚙️ ❔, ✅ 👅 📄 _"🏃 ❓" _ 🩺 🔃 `async` & `await`.
-
-9️⃣. 👉 *➡ 🛠️ 🔢* 🚫 🛬 🎻 (👐 ⚫️ 💪), ✋️ 📇 📖 ⏮️ 🔗 💽.
-
- FastAPI 🔜 ⚙️ `response_model` 🔢 (👈 🔌 🎻) 🗜 📨.
-
-👆 💪 🌀 `dataclasses` ⏮️ 🎏 🆎 ✍ 📚 🎏 🌀 📨 🏗 📊 📊.
-
-✅-📟 ✍ 💁♂ 🔛 👀 🌅 🎯 ℹ.
-
-## 💡 🌅
-
-👆 💪 🌀 `dataclasses` ⏮️ 🎏 Pydantic 🏷, 😖 ⚪️➡️ 👫, 🔌 👫 👆 👍 🏷, ♒️.
-
-💡 🌅, ✅ Pydantic 🩺 🔃 🎻.
-
-## ⏬
-
-👉 💪 ↩️ FastAPI ⏬ `0.67.0`. 👶
diff --git a/docs/em/docs/advanced/events.md b/docs/em/docs/advanced/events.md
deleted file mode 100644
index dcaac710e1..0000000000
--- a/docs/em/docs/advanced/events.md
+++ /dev/null
@@ -1,163 +0,0 @@
-# 🔆 🎉
-
-👆 💪 🔬 ⚛ (📟) 👈 🔜 🛠️ ⏭ 🈸 **▶️ 🆙**. 👉 ⛓ 👈 👉 📟 🔜 🛠️ **🕐**, **⏭** 🈸 **▶️ 📨 📨**.
-
-🎏 🌌, 👆 💪 🔬 ⚛ (📟) 👈 🔜 🛠️ 🕐❔ 🈸 **🤫 🔽**. 👉 💼, 👉 📟 🔜 🛠️ **🕐**, **⏮️** ✔️ 🍵 🎲 **📚 📨**.
-
-↩️ 👉 📟 🛠️ ⏭ 🈸 **▶️** ✊ 📨, & ▶️️ ⏮️ ⚫️ **🏁** 🚚 📨, ⚫️ 📔 🎂 🈸 **🔆** (🔤 "🔆" 🔜 ⚠ 🥈 👶).
-
-👉 💪 📶 ⚠ ⚒ 🆙 **ℹ** 👈 👆 💪 ⚙️ 🎂 📱, & 👈 **💰** 👪 📨, &/⚖️ 👈 👆 💪 **🧹 🆙** ⏮️. 🖼, 💽 🔗 🎱, ⚖️ 🚚 🔗 🎰 🏫 🏷.
-
-## ⚙️ 💼
-
-➡️ ▶️ ⏮️ 🖼 **⚙️ 💼** & ⤴️ 👀 ❔ ❎ ⚫️ ⏮️ 👉.
-
-➡️ 🌈 👈 👆 ✔️ **🎰 🏫 🏷** 👈 👆 💚 ⚙️ 🍵 📨. 👶
-
-🎏 🏷 🔗 👪 📨,, ⚫️ 🚫 1️⃣ 🏷 📍 📨, ⚖️ 1️⃣ 📍 👩💻 ⚖️ 🕳 🎏.
-
-➡️ 🌈 👈 🚚 🏷 💪 **✊ 🕰**, ↩️ ⚫️ ✔️ ✍ 📚 **💽 ⚪️➡️ 💾**. 👆 🚫 💚 ⚫️ 🔠 📨.
-
-👆 💪 📐 ⚫️ 🔝 🎚 🕹/📁, ✋️ 👈 🔜 ⛓ 👈 ⚫️ 🔜 **📐 🏷** 🚥 👆 🏃♂ 🙅 🏧 💯, ⤴️ 👈 💯 🔜 **🐌** ↩️ ⚫️ 🔜 ✔️ ⌛ 🏷 📐 ⏭ 💆♂ 💪 🏃 🔬 🍕 📟.
-
-👈 ⚫️❔ 👥 🔜 ❎, ➡️ 📐 🏷 ⏭ 📨 🍵, ✋️ 🕴 ▶️️ ⏭ 🈸 ▶️ 📨 📨, 🚫 ⏪ 📟 ➖ 📐.
-
-## 🔆
-
-👆 💪 🔬 👉 *🕴* & *🤫* ⚛ ⚙️ `lifespan` 🔢 `FastAPI` 📱, & "🔑 👨💼" (👤 🔜 🎦 👆 ⚫️❔ 👈 🥈).
-
-➡️ ▶️ ⏮️ 🖼 & ⤴️ 👀 ⚫️ ℹ.
-
-👥 ✍ 🔁 🔢 `lifespan()` ⏮️ `yield` 💖 👉:
-
-{* ../../docs_src/events/tutorial003.py hl[16,19] *}
-
-📥 👥 ⚖ 😥 *🕴* 🛠️ 🚚 🏷 🚮 (❌) 🏷 🔢 📖 ⏮️ 🎰 🏫 🏷 ⏭ `yield`. 👉 📟 🔜 🛠️ **⏭** 🈸 **▶️ ✊ 📨**, ⏮️ *🕴*.
-
-& ⤴️, ▶️️ ⏮️ `yield`, 👥 🚚 🏷. 👉 📟 🔜 🛠️ **⏮️** 🈸 **🏁 🚚 📨**, ▶️️ ⏭ *🤫*. 👉 💪, 🖼, 🚀 ℹ 💖 💾 ⚖️ 💻.
-
-/// tip
-
-`shutdown` 🔜 🔨 🕐❔ 👆 **⛔️** 🈸.
-
-🎲 👆 💪 ▶️ 🆕 ⏬, ⚖️ 👆 🤚 🎡 🏃 ⚫️. 🤷
-
-///
-
-### 🔆 🔢
-
-🥇 👜 👀, 👈 👥 ⚖ 🔁 🔢 ⏮️ `yield`. 👉 📶 🎏 🔗 ⏮️ `yield`.
-
-{* ../../docs_src/events/tutorial003.py hl[14:19] *}
-
-🥇 🍕 🔢, ⏭ `yield`, 🔜 🛠️ **⏭** 🈸 ▶️.
-
-& 🍕 ⏮️ `yield` 🔜 🛠️ **⏮️** 🈸 ✔️ 🏁.
-
-### 🔁 🔑 👨💼
-
-🚥 👆 ✅, 🔢 🎀 ⏮️ `@asynccontextmanager`.
-
-👈 🗜 🔢 🔘 🕳 🤙 "**🔁 🔑 👨💼**".
-
-{* ../../docs_src/events/tutorial003.py hl[1,13] *}
-
-**🔑 👨💼** 🐍 🕳 👈 👆 💪 ⚙️ `with` 📄, 🖼, `open()` 💪 ⚙️ 🔑 👨💼:
-
-```Python
-with open("file.txt") as file:
- file.read()
-```
-
-⏮️ ⏬ 🐍, 📤 **🔁 🔑 👨💼**. 👆 🔜 ⚙️ ⚫️ ⏮️ `async with`:
-
-```Python
-async with lifespan(app):
- await do_stuff()
-```
-
-🕐❔ 👆 ✍ 🔑 👨💼 ⚖️ 🔁 🔑 👨💼 💖 🔛, ⚫️❔ ⚫️ 🔨 👈, ⏭ 🛬 `with` 🍫, ⚫️ 🔜 🛠️ 📟 ⏭ `yield`, & ⏮️ ❎ `with` 🍫, ⚫️ 🔜 🛠️ 📟 ⏮️ `yield`.
-
-👆 📟 🖼 🔛, 👥 🚫 ⚙️ ⚫️ 🔗, ✋️ 👥 🚶♀️ ⚫️ FastAPI ⚫️ ⚙️ ⚫️.
-
-`lifespan` 🔢 `FastAPI` 📱 ✊ **🔁 🔑 👨💼**, 👥 💪 🚶♀️ 👆 🆕 `lifespan` 🔁 🔑 👨💼 ⚫️.
-
-{* ../../docs_src/events/tutorial003.py hl[22] *}
-
-## 🎛 🎉 (😢)
-
-/// warning
-
-👍 🌌 🍵 *🕴* & *🤫* ⚙️ `lifespan` 🔢 `FastAPI` 📱 🔬 🔛.
-
-👆 💪 🎲 🚶 👉 🍕.
-
-///
-
-📤 🎛 🌌 🔬 👉 ⚛ 🛠️ ⏮️ *🕴* & ⏮️ *🤫*.
-
-👆 💪 🔬 🎉 🐕🦺 (🔢) 👈 💪 🛠️ ⏭ 🈸 ▶️ 🆙, ⚖️ 🕐❔ 🈸 🤫 🔽.
-
-👫 🔢 💪 📣 ⏮️ `async def` ⚖️ 😐 `def`.
-
-### `startup` 🎉
-
-🚮 🔢 👈 🔜 🏃 ⏭ 🈸 ▶️, 📣 ⚫️ ⏮️ 🎉 `"startup"`:
-
-{* ../../docs_src/events/tutorial001.py hl[8] *}
-
-👉 💼, `startup` 🎉 🐕🦺 🔢 🔜 🔢 🏬 "💽" ( `dict`) ⏮️ 💲.
-
-👆 💪 🚮 🌅 🌘 1️⃣ 🎉 🐕🦺 🔢.
-
-& 👆 🈸 🏆 🚫 ▶️ 📨 📨 ⏭ 🌐 `startup` 🎉 🐕🦺 ✔️ 🏁.
-
-### `shutdown` 🎉
-
-🚮 🔢 👈 🔜 🏃 🕐❔ 🈸 🤫 🔽, 📣 ⚫️ ⏮️ 🎉 `"shutdown"`:
-
-{* ../../docs_src/events/tutorial002.py hl[6] *}
-
-📥, `shutdown` 🎉 🐕🦺 🔢 🔜 ✍ ✍ ⏸ `"Application shutdown"` 📁 `log.txt`.
-
-/// info
-
-`open()` 🔢, `mode="a"` ⛓ "🎻",, ⏸ 🔜 🚮 ⏮️ ⚫️❔ 🔛 👈 📁, 🍵 📁 ⏮️ 🎚.
-
-///
-
-/// tip
-
-👀 👈 👉 💼 👥 ⚙️ 🐩 🐍 `open()` 🔢 👈 🔗 ⏮️ 📁.
-
-, ⚫️ 🔌 👤/🅾 (🔢/🔢), 👈 🚚 "⌛" 👜 ✍ 💾.
-
-✋️ `open()` 🚫 ⚙️ `async` & `await`.
-
-, 👥 📣 🎉 🐕🦺 🔢 ⏮️ 🐩 `def` ↩️ `async def`.
-
-///
-
-/// info
-
-👆 💪 ✍ 🌅 🔃 👫 🎉 🐕🦺 💃 🎉' 🩺.
-
-///
-
-### `startup` & `shutdown` 👯♂️
-
-📤 ↕ 🤞 👈 ⚛ 👆 *🕴* & *🤫* 🔗, 👆 💪 💚 ▶️ 🕳 & ⤴️ 🏁 ⚫️, 📎 ℹ & ⤴️ 🚀 ⚫️, ♒️.
-
-🔨 👈 👽 🔢 👈 🚫 💰 ⚛ ⚖️ 🔢 👯♂️ 🌅 ⚠ 👆 🔜 💪 🏪 💲 🌐 🔢 ⚖️ 🎏 🎱.
-
-↩️ 👈, ⚫️ 🔜 👍 ↩️ ⚙️ `lifespan` 🔬 🔛.
-
-## 📡 ℹ
-
-📡 ℹ 😟 🤓. 👶
-
-🔘, 🔫 📡 🔧, 👉 🍕 🔆 🛠️, & ⚫️ 🔬 🎉 🤙 `startup` & `shutdown`.
-
-## 🎧 🈸
-
-👶 ✔️ 🤯 👈 👫 🔆 🎉 (🕴 & 🤫) 🔜 🕴 🛠️ 👑 🈸, 🚫 [🎧 🈸 - 🗻](sub-applications.md){.internal-link target=_blank}.
diff --git a/docs/em/docs/advanced/generate-clients.md b/docs/em/docs/advanced/generate-clients.md
deleted file mode 100644
index a680c9051e..0000000000
--- a/docs/em/docs/advanced/generate-clients.md
+++ /dev/null
@@ -1,238 +0,0 @@
-# 🏗 👩💻
-
-**FastAPI** ⚓️ 🔛 🗄 🔧, 👆 🤚 🏧 🔗 ⏮️ 📚 🧰, 🔌 🏧 🛠️ 🩺 (🚚 🦁 🎚).
-
-1️⃣ 🎯 📈 👈 🚫 🎯 ⭐ 👈 👆 💪 **🏗 👩💻** (🕣 🤙 **📱** ) 👆 🛠️, 📚 🎏 **🛠️ 🇪🇸**.
-
-## 🗄 👩💻 🚂
-
-📤 📚 🧰 🏗 👩💻 ⚪️➡️ **🗄**.
-
-⚠ 🧰 🗄 🚂.
-
-🚥 👆 🏗 **🕸**, 📶 😌 🎛 🗄-📕-🇦🇪.
-
-## 🏗 📕 🕸 👩💻
-
-➡️ ▶️ ⏮️ 🙅 FastAPI 🈸:
-
-{* ../../docs_src/generate_clients/tutorial001.py hl[9:11,14:15,18,19,23] *}
-
-👀 👈 *➡ 🛠️* 🔬 🏷 👫 ⚙️ 📨 🚀 & 📨 🚀, ⚙️ 🏷 `Item` & `ResponseMessage`.
-
-### 🛠️ 🩺
-
-🚥 👆 🚶 🛠️ 🩺, 👆 🔜 👀 👈 ⚫️ ✔️ **🔗** 📊 📨 📨 & 📨 📨:
-
-
-
-👆 💪 👀 👈 🔗 ↩️ 👫 📣 ⏮️ 🏷 📱.
-
-👈 ℹ 💪 📱 **🗄 🔗**, & ⤴️ 🎦 🛠️ 🩺 (🦁 🎚).
-
-& 👈 🎏 ℹ ⚪️➡️ 🏷 👈 🔌 🗄 ⚫️❔ 💪 ⚙️ **🏗 👩💻 📟**.
-
-### 🏗 📕 👩💻
-
-🔜 👈 👥 ✔️ 📱 ⏮️ 🏷, 👥 💪 🏗 👩💻 📟 🕸.
-
-#### ❎ `openapi-ts`
-
-👆 💪 ❎ `openapi-ts` 👆 🕸 📟 ⏮️:
-
-
-
-👆 🔜 🤚 ✍ 🚀 📨:
-
-
-
-/// tip
-
-👀 ✍ `name` & `price`, 👈 🔬 FastAPI 🈸, `Item` 🏷.
-
-///
-
-👆 🔜 ✔️ ⏸ ❌ 📊 👈 👆 📨:
-
-
-
-📨 🎚 🔜 ✔️ ✍:
-
-
-
-## FastAPI 📱 ⏮️ 🔖
-
-📚 💼 👆 FastAPI 📱 🔜 🦏, & 👆 🔜 🎲 ⚙️ 🔖 🎏 🎏 👪 *➡ 🛠️*.
-
-🖼, 👆 💪 ✔️ 📄 **🏬** & ➕1️⃣ 📄 **👩💻**, & 👫 💪 👽 🔖:
-
-
-{* ../../docs_src/generate_clients/tutorial002.py hl[23,28,36] *}
-
-### 🏗 📕 👩💻 ⏮️ 🔖
-
-🚥 👆 🏗 👩💻 FastAPI 📱 ⚙️ 🔖, ⚫️ 🔜 🛎 🎏 👩💻 📟 ⚓️ 🔛 🔖.
-
-👉 🌌 👆 🔜 💪 ✔️ 👜 ✔ & 👪 ☑ 👩💻 📟:
-
-
-
-👉 💼 👆 ✔️:
-
-* `ItemsService`
-* `UsersService`
-
-### 👩💻 👩🔬 📛
-
-▶️️ 🔜 🏗 👩🔬 📛 💖 `createItemItemsPost` 🚫 👀 📶 🧹:
-
-```TypeScript
-ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
-```
-
-...👈 ↩️ 👩💻 🚂 ⚙️ 🗄 🔗 **🛠️ 🆔** 🔠 *➡ 🛠️*.
-
-🗄 🚚 👈 🔠 🛠️ 🆔 😍 🤭 🌐 *➡ 🛠️*, FastAPI ⚙️ **🔢 📛**, **➡**, & **🇺🇸🔍 👩🔬/🛠️** 🏗 👈 🛠️ 🆔, ↩️ 👈 🌌 ⚫️ 💪 ⚒ 💭 👈 🛠️ 🆔 😍.
-
-✋️ 👤 🔜 🎦 👆 ❔ 📉 👈 ⏭. 👶
-
-## 🛃 🛠️ 🆔 & 👍 👩🔬 📛
-
-👆 💪 **🔀** 🌌 👫 🛠️ 🆔 **🏗** ⚒ 👫 🙅 & ✔️ **🙅 👩🔬 📛** 👩💻.
-
-👉 💼 👆 🔜 ✔️ 🚚 👈 🔠 🛠️ 🆔 **😍** 🎏 🌌.
-
-🖼, 👆 💪 ⚒ 💭 👈 🔠 *➡ 🛠️* ✔️ 🔖, & ⤴️ 🏗 🛠️ 🆔 ⚓️ 🔛 **🔖** & *➡ 🛠️* **📛** (🔢 📛).
-
-### 🛃 🏗 😍 🆔 🔢
-
-FastAPI ⚙️ **😍 🆔** 🔠 *➡ 🛠️*, ⚫️ ⚙️ **🛠️ 🆔** & 📛 🙆 💪 🛃 🏷, 📨 ⚖️ 📨.
-
-👆 💪 🛃 👈 🔢. ⚫️ ✊ `APIRoute` & 🔢 🎻.
-
-🖼, 📥 ⚫️ ⚙️ 🥇 🔖 (👆 🔜 🎲 ✔️ 🕴 1️⃣ 🔖) & *➡ 🛠️* 📛 (🔢 📛).
-
-👆 💪 ⤴️ 🚶♀️ 👈 🛃 🔢 **FastAPI** `generate_unique_id_function` 🔢:
-
-{* ../../docs_src/generate_clients/tutorial003.py hl[8:9,12] *}
-
-### 🏗 📕 👩💻 ⏮️ 🛃 🛠️ 🆔
-
-🔜 🚥 👆 🏗 👩💻 🔄, 👆 🔜 👀 👈 ⚫️ ✔️ 📉 👩🔬 📛:
-
-
-
-👆 👀, 👩🔬 📛 🔜 ✔️ 🔖 & ⤴️ 🔢 📛, 🔜 👫 🚫 🔌 ℹ ⚪️➡️ 📛 ➡ & 🇺🇸🔍 🛠️.
-
-### 🗜 🗄 🔧 👩💻 🚂
-
-🏗 📟 ✔️ **❎ ℹ**.
-
-👥 ⏪ 💭 👈 👉 👩🔬 🔗 **🏬** ↩️ 👈 🔤 `ItemsService` (✊ ⚪️➡️ 🔖), ✋️ 👥 ✔️ 📛 🔡 👩🔬 📛 💁♂️. 👶
-
-👥 🔜 🎲 💚 🚧 ⚫️ 🗄 🏢, 👈 🔜 🚚 👈 🛠️ 🆔 **😍**.
-
-✋️ 🏗 👩💻 👥 💪 **🔀** 🗄 🛠️ 🆔 ▶️️ ⏭ 🏭 👩💻, ⚒ 👈 👩🔬 📛 👌 & **🧹**.
-
-👥 💪 ⏬ 🗄 🎻 📁 `openapi.json` & ⤴️ 👥 💪 **❎ 👈 🔡 🔖** ⏮️ ✍ 💖 👉:
-
-{* ../../docs_src/generate_clients/tutorial004.py *}
-
-⏮️ 👈, 🛠️ 🆔 🔜 📁 ⚪️➡️ 👜 💖 `items-get_items` `get_items`, 👈 🌌 👩💻 🚂 💪 🏗 🙅 👩🔬 📛.
-
-### 🏗 📕 👩💻 ⏮️ 🗜 🗄
-
-🔜 🔚 🏁 📁 `openapi.json`, 👆 🔜 🔀 `package.json` ⚙️ 👈 🇧🇿 📁, 🖼:
-
-```JSON hl_lines="7"
-{
- "name": "frontend-app",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios"
- },
- "author": "",
- "license": "",
- "devDependencies": {
- "@hey-api/openapi-ts": "^0.27.38",
- "typescript": "^4.6.2"
- }
-}
-```
-
-⏮️ 🏭 🆕 👩💻, 👆 🔜 🔜 ✔️ **🧹 👩🔬 📛**, ⏮️ 🌐 **✍**, **⏸ ❌**, ♒️:
-
-
-
-## 💰
-
-🕐❔ ⚙️ 🔁 🏗 👩💻 👆 🔜 **✍** :
-
-* 👩🔬.
-* 📨 🚀 💪, 🔢 🔢, ♒️.
-* 📨 🚀.
-
-👆 🔜 ✔️ **⏸ ❌** 🌐.
-
-& 🕐❔ 👆 ℹ 👩💻 📟, & **♻** 🕸, ⚫️ 🔜 ✔️ 🙆 🆕 *➡ 🛠️* 💪 👩🔬, 🗝 🕐 ❎, & 🙆 🎏 🔀 🔜 🎨 🔛 🏗 📟. 👶
-
-👉 ⛓ 👈 🚥 🕳 🔀 ⚫️ 🔜 **🎨** 🔛 👩💻 📟 🔁. & 🚥 👆 **🏗** 👩💻 ⚫️ 🔜 ❌ 👅 🚥 👆 ✔️ 🙆 **🔖** 📊 ⚙️.
-
-, 👆 🔜 **🔍 📚 ❌** 📶 ⏪ 🛠️ 🛵 ↩️ ✔️ ⌛ ❌ 🎦 🆙 👆 🏁 👩💻 🏭 & ⤴️ 🔄 ℹ 🌐❔ ⚠. 👶
diff --git a/docs/em/docs/advanced/index.md b/docs/em/docs/advanced/index.md
deleted file mode 100644
index 48ef8e46d0..0000000000
--- a/docs/em/docs/advanced/index.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# 🏧 👩💻 🦮
-
-## 🌖 ⚒
-
-👑 [🔰 - 👩💻 🦮](../tutorial/index.md){.internal-link target=_blank} 🔜 🥃 🤝 👆 🎫 🔘 🌐 👑 ⚒ **FastAPI**.
-
-⏭ 📄 👆 🔜 👀 🎏 🎛, 📳, & 🌖 ⚒.
-
-/// tip
-
-⏭ 📄 **🚫 🎯 "🏧"**.
-
- & ⚫️ 💪 👈 👆 ⚙️ 💼, ⚗ 1️⃣ 👫.
-
-///
-
-## ✍ 🔰 🥇
-
-👆 💪 ⚙️ 🏆 ⚒ **FastAPI** ⏮️ 💡 ⚪️➡️ 👑 [🔰 - 👩💻 🦮](../tutorial/index.md){.internal-link target=_blank}.
-
-& ⏭ 📄 🤔 👆 ⏪ ✍ ⚫️, & 🤔 👈 👆 💭 👈 👑 💭.
-
-## 🏎.🅾 ↗️
-
-🚥 👆 🔜 💖 ✊ 🏧-🔰 ↗️ 🔗 👉 📄 🩺, 👆 💪 💚 ✅: 💯-💾 🛠️ ⏮️ FastAPI & ☁ **🏎.🅾**.
-
-👫 ⏳ 🩸 1️⃣0️⃣ 💯 🌐 💰 🛠️ **FastAPI**. 👶 👶
diff --git a/docs/em/docs/advanced/middleware.md b/docs/em/docs/advanced/middleware.md
deleted file mode 100644
index 22d707062f..0000000000
--- a/docs/em/docs/advanced/middleware.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# 🏧 🛠️
-
-👑 🔰 👆 ✍ ❔ 🚮 [🛃 🛠️](../tutorial/middleware.md){.internal-link target=_blank} 👆 🈸.
-
-& ⤴️ 👆 ✍ ❔ 🍵 [⚜ ⏮️ `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}.
-
-👉 📄 👥 🔜 👀 ❔ ⚙️ 🎏 🛠️.
-
-## ❎ 🔫 🛠️
-
-**FastAPI** ⚓️ 🔛 💃 & 🛠️ 🔫 🔧, 👆 💪 ⚙️ 🙆 🔫 🛠️.
-
-🛠️ 🚫 ✔️ ⚒ FastAPI ⚖️ 💃 👷, 📏 ⚫️ ⏩ 🔫 🔌.
-
-🏢, 🔫 🛠️ 🎓 👈 ⌛ 📨 🔫 📱 🥇 ❌.
-
-, 🧾 🥉-🥳 🔫 🛠️ 👫 🔜 🎲 💬 👆 🕳 💖:
-
-```Python
-from unicorn import UnicornMiddleware
-
-app = SomeASGIApp()
-
-new_app = UnicornMiddleware(app, some_config="rainbow")
-```
-
-✋️ FastAPI (🤙 💃) 🚚 🙅 🌌 ⚫️ 👈 ⚒ 💭 👈 🔗 🛠️ 🍵 💽 ❌ & 🛃 ⚠ 🐕🦺 👷 ☑.
-
-👈, 👆 ⚙️ `app.add_middleware()` (🖼 ⚜).
-
-```Python
-from fastapi import FastAPI
-from unicorn import UnicornMiddleware
-
-app = FastAPI()
-
-app.add_middleware(UnicornMiddleware, some_config="rainbow")
-```
-
-`app.add_middleware()` 📨 🛠️ 🎓 🥇 ❌ & 🙆 🌖 ❌ 🚶♀️ 🛠️.
-
-## 🛠️ 🛠️
-
-**FastAPI** 🔌 📚 🛠️ ⚠ ⚙️ 💼, 👥 🔜 👀 ⏭ ❔ ⚙️ 👫.
-
-/// note | 📡 ℹ
-
-⏭ 🖼, 👆 💪 ⚙️ `from starlette.middleware.something import SomethingMiddleware`.
-
-**FastAPI** 🚚 📚 🛠️ `fastapi.middleware` 🏪 👆, 👩💻. ✋️ 🌅 💪 🛠️ 👟 🔗 ⚪️➡️ 💃.
-
-///
-
-## `HTTPSRedirectMiddleware`
-
-🛠️ 👈 🌐 📨 📨 🔜 👯♂️ `https` ⚖️ `wss`.
-
-🙆 📨 📨 `http` ⚖️ `ws` 🔜 ❎ 🔐 ⚖ ↩️.
-
-{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *}
-
-## `TrustedHostMiddleware`
-
-🛠️ 👈 🌐 📨 📨 ✔️ ☑ ⚒ `Host` 🎚, ✔ 💂♂ 🛡 🇺🇸🔍 🦠 🎚 👊.
-
-{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *}
-
-📄 ❌ 🐕🦺:
-
-* `allowed_hosts` - 📇 🆔 📛 👈 🔜 ✔ 📛. 🃏 🆔 ✅ `*.example.com` 🐕🦺 🎀 📁. ✔ 🙆 📛 👯♂️ ⚙️ `allowed_hosts=["*"]` ⚖️ 🚫 🛠️.
-
-🚥 📨 📨 🔨 🚫 ✔ ☑ ⤴️ `400` 📨 🔜 📨.
-
-## `GZipMiddleware`
-
-🍵 🗜 📨 🙆 📨 👈 🔌 `"gzip"` `Accept-Encoding` 🎚.
-
-🛠️ 🔜 🍵 👯♂️ 🐩 & 🎥 📨.
-
-{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *}
-
-📄 ❌ 🐕🦺:
-
-* `minimum_size` - 🚫 🗜 📨 👈 🤪 🌘 👉 💯 📐 🔢. 🔢 `500`.
-
-## 🎏 🛠️
-
-📤 📚 🎏 🔫 🛠️.
-
-🖼:
-
-* Uvicorn `ProxyHeadersMiddleware`
-* 🇸🇲
-
-👀 🎏 💪 🛠️ ✅ 💃 🛠️ 🩺 & 🔫 👌 📇.
diff --git a/docs/em/docs/advanced/openapi-callbacks.md b/docs/em/docs/advanced/openapi-callbacks.md
deleted file mode 100644
index b0a8216681..0000000000
--- a/docs/em/docs/advanced/openapi-callbacks.md
+++ /dev/null
@@ -1,186 +0,0 @@
-# 🗄 ⏲
-
-👆 💪 ✍ 🛠️ ⏮️ *➡ 🛠️* 👈 💪 ⏲ 📨 *🔢 🛠️* ✍ 👱 🙆 (🎲 🎏 👩💻 👈 🔜 *⚙️* 👆 🛠️).
-
-🛠️ 👈 🔨 🕐❔ 👆 🛠️ 📱 🤙 *🔢 🛠️* 📛 "⏲". ↩️ 🖥 👈 🔢 👩💻 ✍ 📨 📨 👆 🛠️ & ⤴️ 👆 🛠️ *🤙 🔙*, 📨 📨 *🔢 🛠️* (👈 🎲 ✍ 🎏 👩💻).
-
-👉 💼, 👆 💪 💚 📄 ❔ 👈 🔢 🛠️ *🔜* 👀 💖. ⚫️❔ *➡ 🛠️* ⚫️ 🔜 ✔️, ⚫️❔ 💪 ⚫️ 🔜 ⌛, ⚫️❔ 📨 ⚫️ 🔜 📨, ♒️.
-
-## 📱 ⏮️ ⏲
-
-➡️ 👀 🌐 👉 ⏮️ 🖼.
-
-🌈 👆 🛠️ 📱 👈 ✔ 🏗 🧾.
-
-👉 🧾 🔜 ✔️ `id`, `title` (📦), `customer`, & `total`.
-
-👩💻 👆 🛠️ (🔢 👩💻) 🔜 ✍ 🧾 👆 🛠️ ⏮️ 🏤 📨.
-
-⤴️ 👆 🛠️ 🔜 (➡️ 🌈):
-
-* 📨 🧾 🕴 🔢 👩💻.
-* 📈 💸.
-* 📨 📨 🔙 🛠️ 👩💻 (🔢 👩💻).
- * 👉 🔜 🔨 📨 🏤 📨 (⚪️➡️ *👆 🛠️*) *🔢 🛠️* 🚚 👈 🔢 👩💻 (👉 "⏲").
-
-## 😐 **FastAPI** 📱
-
-➡️ 🥇 👀 ❔ 😐 🛠️ 📱 🔜 👀 💖 ⏭ ❎ ⏲.
-
-⚫️ 🔜 ✔️ *➡ 🛠️* 👈 🔜 📨 `Invoice` 💪, & 🔢 🔢 `callback_url` 👈 🔜 🔌 📛 ⏲.
-
-👉 🍕 📶 😐, 🌅 📟 🎲 ⏪ 😰 👆:
-
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *}
-
-/// tip
-
-`callback_url` 🔢 🔢 ⚙️ Pydantic 📛 🆎.
-
-///
-
-🕴 🆕 👜 `callbacks=messages_callback_router.routes` ❌ *➡ 🛠️ 👨🎨*. 👥 🔜 👀 ⚫️❔ 👈 ⏭.
-
-## 🔬 ⏲
-
-☑ ⏲ 📟 🔜 🪀 🙇 🔛 👆 👍 🛠️ 📱.
-
-& ⚫️ 🔜 🎲 🪀 📚 ⚪️➡️ 1️⃣ 📱 ⏭.
-
-⚫️ 💪 1️⃣ ⚖️ 2️⃣ ⏸ 📟, 💖:
-
-```Python
-callback_url = "https://example.com/api/v1/invoices/events/"
-httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
-```
-
-✋️ 🎲 🏆 ⚠ 🍕 ⏲ ⚒ 💭 👈 👆 🛠️ 👩💻 (🔢 👩💻) 🛠️ *🔢 🛠️* ☑, 🛄 💽 👈 *👆 🛠️* 🔜 📨 📨 💪 ⏲, ♒️.
-
-, ⚫️❔ 👥 🔜 ⏭ 🚮 📟 📄 ❔ 👈 *🔢 🛠️* 🔜 👀 💖 📨 ⏲ ⚪️➡️ *👆 🛠️*.
-
-👈 🧾 🔜 🎦 🆙 🦁 🎚 `/docs` 👆 🛠️, & ⚫️ 🔜 ➡️ 🔢 👩💻 💭 ❔ 🏗 *🔢 🛠️*.
-
-👉 🖼 🚫 🛠️ ⏲ ⚫️ (👈 💪 ⏸ 📟), 🕴 🧾 🍕.
-
-/// tip
-
-☑ ⏲ 🇺🇸🔍 📨.
-
-🕐❔ 🛠️ ⏲ 👆, 👆 💪 ⚙️ 🕳 💖 🇸🇲 ⚖️ 📨.
-
-///
-
-## ✍ ⏲ 🧾 📟
-
-👉 📟 🏆 🚫 🛠️ 👆 📱, 👥 🕴 💪 ⚫️ *📄* ❔ 👈 *🔢 🛠️* 🔜 👀 💖.
-
-✋️, 👆 ⏪ 💭 ❔ 💪 ✍ 🏧 🧾 🛠️ ⏮️ **FastAPI**.
-
-👥 🔜 ⚙️ 👈 🎏 💡 📄 ❔ *🔢 🛠️* 🔜 👀 💖... 🏗 *➡ 🛠️(Ⓜ)* 👈 🔢 🛠️ 🔜 🛠️ (🕐 👆 🛠️ 🔜 🤙).
-
-/// tip
-
-🕐❔ ✍ 📟 📄 ⏲, ⚫️ 💪 ⚠ 🌈 👈 👆 👈 *🔢 👩💻*. & 👈 👆 ⏳ 🛠️ *🔢 🛠️*, 🚫 *👆 🛠️*.
-
-🍕 🛠️ 👉 ☝ 🎑 ( *🔢 👩💻*) 💪 ℹ 👆 💭 💖 ⚫️ 🌅 ⭐ 🌐❔ 🚮 🔢, Pydantic 🏷 💪, 📨, ♒️. 👈 *🔢 🛠️*.
-
-///
-
-### ✍ ⏲ `APIRouter`
-
-🥇 ✍ 🆕 `APIRouter` 👈 🔜 🔌 1️⃣ ⚖️ 🌅 ⏲.
-
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *}
-
-### ✍ ⏲ *➡ 🛠️*
-
-✍ ⏲ *➡ 🛠️* ⚙️ 🎏 `APIRouter` 👆 ✍ 🔛.
-
-⚫️ 🔜 👀 💖 😐 FastAPI *➡ 🛠️*:
-
-* ⚫️ 🔜 🎲 ✔️ 📄 💪 ⚫️ 🔜 📨, ✅ `body: InvoiceEvent`.
-* & ⚫️ 💪 ✔️ 📄 📨 ⚫️ 🔜 📨, ✅ `response_model=InvoiceEventReceived`.
-
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *}
-
-📤 2️⃣ 👑 🔺 ⚪️➡️ 😐 *➡ 🛠️*:
-
-* ⚫️ 🚫 💪 ✔️ 🙆 ☑ 📟, ↩️ 👆 📱 🔜 🙅 🤙 👉 📟. ⚫️ 🕴 ⚙️ 📄 *🔢 🛠️*. , 🔢 💪 ✔️ `pass`.
-* *➡* 💪 🔌 🗄 3️⃣ 🧬 (👀 🌖 🔛) 🌐❔ ⚫️ 💪 ⚙️ 🔢 ⏮️ 🔢 & 🍕 ⏮️ 📨 📨 *👆 🛠️*.
-
-### ⏲ ➡ 🧬
-
-⏲ *➡* 💪 ✔️ 🗄 3️⃣ 🧬 👈 💪 🔌 🍕 ⏮️ 📨 📨 *👆 🛠️*.
-
-👉 💼, ⚫️ `str`:
-
-```Python
-"{$callback_url}/invoices/{$request.body.id}"
-```
-
-, 🚥 👆 🛠️ 👩💻 (🔢 👩💻) 📨 📨 *👆 🛠️* :
-
-```
-https://yourapi.com/invoices/?callback_url=https://www.external.org/events
-```
-
-⏮️ 🎻 💪:
-
-```JSON
-{
- "id": "2expen51ve",
- "customer": "Mr. Richie Rich",
- "total": "9999"
-}
-```
-
-⤴️ *👆 🛠️* 🔜 🛠️ 🧾, & ☝ ⏪, 📨 ⏲ 📨 `callback_url` ( *🔢 🛠️*):
-
-```
-https://www.external.org/events/invoices/2expen51ve
-```
-
-⏮️ 🎻 💪 ⚗ 🕳 💖:
-
-```JSON
-{
- "description": "Payment celebration",
- "paid": true
-}
-```
-
-& ⚫️ 🔜 ⌛ 📨 ⚪️➡️ 👈 *🔢 🛠️* ⏮️ 🎻 💪 💖:
-
-```JSON
-{
- "ok": true
-}
-```
-
-/// tip
-
-👀 ❔ ⏲ 📛 ⚙️ 🔌 📛 📨 🔢 🔢 `callback_url` (`https://www.external.org/events`) & 🧾 `id` ⚪️➡️ 🔘 🎻 💪 (`2expen51ve`).
-
-///
-
-### 🚮 ⏲ 📻
-
-👉 ☝ 👆 ✔️ *⏲ ➡ 🛠️(Ⓜ)* 💪 (1️⃣(Ⓜ) 👈 *🔢 👩💻* 🔜 🛠️ *🔢 🛠️*) ⏲ 📻 👆 ✍ 🔛.
-
-🔜 ⚙️ 🔢 `callbacks` *👆 🛠️ ➡ 🛠️ 👨🎨* 🚶♀️ 🔢 `.routes` (👈 🤙 `list` 🛣/*➡ 🛠️*) ⚪️➡️ 👈 ⏲ 📻:
-
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *}
-
-/// tip
-
-👀 👈 👆 🚫 🚶♀️ 📻 ⚫️ (`invoices_callback_router`) `callback=`, ✋️ 🔢 `.routes`, `invoices_callback_router.routes`.
-
-///
-
-### ✅ 🩺
-
-🔜 👆 💪 ▶️ 👆 📱 ⏮️ Uvicorn & 🚶 http://127.0.0.1:8000/docs.
-
-👆 🔜 👀 👆 🩺 ✅ "⏲" 📄 👆 *➡ 🛠️* 👈 🎦 ❔ *🔢 🛠️* 🔜 👀 💖:
-
-
diff --git a/docs/em/docs/advanced/path-operation-advanced-configuration.md b/docs/em/docs/advanced/path-operation-advanced-configuration.md
deleted file mode 100644
index 9d9d5fa8da..0000000000
--- a/docs/em/docs/advanced/path-operation-advanced-configuration.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# ➡ 🛠️ 🏧 📳
-
-## 🗄 {
-
-/// warning
-
-🚥 👆 🚫 "🕴" 🗄, 👆 🎲 🚫 💪 👉.
-
-///
-
-👆 💪 ⚒ 🗄 `operationId` ⚙️ 👆 *➡ 🛠️* ⏮️ 🔢 `operation_id`.
-
-👆 🔜 ✔️ ⚒ 💭 👈 ⚫️ 😍 🔠 🛠️.
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *}
-
-### ⚙️ *➡ 🛠️ 🔢* 📛 {
-
-🚥 👆 💚 ⚙️ 👆 🔗' 🔢 📛 `operationId`Ⓜ, 👆 💪 🔁 🤭 🌐 👫 & 🔐 🔠 *➡ 🛠️* `operation_id` ⚙️ 👫 `APIRoute.name`.
-
-👆 🔜 ⚫️ ⏮️ ❎ 🌐 👆 *➡ 🛠️*.
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12:21,24] *}
-
-/// tip
-
-🚥 👆 ❎ 🤙 `app.openapi()`, 👆 🔜 ℹ `operationId`Ⓜ ⏭ 👈.
-
-///
-
-/// warning
-
-🚥 👆 👉, 👆 ✔️ ⚒ 💭 🔠 1️⃣ 👆 *➡ 🛠️ 🔢* ✔️ 😍 📛.
-
-🚥 👫 🎏 🕹 (🐍 📁).
-
-///
-
-## 🚫 ⚪️➡️ 🗄
-
-🚫 *➡ 🛠️* ⚪️➡️ 🏗 🗄 🔗 (& ➡️, ⚪️➡️ 🏧 🧾 ⚙️), ⚙️ 🔢 `include_in_schema` & ⚒ ⚫️ `False`:
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *}
-
-## 🏧 📛 ⚪️➡️ #️⃣
-
-👆 💪 📉 ⏸ ⚙️ ⚪️➡️ #️⃣ *➡ 🛠️ 🔢* 🗄.
-
-❎ `\f` (😖 "📨 🍼" 🦹) 🤕 **FastAPI** 🔁 🔢 ⚙️ 🗄 👉 ☝.
-
-⚫️ 🏆 🚫 🎦 🆙 🧾, ✋️ 🎏 🧰 (✅ 🐉) 🔜 💪 ⚙️ 🎂.
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *}
-
-## 🌖 📨
-
-👆 🎲 ✔️ 👀 ❔ 📣 `response_model` & `status_code` *➡ 🛠️*.
-
-👈 🔬 🗃 🔃 👑 📨 *➡ 🛠️*.
-
-👆 💪 📣 🌖 📨 ⏮️ 👫 🏷, 👔 📟, ♒️.
-
-📤 🎂 📃 📥 🧾 🔃 ⚫️, 👆 💪 ✍ ⚫️ [🌖 📨 🗄](additional-responses.md){.internal-link target=_blank}.
-
-## 🗄 ➕
-
-🕐❔ 👆 📣 *➡ 🛠️* 👆 🈸, **FastAPI** 🔁 🏗 🔗 🗃 🔃 👈 *➡ 🛠️* 🔌 🗄 🔗.
-
-/// note | 📡 ℹ
-
-🗄 🔧 ⚫️ 🤙 🛠️ 🎚.
-
-///
-
-⚫️ ✔️ 🌐 ℹ 🔃 *➡ 🛠️* & ⚙️ 🏗 🏧 🧾.
-
-⚫️ 🔌 `tags`, `parameters`, `requestBody`, `responses`, ♒️.
-
-👉 *➡ 🛠️*-🎯 🗄 🔗 🛎 🏗 🔁 **FastAPI**, ✋️ 👆 💪 ↔ ⚫️.
-
-/// tip
-
-👉 🔅 🎚 ↔ ☝.
-
-🚥 👆 🕴 💪 📣 🌖 📨, 🌅 🏪 🌌 ⚫️ ⏮️ [🌖 📨 🗄](additional-responses.md){.internal-link target=_blank}.
-
-///
-
-👆 💪 ↔ 🗄 🔗 *➡ 🛠️* ⚙️ 🔢 `openapi_extra`.
-
-### 🗄 ↔
-
-👉 `openapi_extra` 💪 👍, 🖼, 📣 [🗄 ↔](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *}
-
-🚥 👆 📂 🏧 🛠️ 🩺, 👆 ↔ 🔜 🎦 🆙 🔝 🎯 *➡ 🛠️*.
-
-
-
-& 🚥 👆 👀 📉 🗄 ( `/openapi.json` 👆 🛠️), 👆 🔜 👀 👆 ↔ 🍕 🎯 *➡ 🛠️* 💁♂️:
-
-```JSON hl_lines="22"
-{
- "openapi": "3.0.2",
- "info": {
- "title": "FastAPI",
- "version": "0.1.0"
- },
- "paths": {
- "/items/": {
- "get": {
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {}
- }
- }
- }
- },
- "x-aperture-labs-portal": "blue"
- }
- }
- }
-}
-```
-
-### 🛃 🗄 *➡ 🛠️* 🔗
-
-📖 `openapi_extra` 🔜 🙇 🔗 ⏮️ 🔁 🏗 🗄 🔗 *➡ 🛠️*.
-
-, 👆 💪 🚮 🌖 💽 🔁 🏗 🔗.
-
-🖼, 👆 💪 💭 ✍ & ✔ 📨 ⏮️ 👆 👍 📟, 🍵 ⚙️ 🏧 ⚒ FastAPI ⏮️ Pydantic, ✋️ 👆 💪 💚 🔬 📨 🗄 🔗.
-
-👆 💪 👈 ⏮️ `openapi_extra`:
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[20:37,39:40] *}
-
-👉 🖼, 👥 🚫 📣 🙆 Pydantic 🏷. 👐, 📨 💪 🚫 🎻 🎻, ⚫️ ✍ 🔗 `bytes`, & 🔢 `magic_data_reader()` 🔜 🈚 🎻 ⚫️ 🌌.
-
-👐, 👥 💪 📣 📈 🔗 📨 💪.
-
-### 🛃 🗄 🎚 🆎
-
-⚙️ 👉 🎏 🎱, 👆 💪 ⚙️ Pydantic 🏷 🔬 🎻 🔗 👈 ⤴️ 🔌 🛃 🗄 🔗 📄 *➡ 🛠️*.
-
-& 👆 💪 👉 🚥 💽 🆎 📨 🚫 🎻.
-
-🖼, 👉 🈸 👥 🚫 ⚙️ FastAPI 🛠️ 🛠️ ⚗ 🎻 🔗 ⚪️➡️ Pydantic 🏷 🚫 🏧 🔬 🎻. 👐, 👥 📣 📨 🎚 🆎 📁, 🚫 🎻:
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22,24] *}
-
-👐, 👐 👥 🚫 ⚙️ 🔢 🛠️ 🛠️, 👥 ⚙️ Pydantic 🏷 ❎ 🏗 🎻 🔗 💽 👈 👥 💚 📨 📁.
-
-⤴️ 👥 ⚙️ 📨 🔗, & ⚗ 💪 `bytes`. 👉 ⛓ 👈 FastAPI 🏆 🚫 🔄 🎻 📨 🚀 🎻.
-
-& ⤴️ 👆 📟, 👥 🎻 👈 📁 🎚 🔗, & ⤴️ 👥 🔄 ⚙️ 🎏 Pydantic 🏷 ✔ 📁 🎚:
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *}
-
-/// tip
-
-📥 👥 🏤-⚙️ 🎏 Pydantic 🏷.
-
-✋️ 🎏 🌌, 👥 💪 ✔️ ✔ ⚫️ 🎏 🌌.
-
-///
diff --git a/docs/em/docs/advanced/response-change-status-code.md b/docs/em/docs/advanced/response-change-status-code.md
deleted file mode 100644
index 4933484dd3..0000000000
--- a/docs/em/docs/advanced/response-change-status-code.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# 📨 - 🔀 👔 📟
-
-👆 🎲 ✍ ⏭ 👈 👆 💪 ⚒ 🔢 [📨 👔 📟](../tutorial/response-status-code.md){.internal-link target=_blank}.
-
-✋️ 💼 👆 💪 📨 🎏 👔 📟 🌘 🔢.
-
-## ⚙️ 💼
-
-🖼, 🌈 👈 👆 💚 📨 🇺🇸🔍 👔 📟 "👌" `200` 🔢.
-
-✋️ 🚥 💽 🚫 🔀, 👆 💚 ✍ ⚫️, & 📨 🇺🇸🔍 👔 📟 "✍" `201`.
-
-✋️ 👆 💚 💪 ⛽ & 🗜 💽 👆 📨 ⏮️ `response_model`.
-
-📚 💼, 👆 💪 ⚙️ `Response` 🔢.
-
-## ⚙️ `Response` 🔢
-
-👆 💪 📣 🔢 🆎 `Response` 👆 *➡ 🛠️ 🔢* (👆 💪 🍪 & 🎚).
-
-& ⤴️ 👆 💪 ⚒ `status_code` 👈 *🔀* 📨 🎚.
-
-{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *}
-
-& ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️).
-
-& 🚥 👆 📣 `response_model`, ⚫️ 🔜 ⚙️ ⛽ & 🗜 🎚 👆 📨.
-
-**FastAPI** 🔜 ⚙️ 👈 *🔀* 📨 ⚗ 👔 📟 (🍪 & 🎚), & 🔜 🚮 👫 🏁 📨 👈 🔌 💲 👆 📨, ⛽ 🙆 `response_model`.
-
-👆 💪 📣 `Response` 🔢 🔗, & ⚒ 👔 📟 👫. ✋️ ✔️ 🤯 👈 🏁 1️⃣ ⚒ 🔜 🏆.
diff --git a/docs/em/docs/advanced/response-cookies.md b/docs/em/docs/advanced/response-cookies.md
deleted file mode 100644
index a6e37ad74d..0000000000
--- a/docs/em/docs/advanced/response-cookies.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# 📨 🍪
-
-## ⚙️ `Response` 🔢
-
-👆 💪 📣 🔢 🆎 `Response` 👆 *➡ 🛠️ 🔢*.
-
-& ⤴️ 👆 💪 ⚒ 🍪 👈 *🔀* 📨 🎚.
-
-{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *}
-
-& ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️).
-
-& 🚥 👆 📣 `response_model`, ⚫️ 🔜 ⚙️ ⛽ & 🗜 🎚 👆 📨.
-
-**FastAPI** 🔜 ⚙️ 👈 *🔀* 📨 ⚗ 🍪 (🎚 & 👔 📟), & 🔜 🚮 👫 🏁 📨 👈 🔌 💲 👆 📨, ⛽ 🙆 `response_model`.
-
-👆 💪 📣 `Response` 🔢 🔗, & ⚒ 🍪 (& 🎚) 👫.
-
-## 📨 `Response` 🔗
-
-👆 💪 ✍ 🍪 🕐❔ 🛬 `Response` 🔗 👆 📟.
-
-👈, 👆 💪 ✍ 📨 🔬 [📨 📨 🔗](response-directly.md){.internal-link target=_blank}.
-
-⤴️ ⚒ 🍪 ⚫️, & ⤴️ 📨 ⚫️:
-
-{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
-
-/// tip
-
-✔️ 🤯 👈 🚥 👆 📨 📨 🔗 ↩️ ⚙️ `Response` 🔢, FastAPI 🔜 📨 ⚫️ 🔗.
-
-, 👆 🔜 ✔️ ⚒ 💭 👆 💽 ☑ 🆎. 🤶 Ⓜ. ⚫️ 🔗 ⏮️ 🎻, 🚥 👆 🛬 `JSONResponse`.
-
- & 👈 👆 🚫 📨 🙆 📊 👈 🔜 ✔️ ⛽ `response_model`.
-
-///
-
-### 🌅 ℹ
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.responses import Response` ⚖️ `from starlette.responses import JSONResponse`.
-
-**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃.
-
- & `Response` 💪 ⚙️ 🛎 ⚒ 🎚 & 🍪, **FastAPI** 🚚 ⚫️ `fastapi.Response`.
-
-///
-
-👀 🌐 💪 🔢 & 🎛, ✅ 🧾 💃.
diff --git a/docs/em/docs/advanced/response-directly.md b/docs/em/docs/advanced/response-directly.md
deleted file mode 100644
index 29819a2055..0000000000
--- a/docs/em/docs/advanced/response-directly.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# 📨 📨 🔗
-
-🕐❔ 👆 ✍ **FastAPI** *➡ 🛠️* 👆 💪 🛎 📨 🙆 📊 ⚪️➡️ ⚫️: `dict`, `list`, Pydantic 🏷, 💽 🏷, ♒️.
-
-🔢, **FastAPI** 🔜 🔁 🗜 👈 📨 💲 🎻 ⚙️ `jsonable_encoder` 🔬 [🎻 🔗 🔢](../tutorial/encoder.md){.internal-link target=_blank}.
-
-⤴️, ⛅ 🎑, ⚫️ 🔜 🚮 👈 🎻-🔗 💽 (✅ `dict`) 🔘 `JSONResponse` 👈 🔜 ⚙️ 📨 📨 👩💻.
-
-✋️ 👆 💪 📨 `JSONResponse` 🔗 ⚪️➡️ 👆 *➡ 🛠️*.
-
-⚫️ 💪 ⚠, 🖼, 📨 🛃 🎚 ⚖️ 🍪.
-
-## 📨 `Response`
-
-👐, 👆 💪 📨 🙆 `Response` ⚖️ 🙆 🎧-🎓 ⚫️.
-
-/// tip
-
-`JSONResponse` ⚫️ 🎧-🎓 `Response`.
-
-///
-
-& 🕐❔ 👆 📨 `Response`, **FastAPI** 🔜 🚶♀️ ⚫️ 🔗.
-
-⚫️ 🏆 🚫 🙆 💽 🛠️ ⏮️ Pydantic 🏷, ⚫️ 🏆 🚫 🗜 🎚 🙆 🆎, ♒️.
-
-👉 🤝 👆 📚 💪. 👆 💪 📨 🙆 📊 🆎, 🔐 🙆 💽 📄 ⚖️ 🔬, ♒️.
-
-## ⚙️ `jsonable_encoder` `Response`
-
-↩️ **FastAPI** 🚫 🙆 🔀 `Response` 👆 📨, 👆 ✔️ ⚒ 💭 ⚫️ 🎚 🔜 ⚫️.
-
-🖼, 👆 🚫🔜 🚮 Pydantic 🏷 `JSONResponse` 🍵 🥇 🏭 ⚫️ `dict` ⏮️ 🌐 📊 🆎 (💖 `datetime`, `UUID`, ♒️) 🗜 🎻-🔗 🆎.
-
-📚 💼, 👆 💪 ⚙️ `jsonable_encoder` 🗜 👆 📊 ⏭ 🚶♀️ ⚫️ 📨:
-
-{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *}
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.responses import JSONResponse`.
-
-**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃.
-
-///
-
-## 🛬 🛃 `Response`
-
-🖼 🔛 🎦 🌐 🍕 👆 💪, ✋️ ⚫️ 🚫 📶 ⚠, 👆 💪 ✔️ 📨 `item` 🔗, & **FastAPI** 🔜 🚮 ⚫️ `JSONResponse` 👆, 🏭 ⚫️ `dict`, ♒️. 🌐 👈 🔢.
-
-🔜, ➡️ 👀 ❔ 👆 💪 ⚙️ 👈 📨 🛃 📨.
-
-➡️ 💬 👈 👆 💚 📨 📂 📨.
-
-👆 💪 🚮 👆 📂 🎚 🎻, 🚮 ⚫️ `Response`, & 📨 ⚫️:
-
-{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
-
-## 🗒
-
-🕐❔ 👆 📨 `Response` 🔗 🚮 📊 🚫 ✔, 🗜 (🎻), 🚫 📄 🔁.
-
-✋️ 👆 💪 📄 ⚫️ 🔬 [🌖 📨 🗄](additional-responses.md){.internal-link target=_blank}.
-
-👆 💪 👀 ⏪ 📄 ❔ ⚙️/📣 👉 🛃 `Response`Ⓜ ⏪ ✔️ 🏧 💽 🛠️, 🧾, ♒️.
diff --git a/docs/em/docs/advanced/response-headers.md b/docs/em/docs/advanced/response-headers.md
deleted file mode 100644
index c255380d67..0000000000
--- a/docs/em/docs/advanced/response-headers.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# 📨 🎚
-
-## ⚙️ `Response` 🔢
-
-👆 💪 📣 🔢 🆎 `Response` 👆 *➡ 🛠️ 🔢* (👆 💪 🍪).
-
-& ⤴️ 👆 💪 ⚒ 🎚 👈 *🔀* 📨 🎚.
-
-{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *}
-
-& ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️).
-
-& 🚥 👆 📣 `response_model`, ⚫️ 🔜 ⚙️ ⛽ & 🗜 🎚 👆 📨.
-
-**FastAPI** 🔜 ⚙️ 👈 *🔀* 📨 ⚗ 🎚 (🍪 & 👔 📟), & 🔜 🚮 👫 🏁 📨 👈 🔌 💲 👆 📨, ⛽ 🙆 `response_model`.
-
-👆 💪 📣 `Response` 🔢 🔗, & ⚒ 🎚 (& 🍪) 👫.
-
-## 📨 `Response` 🔗
-
-👆 💪 🚮 🎚 🕐❔ 👆 📨 `Response` 🔗.
-
-✍ 📨 🔬 [📨 📨 🔗](response-directly.md){.internal-link target=_blank} & 🚶♀️ 🎚 🌖 🔢:
-
-{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *}
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.responses import Response` ⚖️ `from starlette.responses import JSONResponse`.
-
-**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃.
-
- & `Response` 💪 ⚙️ 🛎 ⚒ 🎚 & 🍪, **FastAPI** 🚚 ⚫️ `fastapi.Response`.
-
-///
-
-## 🛃 🎚
-
-✔️ 🤯 👈 🛃 © 🎚 💪 🚮 ⚙️ '✖-' 🔡.
-
-✋️ 🚥 👆 ✔️ 🛃 🎚 👈 👆 💚 👩💻 🖥 💪 👀, 👆 💪 🚮 👫 👆 ⚜ 📳 (✍ 🌅 [⚜ (✖️-🇨🇳 ℹ 🤝)](../tutorial/cors.md){.internal-link target=_blank}), ⚙️ 🔢 `expose_headers` 📄 💃 ⚜ 🩺.
diff --git a/docs/em/docs/advanced/security/http-basic-auth.md b/docs/em/docs/advanced/security/http-basic-auth.md
deleted file mode 100644
index 73736f3b35..0000000000
--- a/docs/em/docs/advanced/security/http-basic-auth.md
+++ /dev/null
@@ -1,107 +0,0 @@
-# 🇺🇸🔍 🔰 🔐
-
-🙅 💼, 👆 💪 ⚙️ 🇺🇸🔍 🔰 🔐.
-
-🇺🇸🔍 🔰 🔐, 🈸 ⌛ 🎚 👈 🔌 🆔 & 🔐.
-
-🚥 ⚫️ 🚫 📨 ⚫️, ⚫️ 📨 🇺🇸🔍 4️⃣0️⃣1️⃣ "⛔" ❌.
-
-& 📨 🎚 `WWW-Authenticate` ⏮️ 💲 `Basic`, & 📦 `realm` 🔢.
-
-👈 💬 🖥 🎦 🛠️ 📋 🆔 & 🔐.
-
-⤴️, 🕐❔ 👆 🆎 👈 🆔 & 🔐, 🖥 📨 👫 🎚 🔁.
-
-## 🙅 🇺🇸🔍 🔰 🔐
-
-* 🗄 `HTTPBasic` & `HTTPBasicCredentials`.
-* ✍ "`security` ⚖" ⚙️ `HTTPBasic`.
-* ⚙️ 👈 `security` ⏮️ 🔗 👆 *➡ 🛠️*.
-* ⚫️ 📨 🎚 🆎 `HTTPBasicCredentials`:
- * ⚫️ 🔌 `username` & `password` 📨.
-
-{* ../../docs_src/security/tutorial006.py hl[2,6,10] *}
-
-🕐❔ 👆 🔄 📂 📛 🥇 🕰 (⚖️ 🖊 "🛠️" 🔼 🩺) 🖥 🔜 💭 👆 👆 🆔 & 🔐:
-
-
-
-## ✅ 🆔
-
-📥 🌅 🏁 🖼.
-
-⚙️ 🔗 ✅ 🚥 🆔 & 🔐 ☑.
-
-👉, ⚙️ 🐍 🐩 🕹 `secrets` ✅ 🆔 & 🔐.
-
-`secrets.compare_digest()` 💪 ✊ `bytes` ⚖️ `str` 👈 🕴 🔌 🔠 🦹 (🕐 🇪🇸), 👉 ⛓ ⚫️ 🚫🔜 👷 ⏮️ 🦹 💖 `á`, `Sebastián`.
-
-🍵 👈, 👥 🥇 🗜 `username` & `password` `bytes` 🔢 👫 ⏮️ 🔠-8️⃣.
-
-⤴️ 👥 💪 ⚙️ `secrets.compare_digest()` 🚚 👈 `credentials.username` `"stanleyjobson"`, & 👈 `credentials.password` `"swordfish"`.
-
-{* ../../docs_src/security/tutorial007.py hl[1,11:21] *}
-
-👉 🔜 🎏:
-
-```Python
-if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
- # Return some error
- ...
-```
-
-✋️ ⚙️ `secrets.compare_digest()` ⚫️ 🔜 🔐 🛡 🆎 👊 🤙 "🕰 👊".
-
-### ⏲ 👊
-
-✋️ ⚫️❔ "⏲ 👊"❓
-
-➡️ 🌈 👊 🔄 💭 🆔 & 🔐.
-
-& 👫 📨 📨 ⏮️ 🆔 `johndoe` & 🔐 `love123`.
-
-⤴️ 🐍 📟 👆 🈸 🔜 🌓 🕳 💖:
-
-```Python
-if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
- ...
-```
-
-✋️ ▶️️ 🙍 🐍 🔬 🥇 `j` `johndoe` 🥇 `s` `stanleyjobson`, ⚫️ 🔜 📨 `False`, ↩️ ⚫️ ⏪ 💭 👈 📚 2️⃣ 🎻 🚫 🎏, 💭 👈 "📤 🙅♂ 💪 🗑 🌅 📊 ⚖ 🎂 🔤". & 👆 🈸 🔜 💬 "❌ 👩💻 ⚖️ 🔐".
-
-✋️ ⤴️ 👊 🔄 ⏮️ 🆔 `stanleyjobsox` & 🔐 `love123`.
-
-& 👆 🈸 📟 🔨 🕳 💖:
-
-```Python
-if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
- ...
-```
-
-🐍 🔜 ✔️ 🔬 🎂 `stanleyjobso` 👯♂️ `stanleyjobsox` & `stanleyjobson` ⏭ 🤔 👈 👯♂️ 🎻 🚫 🎏. ⚫️ 🔜 ✊ ➕ ⏲ 📨 🔙 "❌ 👩💻 ⚖️ 🔐".
-
-#### 🕰 ❔ ℹ 👊
-
-👈 ☝, 👀 👈 💽 ✊ ⏲ 📏 📨 "❌ 👩💻 ⚖️ 🔐" 📨, 👊 🔜 💭 👈 👫 🤚 _🕳_ ▶️️, ▶️ 🔤 ▶️️.
-
-& ⤴️ 👫 💪 🔄 🔄 🤔 👈 ⚫️ 🎲 🕳 🌖 🎏 `stanleyjobsox` 🌘 `johndoe`.
-
-#### "🕴" 👊
-
-↗️, 👊 🔜 🚫 🔄 🌐 👉 ✋, 👫 🔜 ✍ 📋 ⚫️, 🎲 ⏮️ 💯 ⚖️ 💯 💯 📍 🥈. & 🔜 🤚 1️⃣ ➕ ☑ 🔤 🕰.
-
-✋️ 🔨 👈, ⏲ ⚖️ 📆 👊 🔜 ✔️ 💭 ☑ 🆔 & 🔐, ⏮️ "ℹ" 👆 🈸, ⚙️ 🕰 ✊ ❔.
-
-#### 🔧 ⚫️ ⏮️ `secrets.compare_digest()`
-
-✋️ 👆 📟 👥 🤙 ⚙️ `secrets.compare_digest()`.
-
-📏, ⚫️ 🔜 ✊ 🎏 🕰 🔬 `stanleyjobsox` `stanleyjobson` 🌘 ⚫️ ✊ 🔬 `johndoe` `stanleyjobson`. & 🎏 🔐.
-
-👈 🌌, ⚙️ `secrets.compare_digest()` 👆 🈸 📟, ⚫️ 🔜 🔒 🛡 👉 🎂 ↔ 💂♂ 👊.
-
-### 📨 ❌
-
-⏮️ 🔍 👈 🎓 ❌, 📨 `HTTPException` ⏮️ 👔 📟 4️⃣0️⃣1️⃣ (🎏 📨 🕐❔ 🙅♂ 🎓 🚚) & 🚮 🎚 `WWW-Authenticate` ⚒ 🖥 🎦 💳 📋 🔄:
-
-{* ../../docs_src/security/tutorial007.py hl[23:27] *}
diff --git a/docs/em/docs/advanced/security/index.md b/docs/em/docs/advanced/security/index.md
deleted file mode 100644
index 5cdc475053..0000000000
--- a/docs/em/docs/advanced/security/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# 🏧 💂♂
-
-## 🌖 ⚒
-
-📤 ➕ ⚒ 🍵 💂♂ ↖️ ⚪️➡️ 🕐 📔 [🔰 - 👩💻 🦮: 💂♂](../../tutorial/security/index.md){.internal-link target=_blank}.
-
-/// tip
-
-⏭ 📄 **🚫 🎯 "🏧"**.
-
- & ⚫️ 💪 👈 👆 ⚙️ 💼, ⚗ 1️⃣ 👫.
-
-///
-
-## ✍ 🔰 🥇
-
-⏭ 📄 🤔 👆 ⏪ ✍ 👑 [🔰 - 👩💻 🦮: 💂♂](../../tutorial/security/index.md){.internal-link target=_blank}.
-
-👫 🌐 ⚓️ 🔛 🎏 🔧, ✋️ ✔ ➕ 🛠️.
diff --git a/docs/em/docs/advanced/security/oauth2-scopes.md b/docs/em/docs/advanced/security/oauth2-scopes.md
deleted file mode 100644
index 9e3bc00587..0000000000
--- a/docs/em/docs/advanced/security/oauth2-scopes.md
+++ /dev/null
@@ -1,274 +0,0 @@
-# Oauth2️⃣ ↔
-
-👆 💪 ⚙️ Oauth2️⃣ ↔ 🔗 ⏮️ **FastAPI**, 👫 🛠️ 👷 💎.
-
-👉 🔜 ✔ 👆 ✔️ 🌖 👌-🧽 ✔ ⚙️, 📄 Oauth2️⃣ 🐩, 🛠️ 🔘 👆 🗄 🈸 (& 🛠️ 🩺).
-
-Oauth2️⃣ ⏮️ ↔ 🛠️ ⚙️ 📚 🦏 🤝 🐕🦺, 💖 👱📔, 🇺🇸🔍, 📂, 🤸♂, 👱📔, ♒️. 👫 ⚙️ ⚫️ 🚚 🎯 ✔ 👩💻 & 🈸.
-
-🔠 🕰 👆 "🕹 ⏮️" 👱📔, 🇺🇸🔍, 📂, 🤸♂, 👱📔, 👈 🈸 ⚙️ Oauth2️⃣ ⏮️ ↔.
-
-👉 📄 👆 🔜 👀 ❔ 🛠️ 🤝 & ✔ ⏮️ 🎏 Oauth2️⃣ ⏮️ ↔ 👆 **FastAPI** 🈸.
-
-/// warning
-
-👉 🌅 ⚖️ 🌘 🏧 📄. 🚥 👆 ▶️, 👆 💪 🚶 ⚫️.
-
-👆 🚫 🎯 💪 Oauth2️⃣ ↔, & 👆 💪 🍵 🤝 & ✔ 👐 👆 💚.
-
-✋️ Oauth2️⃣ ⏮️ ↔ 💪 🎆 🛠️ 🔘 👆 🛠️ (⏮️ 🗄) & 👆 🛠️ 🩺.
-
-👐, 👆 🛠️ 📚 ↔, ⚖️ 🙆 🎏 💂♂/✔ 📄, 👐 👆 💪, 👆 📟.
-
-📚 💼, Oauth2️⃣ ⏮️ ↔ 💪 👹.
-
-✋️ 🚥 👆 💭 👆 💪 ⚫️, ⚖️ 👆 😟, 🚧 👂.
-
-///
-
-## Oauth2️⃣ ↔ & 🗄
-
-Oauth2️⃣ 🔧 🔬 "↔" 📇 🎻 🎏 🚀.
-
-🎚 🔠 👉 🎻 💪 ✔️ 🙆 📁, ✋️ 🔜 🚫 🔌 🚀.
-
-👫 ↔ 🎨 "✔".
-
-🗄 (✅ 🛠️ 🩺), 👆 💪 🔬 "💂♂ ⚖".
-
-🕐❔ 1️⃣ 👫 💂♂ ⚖ ⚙️ Oauth2️⃣, 👆 💪 📣 & ⚙️ ↔.
-
-🔠 "↔" 🎻 (🍵 🚀).
-
-👫 🛎 ⚙️ 📣 🎯 💂♂ ✔, 🖼:
-
-* `users:read` ⚖️ `users:write` ⚠ 🖼.
-* `instagram_basic` ⚙️ 👱📔 / 👱📔.
-* `https://www.googleapis.com/auth/drive` ⚙️ 🇺🇸🔍.
-
-/// info
-
-Oauth2️⃣ "↔" 🎻 👈 📣 🎯 ✔ ✔.
-
-⚫️ 🚫 🤔 🚥 ⚫️ ✔️ 🎏 🦹 💖 `:` ⚖️ 🚥 ⚫️ 📛.
-
-👈 ℹ 🛠️ 🎯.
-
-Oauth2️⃣ 👫 🎻.
-
-///
-
-## 🌐 🎑
-
-🥇, ➡️ 🔜 👀 🍕 👈 🔀 ⚪️➡️ 🖼 👑 **🔰 - 👩💻 🦮** [Oauth2️⃣ ⏮️ 🔐 (& 🔁), 📨 ⏮️ 🥙 🤝](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. 🔜 ⚙️ Oauth2️⃣ ↔:
-
-{* ../../docs_src/security/tutorial005.py hl[2,4,8,12,46,64,105,107:115,121:125,129:135,140,156] *}
-
-🔜 ➡️ 📄 👈 🔀 🔁 🔁.
-
-## Oauth2️⃣ 💂♂ ⚖
-
-🥇 🔀 👈 🔜 👥 📣 Oauth2️⃣ 💂♂ ⚖ ⏮️ 2️⃣ 💪 ↔, `me` & `items`.
-
-`scopes` 🔢 📨 `dict` ⏮️ 🔠 ↔ 🔑 & 📛 💲:
-
-{* ../../docs_src/security/tutorial005.py hl[62:65] *}
-
-↩️ 👥 🔜 📣 📚 ↔, 👫 🔜 🎦 🆙 🛠️ 🩺 🕐❔ 👆 🕹-/✔.
-
-& 👆 🔜 💪 🖊 ❔ ↔ 👆 💚 🤝 🔐: `me` & `items`.
-
-👉 🎏 🛠️ ⚙️ 🕐❔ 👆 🤝 ✔ ⏪ 🚨 ⏮️ 👱📔, 🇺🇸🔍, 📂, ♒️:
-
-
-
-## 🥙 🤝 ⏮️ ↔
-
-🔜, 🔀 🤝 *➡ 🛠️* 📨 ↔ 📨.
-
-👥 ⚙️ 🎏 `OAuth2PasswordRequestForm`. ⚫️ 🔌 🏠 `scopes` ⏮️ `list` `str`, ⏮️ 🔠 ↔ ⚫️ 📨 📨.
-
-& 👥 📨 ↔ 🍕 🥙 🤝.
-
-/// danger
-
-🦁, 📥 👥 ❎ ↔ 📨 🔗 🤝.
-
-✋️ 👆 🈸, 💂♂, 👆 🔜 ⚒ 💭 👆 🕴 🚮 ↔ 👈 👩💻 🤙 💪 ✔️, ⚖️ 🕐 👆 ✔️ 🔁.
-
-///
-
-{* ../../docs_src/security/tutorial005.py hl[156] *}
-
-## 📣 ↔ *➡ 🛠️* & 🔗
-
-🔜 👥 📣 👈 *➡ 🛠️* `/users/me/items/` 🚚 ↔ `items`.
-
-👉, 👥 🗄 & ⚙️ `Security` ⚪️➡️ `fastapi`.
-
-👆 💪 ⚙️ `Security` 📣 🔗 (💖 `Depends`), ✋️ `Security` 📨 🔢 `scopes` ⏮️ 📇 ↔ (🎻).
-
-👉 💼, 👥 🚶♀️ 🔗 🔢 `get_current_active_user` `Security` (🎏 🌌 👥 🔜 ⏮️ `Depends`).
-
-✋️ 👥 🚶♀️ `list` ↔, 👉 💼 ⏮️ 1️⃣ ↔: `items` (⚫️ 💪 ✔️ 🌅).
-
-& 🔗 🔢 `get_current_active_user` 💪 📣 🎧-🔗, 🚫 🕴 ⏮️ `Depends` ✋️ ⏮️ `Security`. 📣 🚮 👍 🎧-🔗 🔢 (`get_current_user`), & 🌖 ↔ 📄.
-
-👉 💼, ⚫️ 🚚 ↔ `me` (⚫️ 💪 🚚 🌅 🌘 1️⃣ ↔).
-
-/// note
-
-👆 🚫 🎯 💪 🚮 🎏 ↔ 🎏 🥉.
-
-👥 🔨 ⚫️ 📥 🎦 ❔ **FastAPI** 🍵 ↔ 📣 🎏 🎚.
-
-///
-
-{* ../../docs_src/security/tutorial005.py hl[4,140,169] *}
-
-/// info | 📡 ℹ
-
-`Security` 🤙 🏿 `Depends`, & ⚫️ ✔️ 1️⃣ ➕ 🔢 👈 👥 🔜 👀 ⏪.
-
-✋️ ⚙️ `Security` ↩️ `Depends`, **FastAPI** 🔜 💭 👈 ⚫️ 💪 📣 💂♂ ↔, ⚙️ 👫 🔘, & 📄 🛠️ ⏮️ 🗄.
-
-✋️ 🕐❔ 👆 🗄 `Query`, `Path`, `Depends`, `Security` & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓.
-
-///
-
-## ⚙️ `SecurityScopes`
-
-🔜 ℹ 🔗 `get_current_user`.
-
-👉 1️⃣ ⚙️ 🔗 🔛.
-
-📥 👥 ⚙️ 🎏 Oauth2️⃣ ⚖ 👥 ✍ ⏭, 📣 ⚫️ 🔗: `oauth2_scheme`.
-
-↩️ 👉 🔗 🔢 🚫 ✔️ 🙆 ↔ 📄 ⚫️, 👥 💪 ⚙️ `Depends` ⏮️ `oauth2_scheme`, 👥 🚫 ✔️ ⚙️ `Security` 🕐❔ 👥 🚫 💪 ✔ 💂♂ ↔.
-
-👥 📣 🎁 🔢 🆎 `SecurityScopes`, 🗄 ⚪️➡️ `fastapi.security`.
-
-👉 `SecurityScopes` 🎓 🎏 `Request` (`Request` ⚙️ 🤚 📨 🎚 🔗).
-
-{* ../../docs_src/security/tutorial005.py hl[8,105] *}
-
-## ⚙️ `scopes`
-
-🔢 `security_scopes` 🔜 🆎 `SecurityScopes`.
-
-⚫️ 🔜 ✔️ 🏠 `scopes` ⏮️ 📇 ⚗ 🌐 ↔ ✔ ⚫️ & 🌐 🔗 👈 ⚙️ 👉 🎧-🔗. 👈 ⛓, 🌐 "⚓️"... 👉 💪 🔊 😨, ⚫️ 🔬 🔄 ⏪ 🔛.
-
-`security_scopes` 🎚 (🎓 `SecurityScopes`) 🚚 `scope_str` 🔢 ⏮️ 👁 🎻, 🔌 👈 ↔ 👽 🚀 (👥 🔜 ⚙️ ⚫️).
-
-👥 ✍ `HTTPException` 👈 👥 💪 🏤-⚙️ (`raise`) ⏪ 📚 ☝.
-
-👉 ⚠, 👥 🔌 ↔ 🚚 (🚥 🙆) 🎻 👽 🚀 (⚙️ `scope_str`). 👥 🚮 👈 🎻 ⚗ ↔ `WWW-Authenticate` 🎚 (👉 🍕 🔌).
-
-{* ../../docs_src/security/tutorial005.py hl[105,107:115] *}
-
-## ✔ `username` & 💽 💠
-
-👥 ✔ 👈 👥 🤚 `username`, & ⚗ ↔.
-
-& ⤴️ 👥 ✔ 👈 📊 ⏮️ Pydantic 🏷 (✊ `ValidationError` ⚠), & 🚥 👥 🤚 ❌ 👂 🥙 🤝 ⚖️ ⚖ 📊 ⏮️ Pydantic, 👥 🤚 `HTTPException` 👥 ✍ ⏭.
-
-👈, 👥 ℹ Pydantic 🏷 `TokenData` ⏮️ 🆕 🏠 `scopes`.
-
-⚖ 📊 ⏮️ Pydantic 👥 💪 ⚒ 💭 👈 👥 ✔️, 🖼, ⚫️❔ `list` `str` ⏮️ ↔ & `str` ⏮️ `username`.
-
-↩️, 🖼, `dict`, ⚖️ 🕳 🙆, ⚫️ 💪 💔 🈸 ☝ ⏪, ⚒ ⚫️ 💂♂ ⚠.
-
-👥 ✔ 👈 👥 ✔️ 👩💻 ⏮️ 👈 🆔, & 🚥 🚫, 👥 🤚 👈 🎏 ⚠ 👥 ✍ ⏭.
-
-{* ../../docs_src/security/tutorial005.py hl[46,116:128] *}
-
-## ✔ `scopes`
-
-👥 🔜 ✔ 👈 🌐 ↔ ✔, 👉 🔗 & 🌐 ⚓️ (🔌 *➡ 🛠️*), 🔌 ↔ 🚚 🤝 📨, ⏪ 🤚 `HTTPException`.
-
-👉, 👥 ⚙️ `security_scopes.scopes`, 👈 🔌 `list` ⏮️ 🌐 👫 ↔ `str`.
-
-{* ../../docs_src/security/tutorial005.py hl[129:135] *}
-
-## 🔗 🌲 & ↔
-
-➡️ 📄 🔄 👉 🔗 🌲 & ↔.
-
-`get_current_active_user` 🔗 ✔️ 🎧-🔗 🔛 `get_current_user`, ↔ `"me"` 📣 `get_current_active_user` 🔜 🔌 📇 ✔ ↔ `security_scopes.scopes` 🚶♀️ `get_current_user`.
-
-*➡ 🛠️* ⚫️ 📣 ↔, `"items"`, 👉 🔜 📇 `security_scopes.scopes` 🚶♀️ `get_current_user`.
-
-📥 ❔ 🔗 🔗 & ↔ 👀 💖:
-
-* *➡ 🛠️* `read_own_items` ✔️:
- * ✔ ↔ `["items"]` ⏮️ 🔗:
- * `get_current_active_user`:
- * 🔗 🔢 `get_current_active_user` ✔️:
- * ✔ ↔ `["me"]` ⏮️ 🔗:
- * `get_current_user`:
- * 🔗 🔢 `get_current_user` ✔️:
- * 🙅♂ ↔ ✔ ⚫️.
- * 🔗 ⚙️ `oauth2_scheme`.
- * `security_scopes` 🔢 🆎 `SecurityScopes`:
- * 👉 `security_scopes` 🔢 ✔️ 🏠 `scopes` ⏮️ `list` ⚗ 🌐 👫 ↔ 📣 🔛,:
- * `security_scopes.scopes` 🔜 🔌 `["me", "items"]` *➡ 🛠️* `read_own_items`.
- * `security_scopes.scopes` 🔜 🔌 `["me"]` *➡ 🛠️* `read_users_me`, ↩️ ⚫️ 📣 🔗 `get_current_active_user`.
- * `security_scopes.scopes` 🔜 🔌 `[]` (🕳) *➡ 🛠️* `read_system_status`, ↩️ ⚫️ 🚫 📣 🙆 `Security` ⏮️ `scopes`, & 🚮 🔗, `get_current_user`, 🚫 📣 🙆 `scope` 👯♂️.
-
-/// tip
-
-⚠ & "🎱" 👜 📥 👈 `get_current_user` 🔜 ✔️ 🎏 📇 `scopes` ✅ 🔠 *➡ 🛠️*.
-
-🌐 ⚓️ 🔛 `scopes` 📣 🔠 *➡ 🛠️* & 🔠 🔗 🔗 🌲 👈 🎯 *➡ 🛠️*.
-
-///
-
-## 🌖 ℹ 🔃 `SecurityScopes`
-
-👆 💪 ⚙️ `SecurityScopes` 🙆 ☝, & 💗 🥉, ⚫️ 🚫 ✔️ "🌱" 🔗.
-
-⚫️ 🔜 🕧 ✔️ 💂♂ ↔ 📣 ⏮️ `Security` 🔗 & 🌐 ⚓️ **👈 🎯** *➡ 🛠️* & **👈 🎯** 🔗 🌲.
-
-↩️ `SecurityScopes` 🔜 ✔️ 🌐 ↔ 📣 ⚓️, 👆 💪 ⚙️ ⚫️ ✔ 👈 🤝 ✔️ 🚚 ↔ 🇨🇫 🔗 🔢, & ⤴️ 📣 🎏 ↔ 📄 🎏 *➡ 🛠️*.
-
-👫 🔜 ✅ ➡ 🔠 *➡ 🛠️*.
-
-## ✅ ⚫️
-
-🚥 👆 📂 🛠️ 🩺, 👆 💪 🔓 & ✔ ❔ ↔ 👆 💚 ✔.
-
-
-
-🚥 👆 🚫 🖊 🙆 ↔, 👆 🔜 "🔓", ✋️ 🕐❔ 👆 🔄 🔐 `/users/me/` ⚖️ `/users/me/items/` 👆 🔜 🤚 ❌ 💬 👈 👆 🚫 ✔️ 🥃 ✔. 👆 🔜 💪 🔐 `/status/`.
-
-& 🚥 👆 🖊 ↔ `me` ✋️ 🚫 ↔ `items`, 👆 🔜 💪 🔐 `/users/me/` ✋️ 🚫 `/users/me/items/`.
-
-👈 ⚫️❔ 🔜 🔨 🥉 🥳 🈸 👈 🔄 🔐 1️⃣ 👫 *➡ 🛠️* ⏮️ 🤝 🚚 👩💻, ⚓️ 🔛 ❔ 📚 ✔ 👩💻 🤝 🈸.
-
-## 🔃 🥉 🥳 🛠️
-
-👉 🖼 👥 ⚙️ Oauth2️⃣ "🔐" 💧.
-
-👉 ☑ 🕐❔ 👥 🚨 👆 👍 🈸, 🎲 ⏮️ 👆 👍 🕸.
-
-↩️ 👥 💪 💙 ⚫️ 📨 `username` & `password`, 👥 🎛 ⚫️.
-
-✋️ 🚥 👆 🏗 Oauth2️⃣ 🈸 👈 🎏 🔜 🔗 (➡, 🚥 👆 🏗 🤝 🐕🦺 🌓 👱📔, 🇺🇸🔍, 📂, ♒️.) 👆 🔜 ⚙️ 1️⃣ 🎏 💧.
-
-🌅 ⚠ 🔑 💧.
-
-🏆 🔐 📟 💧, ✋️ 🌖 🏗 🛠️ ⚫️ 🚚 🌅 📶. ⚫️ 🌅 🏗, 📚 🐕🦺 🔚 🆙 ✔ 🔑 💧.
-
-/// note
-
-⚫️ ⚠ 👈 🔠 🤝 🐕🦺 📛 👫 💧 🎏 🌌, ⚒ ⚫️ 🍕 👫 🏷.
-
-✋️ 🔚, 👫 🛠️ 🎏 Oauth2️⃣ 🐩.
-
-///
-
-**FastAPI** 🔌 🚙 🌐 👫 Oauth2️⃣ 🤝 💧 `fastapi.security.oauth2`.
-
-## `Security` 👨🎨 `dependencies`
-
-🎏 🌌 👆 💪 🔬 `list` `Depends` 👨🎨 `dependencies` 🔢 (🔬 [🔗 ➡ 🛠️ 👨🎨](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), 👆 💪 ⚙️ `Security` ⏮️ `scopes` 📤.
diff --git a/docs/em/docs/advanced/settings.md b/docs/em/docs/advanced/settings.md
deleted file mode 100644
index 7fdd0d68a6..0000000000
--- a/docs/em/docs/advanced/settings.md
+++ /dev/null
@@ -1,396 +0,0 @@
-# ⚒ & 🌐 🔢
-
-📚 💼 👆 🈸 💪 💪 🔢 ⚒ ⚖️ 📳, 🖼 ㊙ 🔑, 💽 🎓, 🎓 📧 🐕🦺, ♒️.
-
-🏆 👫 ⚒ 🔢 (💪 🔀), 💖 💽 📛. & 📚 💪 🚿, 💖 ㊙.
-
-👉 🤔 ⚫️ ⚠ 🚚 👫 🌐 🔢 👈 ✍ 🈸.
-
-## 🌐 🔢
-
-/// tip
-
-🚥 👆 ⏪ 💭 ⚫️❔ "🌐 🔢" & ❔ ⚙️ 👫, 💭 🆓 🚶 ⏭ 📄 🔛.
-
-///
-
-🌐 🔢 (💭 "🇨🇻 {") 🔢 👈 🖖 🏞 🐍 📟, 🏃♂ ⚙️, & 💪 ✍ 👆 🐍 📟 (⚖️ 🎏 📋 👍).
-
-👆 💪 ✍ & ⚙️ 🌐 🔢 🐚, 🍵 💆♂ 🐍:
-
-//// tab | 💾, 🇸🇻, 🚪 🎉
-
-
-
-& ⤴️, 📂 🩺 🎧-🈸, http://127.0.0.1:8000/subapi/docs.
-
-👆 🔜 👀 🏧 🛠️ 🩺 🎧-🈸, ✅ 🕴 🚮 👍 _➡ 🛠️_, 🌐 🔽 ☑ 🎧-➡ 🔡 `/subapi`:
-
-
-
-🚥 👆 🔄 🔗 ⏮️ 🙆 2️⃣ 👩💻 🔢, 👫 🔜 👷 ☑, ↩️ 🖥 🔜 💪 💬 🔠 🎯 📱 ⚖️ 🎧-📱.
-
-### 📡 ℹ: `root_path`
-
-🕐❔ 👆 🗻 🎧-🈸 🔬 🔛, FastAPI 🔜 ✊ 💅 🔗 🗻 ➡ 🎧-🈸 ⚙️ 🛠️ ⚪️➡️ 🔫 🔧 🤙 `root_path`.
-
-👈 🌌, 🎧-🈸 🔜 💭 ⚙️ 👈 ➡ 🔡 🩺 🎚.
-
-& 🎧-🈸 💪 ✔️ 🚮 👍 📌 🎧-🈸 & 🌐 🔜 👷 ☑, ↩️ FastAPI 🍵 🌐 👉 `root_path`Ⓜ 🔁.
-
-👆 🔜 💡 🌅 🔃 `root_path` & ❔ ⚙️ ⚫️ 🎯 📄 🔃 [⛅ 🗳](behind-a-proxy.md){.internal-link target=_blank}.
diff --git a/docs/em/docs/advanced/templates.md b/docs/em/docs/advanced/templates.md
deleted file mode 100644
index 2e8f562280..0000000000
--- a/docs/em/docs/advanced/templates.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# 📄
-
-👆 💪 ⚙️ 🙆 📄 🚒 👆 💚 ⏮️ **FastAPI**.
-
-⚠ ⚒ Jinja2️⃣, 🎏 1️⃣ ⚙️ 🏺 & 🎏 🧰.
-
-📤 🚙 🔗 ⚫️ 💪 👈 👆 💪 ⚙️ 🔗 👆 **FastAPI** 🈸 (🚚 💃).
-
-## ❎ 🔗
-
-❎ `jinja2`:
-
-
-
-👆 💪 🆎 📧 🔢 📦, & 📨 👫:
-
-
-
-& 👆 **FastAPI** 🈸 ⏮️ *️⃣ 🔜 📨 🔙:
-
-
-
-👆 💪 📨 (& 📨) 📚 📧:
-
-
-
-& 🌐 👫 🔜 ⚙️ 🎏 *️⃣ 🔗.
-
-## ⚙️ `Depends` & 🎏
-
-*️⃣ 🔗 👆 💪 🗄 ⚪️➡️ `fastapi` & ⚙️:
-
-* `Depends`
-* `Security`
-* `Cookie`
-* `Header`
-* `Path`
-* `Query`
-
-👫 👷 🎏 🌌 🎏 FastAPI 🔗/*➡ 🛠️*:
-
-{* ../../docs_src/websockets/tutorial002.py hl[66:77,76:91] *}
-
-/// info
-
-👉 *️⃣ ⚫️ 🚫 🤙 ⚒ 🔑 🤚 `HTTPException`, ↩️ 👥 🤚 `WebSocketException`.
-
-👆 💪 ⚙️ 📪 📟 ⚪️➡️ ☑ 📟 🔬 🔧.
-
-///
-
-### 🔄 *️⃣ ⏮️ 🔗
-
-🚥 👆 📁 📛 `main.py`, 🏃 👆 🈸 ⏮️:
-
-
-
-## 🚚 🔀 & 💗 👩💻
-
-🕐❔ *️⃣ 🔗 📪, `await websocket.receive_text()` 🔜 🤚 `WebSocketDisconnect` ⚠, ❔ 👆 💪 ⤴️ ✊ & 🍵 💖 👉 🖼.
-
-{* ../../docs_src/websockets/tutorial003.py hl[81:83] *}
-
-🔄 ⚫️ 👅:
-
-* 📂 📱 ⏮️ 📚 🖥 📑.
-* ✍ 📧 ⚪️➡️ 👫.
-* ⤴️ 🔐 1️⃣ 📑.
-
-👈 🔜 🤚 `WebSocketDisconnect` ⚠, & 🌐 🎏 👩💻 🔜 📨 📧 💖:
-
-```
-Client #1596980209979 left the chat
-```
-
-/// tip
-
-📱 🔛 ⭐ & 🙅 🖼 🎦 ❔ 🍵 & 📻 📧 📚 *️⃣ 🔗.
-
-✋️ ✔️ 🤯 👈, 🌐 🍵 💾, 👁 📇, ⚫️ 🔜 🕴 👷 ⏪ 🛠️ 🏃, & 🔜 🕴 👷 ⏮️ 👁 🛠️.
-
-🚥 👆 💪 🕳 ⏩ 🛠️ ⏮️ FastAPI ✋️ 👈 🌖 🏋️, 🐕🦺 ✳, ✳ ⚖️ 🎏, ✅ 🗜/📻.
-
-///
-
-## 🌅 ℹ
-
-💡 🌅 🔃 🎛, ✅ 💃 🧾:
-
-* `WebSocket` 🎓.
-* 🎓-⚓️ *️⃣ 🚚.
diff --git a/docs/em/docs/advanced/wsgi.md b/docs/em/docs/advanced/wsgi.md
deleted file mode 100644
index d923347d5a..0000000000
--- a/docs/em/docs/advanced/wsgi.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# ✅ 🇨🇻 - 🏺, ✳, 🎏
-
-👆 💪 🗻 🇨🇻 🈸 👆 👀 ⏮️ [🎧 🈸 - 🗻](sub-applications.md){.internal-link target=_blank}, [⛅ 🗳](behind-a-proxy.md){.internal-link target=_blank}.
-
-👈, 👆 💪 ⚙️ `WSGIMiddleware` & ⚙️ ⚫️ 🎁 👆 🇨🇻 🈸, 🖼, 🏺, ✳, ♒️.
-
-## ⚙️ `WSGIMiddleware`
-
-👆 💪 🗄 `WSGIMiddleware`.
-
-⤴️ 🎁 🇨🇻 (✅ 🏺) 📱 ⏮️ 🛠️.
-
-& ⤴️ 🗻 👈 🔽 ➡.
-
-{* ../../docs_src/wsgi/tutorial001.py hl[2:3,22] *}
-
-## ✅ ⚫️
-
-🔜, 🔠 📨 🔽 ➡ `/v1/` 🔜 🍵 🏺 🈸.
-
-& 🎂 🔜 🍵 **FastAPI**.
-
-🚥 👆 🏃 ⚫️ ⏮️ Uvicorn & 🚶 http://localhost:8000/v1/ 👆 🔜 👀 📨 ⚪️➡️ 🏺:
-
-```txt
-Hello, World from Flask!
-```
-
-& 🚥 👆 🚶 http://localhost:8000/v2 👆 🔜 👀 📨 ⚪️➡️ FastAPI:
-
-```JSON
-{
- "message": "Hello World"
-}
-```
diff --git a/docs/em/docs/alternatives.md b/docs/em/docs/alternatives.md
deleted file mode 100644
index 4cbac7539f..0000000000
--- a/docs/em/docs/alternatives.md
+++ /dev/null
@@ -1,485 +0,0 @@
-# 🎛, 🌈 & 🔺
-
-⚫️❔ 😮 **FastAPI**, ❔ ⚫️ 🔬 🎏 🎛 & ⚫️❔ ⚫️ 🇭🇲 ⚪️➡️ 👫.
-
-## 🎶
-
-**FastAPI** 🚫🔜 🔀 🚥 🚫 ⏮️ 👷 🎏.
-
-📤 ✔️ 📚 🧰 ✍ ⏭ 👈 ✔️ ℹ 😮 🚮 🏗.
-
-👤 ✔️ ❎ 🏗 🆕 🛠️ 📚 1️⃣2️⃣🗓️. 🥇 👤 🔄 ❎ 🌐 ⚒ 📔 **FastAPI** ⚙️ 📚 🎏 🛠️, 🔌-🔌, & 🧰.
-
-✋️ ☝, 📤 🙅♂ 🎏 🎛 🌘 🏗 🕳 👈 🚚 🌐 👫 ⚒, ✊ 🏆 💭 ⚪️➡️ ⏮️ 🧰, & 🌀 👫 🏆 🌌 💪, ⚙️ 🇪🇸 ⚒ 👈 ➖🚫 💪 ⏭ (🐍 3️⃣.6️⃣ ➕ 🆎 🔑).
-
-## ⏮️ 🧰
-
-### ✳
-
-⚫️ 🌅 🌟 🐍 🛠️ & 🛎 🕴. ⚫️ ⚙️ 🏗 ⚙️ 💖 👱📔.
-
-⚫️ 📶 😆 🔗 ⏮️ 🔗 💽 (💖 ✳ ⚖️ ✳),, ✔️ ☁ 💽 (💖 🗄, ✳, 👸, ♒️) 👑 🏪 🚒 🚫 📶 ⏩.
-
-⚫️ ✍ 🏗 🕸 👩💻, 🚫 ✍ 🔗 ⚙️ 🏛 🕸 (💖 😥, Vue.js & 📐) ⚖️ 🎏 ⚙️ (💖 ☁ 📳) 🔗 ⏮️ ⚫️.
-
-### ✳ 🎂 🛠️
-
-✳ 🎂 🛠️ ✍ 🗜 🧰 🏗 🕸 🔗 ⚙️ ✳ 🔘, 📉 🚮 🛠️ 🛠️.
-
-⚫️ ⚙️ 📚 🏢 ✅ 🦎, 🟥 👒 & 🎟.
-
-⚫️ 🕐 🥇 🖼 **🏧 🛠️ 🧾**, & 👉 🎯 🕐 🥇 💭 👈 😮 "🔎" **FastAPI**.
-
-/// note
-
-✳ 🎂 🛠️ ✍ ✡ 🇺🇸🏛. 🎏 👼 💃 & Uvicorn, 🔛 ❔ **FastAPI** ⚓️.
-
-///
-
-/// check | 😮 **FastAPI**
-
-✔️ 🏧 🛠️ 🧾 🕸 👩💻 🔢.
-
-///
-
-### 🏺
-
-🏺 "🕸", ⚫️ 🚫 🔌 💽 🛠️ 🚫 📚 👜 👈 👟 🔢 ✳.
-
-👉 🦁 & 💪 ✔ 🔨 👜 💖 ⚙️ ☁ 💽 👑 💽 💾 ⚙️.
-
-⚫️ 📶 🙅, ⚫️ 📶 🏋️ 💡, 👐 🧾 🤚 🙁 📡 ☝.
-
-⚫️ 🛎 ⚙️ 🎏 🈸 👈 🚫 🎯 💪 💽, 👩💻 🧾, ⚖️ 🙆 📚 ⚒ 👈 👟 🏤-🏗 ✳. 👐 📚 👫 ⚒ 💪 🚮 ⏮️ 🔌-🔌.
-
-👉 ⚖ 🍕, & ➖ "🕸" 👈 💪 ↔ 📔 ⚫️❔ ⚫️❔ 💪 🔑 ⚒ 👈 👤 💚 🚧.
-
-👐 🦁 🏺, ⚫️ 😑 💖 👍 🏏 🏗 🔗. ⏭ 👜 🔎 "✳ 🎂 🛠️" 🏺.
-
-/// check | 😮 **FastAPI**
-
-◾-🛠️. ⚒ ⚫️ ⏩ 🌀 & 🏏 🧰 & 🍕 💪.
-
-✔️ 🙅 & ⏩ ⚙️ 🕹 ⚙️.
-
-///
-
-### 📨
-
-**FastAPI** 🚫 🤙 🎛 **📨**. 👫 ↔ 📶 🎏.
-
-⚫️ 🔜 🤙 ⚠ ⚙️ 📨 *🔘* FastAPI 🈸.
-
-✋️, FastAPI 🤚 🌈 ⚪️➡️ 📨.
-
-**📨** 🗃 *🔗* ⏮️ 🔗 (👩💻), ⏪ **FastAPI** 🗃 *🏗* 🔗 (💽).
-
-👫, 🌖 ⚖️ 🌘, 🔄 🔚, 🔗 🔠 🎏.
-
-📨 ✔️ 📶 🙅 & 🏋️ 🔧, ⚫️ 📶 ⏩ ⚙️, ⏮️ 🤔 🔢. ✋️ 🎏 🕰, ⚫️ 📶 🏋️ & 🛃.
-
-👈 ⚫️❔, 💬 🛂 🕸:
-
-> 📨 1️⃣ 🏆 ⏬ 🐍 📦 🌐 🕰
-
-🌌 👆 ⚙️ ⚫️ 📶 🙅. 🖼, `GET` 📨, 👆 🔜 ✍:
-
-```Python
-response = requests.get("http://example.com/some/url")
-```
-
-FastAPI 😑 🛠️ *➡ 🛠️* 💪 👀 💖:
-
-```Python hl_lines="1"
-@app.get("/some/url")
-def read_url():
- return {"message": "Hello World"}
-```
-
-👀 🔀 `requests.get(...)` & `@app.get(...)`.
-
-/// check | 😮 **FastAPI**
-
-* ✔️ 🙅 & 🏋️ 🛠️.
-* ⚙️ 🇺🇸🔍 👩🔬 📛 (🛠️) 🔗, 🎯 & 🏋️ 🌌.
-* ✔️ 🤔 🔢, ✋️ 🏋️ 🛃.
-
-///
-
-### 🦁 / 🗄
-
-👑 ⚒ 👤 💚 ⚪️➡️ ✳ 🎂 🛠️ 🏧 🛠️ 🧾.
-
-⤴️ 👤 🔎 👈 📤 🐩 📄 🔗, ⚙️ 🎻 (⚖️ 📁, ↔ 🎻) 🤙 🦁.
-
-& 📤 🕸 👩💻 🔢 🦁 🛠️ ⏪ ✍. , 💆♂ 💪 🏗 🦁 🧾 🛠️ 🔜 ✔ ⚙️ 👉 🕸 👩💻 🔢 🔁.
-
-☝, 🦁 👐 💾 🏛, 📁 🗄.
-
-👈 ⚫️❔ 🕐❔ 💬 🔃 ⏬ 2️⃣.0️⃣ ⚫️ ⚠ 💬 "🦁", & ⏬ 3️⃣ ➕ "🗄".
-
-/// check | 😮 **FastAPI**
-
-🛠️ & ⚙️ 📂 🐩 🛠️ 🔧, ↩️ 🛃 🔗.
-
- & 🛠️ 🐩-⚓️ 👩💻 🔢 🧰:
-
-* 🦁 🎚
-* 📄
-
-👫 2️⃣ 👐 ➖ 📶 🌟 & ⚖, ✋️ 🔨 ⏩ 🔎, 👆 💪 🔎 💯 🌖 🎛 👩💻 🔢 🗄 (👈 👆 💪 ⚙️ ⏮️ **FastAPI**).
-
-///
-
-### 🏺 🎂 🛠️
-
-📤 📚 🏺 🎂 🛠️, ✋️ ⏮️ 💰 🕰 & 👷 🔘 🔬 👫, 👤 🔎 👈 📚 😞 ⚖️ 🚫, ⏮️ 📚 🧍 ❔ 👈 ⚒ 👫 🙃.
-
-### 🍭
-
-1️⃣ 👑 ⚒ 💪 🛠️ ⚙️ 📊 "🛠️" ❔ ✊ 📊 ⚪️➡️ 📟 (🐍) & 🏭 ⚫️ 🔘 🕳 👈 💪 📨 🔘 🕸. 🖼, 🏭 🎚 ⚗ 📊 ⚪️➡️ 💽 🔘 🎻 🎚. 🏭 `datetime` 🎚 🔘 🎻, ♒️.
-
-➕1️⃣ 🦏 ⚒ 💚 🔗 💽 🔬, ⚒ 💭 👈 💽 ☑, 🤝 🎯 🔢. 🖼, 👈 🏑 `int`, & 🚫 🎲 🎻. 👉 ✴️ ⚠ 📨 💽.
-
-🍵 💽 🔬 ⚙️, 👆 🔜 ✔️ 🌐 ✅ ✋, 📟.
-
-👫 ⚒ ⚫️❔ 🍭 🏗 🚚. ⚫️ 👑 🗃, & 👤 ✔️ ⚙️ ⚫️ 📚 ⏭.
-
-✋️ ⚫️ ✍ ⏭ 📤 🔀 🐍 🆎 🔑. , 🔬 🔠 🔗 👆 💪 ⚙️ 🎯 🇨🇻 & 🎓 🚚 🍭.
-
-/// check | 😮 **FastAPI**
-
-⚙️ 📟 🔬 "🔗" 👈 🚚 💽 🆎 & 🔬, 🔁.
-
-///
-
-### Webarg
-
-➕1️⃣ 🦏 ⚒ ✔ 🔗 ✍ 📊 ⚪️➡️ 📨 📨.
-
-Webarg 🧰 👈 ⚒ 🚚 👈 🔛 🔝 📚 🛠️, 🔌 🏺.
-
-⚫️ ⚙️ 🍭 🔘 💽 🔬. & ⚫️ ✍ 🎏 👩💻.
-
-⚫️ 👑 🧰 & 👤 ✔️ ⚙️ ⚫️ 📚 💁♂️, ⏭ ✔️ **FastAPI**.
-
-/// info
-
-Webarg ✍ 🎏 🍭 👩💻.
-
-///
-
-/// check | 😮 **FastAPI**
-
-✔️ 🏧 🔬 📨 📨 💽.
-
-///
-
-### APISpec
-
-🍭 & Webarg 🚚 🔬, ✍ & 🛠️ 🔌-🔌.
-
-✋️ 🧾 ❌. ⤴️ APISpec ✍.
-
-⚫️ 🔌-📚 🛠️ (& 📤 🔌-💃 💁♂️).
-
-🌌 ⚫️ 👷 👈 👆 ✍ 🔑 🔗 ⚙️ 📁 📁 🔘 #️⃣ 🔠 🔢 🚚 🛣.
-
-& ⚫️ 🏗 🗄 🔗.
-
-👈 ❔ ⚫️ 👷 🏺, 💃, 🆘, ♒️.
-
-✋️ ⤴️, 👥 ✔️ 🔄 ⚠ ✔️ ◾-❕, 🔘 🐍 🎻 (🦏 📁).
-
-👨🎨 💪 🚫 ℹ 🌅 ⏮️ 👈. & 🚥 👥 🔀 🔢 ⚖️ 🍭 🔗 & 💭 🔀 👈 📁#️⃣, 🏗 🔗 🔜 ❌.
-
-/// info
-
-APISpec ✍ 🎏 🍭 👩💻.
-
-///
-
-/// check | 😮 **FastAPI**
-
-🐕🦺 📂 🐩 🛠️, 🗄.
-
-///
-
-### 🏺-Apispec
-
-⚫️ 🏺 🔌 -, 👈 👔 👯♂️ Webarg, 🍭 & APISpec.
-
-⚫️ ⚙️ ℹ ⚪️➡️ Webarg & 🍭 🔁 🏗 🗄 🔗, ⚙️ APISpec.
-
-⚫️ 👑 🧰, 📶 🔽-📈. ⚫️ 🔜 🌌 🌖 🌟 🌘 📚 🏺 🔌-🔌 👅 📤. ⚫️ 💪 ↩️ 🚮 🧾 ➖ 💁♂️ 🩲 & 📝.
-
-👉 ❎ ✔️ ✍ 📁 (➕1️⃣ ❕) 🔘 🐍 ✍.
-
-👉 🌀 🏺, 🏺-Apispec ⏮️ 🍭 & Webarg 👇 💕 👩💻 📚 ⏭ 🏗 **FastAPI**.
-
-⚙️ ⚫️ ↘️ 🏗 📚 🏺 🌕-📚 🚂. 👫 👑 📚 👤 (& 📚 🔢 🏉) ✔️ ⚙️ 🆙 🔜:
-
-* https://github.com/tiangolo/full-stack
-* https://github.com/tiangolo/full-stack-flask-couchbase
-* https://github.com/tiangolo/full-stack-flask-couchdb
-
-& 👫 🎏 🌕-📚 🚂 🧢 [**FastAPI** 🏗 🚂](project-generation.md){.internal-link target=_blank}.
-
-/// info
-
-🏺-Apispec ✍ 🎏 🍭 👩💻.
-
-///
-
-/// check | 😮 **FastAPI**
-
-🏗 🗄 🔗 🔁, ⚪️➡️ 🎏 📟 👈 🔬 🛠️ & 🔬.
-
-///
-
-### NestJS (& 📐)
-
-👉 ➖🚫 🚫 🐍, NestJS 🕸 (📕) ✳ 🛠️ 😮 📐.
-
-⚫️ 🏆 🕳 🙁 🎏 ⚫️❔ 💪 🔨 ⏮️ 🏺-Apispec.
-
-⚫️ ✔️ 🛠️ 🔗 💉 ⚙️, 😮 📐 2️⃣. ⚫️ 🚚 🏤-® "💉" (💖 🌐 🎏 🔗 💉 ⚙️ 👤 💭),, ⚫️ 🚮 🎭 & 📟 🔁.
-
-🔢 🔬 ⏮️ 📕 🆎 (🎏 🐍 🆎 🔑), 👨🎨 🐕🦺 👍.
-
-✋️ 📕 📊 🚫 🛡 ⏮️ 📹 🕸, ⚫️ 🚫🔜 ⚓️ 🔛 🆎 🔬 🔬, 🛠️ & 🧾 🎏 🕰. ↩️ 👉 & 🔧 🚫, 🤚 🔬, 🛠️ & 🏧 🔗 ⚡, ⚫️ 💪 🚮 👨🎨 📚 🥉. , ⚫️ ▶️️ 🔁.
-
-⚫️ 💪 🚫 🍵 🔁 🏷 📶 👍. , 🚥 🎻 💪 📨 🎻 🎚 👈 ✔️ 🔘 🏑 👈 🔄 🐦 🎻 🎚, ⚫️ 🚫🔜 ☑ 📄 & ✔.
-
-/// check | 😮 **FastAPI**
-
-⚙️ 🐍 🆎 ✔️ 👑 👨🎨 🐕🦺.
-
-✔️ 🏋️ 🔗 💉 ⚙️. 🔎 🌌 📉 📟 🔁.
-
-///
-
-### 🤣
-
-⚫️ 🕐 🥇 📶 ⏩ 🐍 🛠️ ⚓️ 🔛 `asyncio`. ⚫️ ⚒ 📶 🎏 🏺.
-
-/// note | 📡 ℹ
-
-⚫️ ⚙️ `uvloop` ↩️ 🔢 🐍 `asyncio` ➰. 👈 ⚫️❔ ⚒ ⚫️ ⏩.
-
-⚫️ 🎯 😮 Uvicorn & 💃, 👈 ⏳ ⏩ 🌘 🤣 📂 📇.
-
-///
-
-/// check | 😮 **FastAPI**
-
-🔎 🌌 ✔️ 😜 🎭.
-
-👈 ⚫️❔ **FastAPI** ⚓️ 🔛 💃, ⚫️ ⏩ 🛠️ 💪 (💯 🥉-🥳 📇).
-
-///
-
-### 🦅
-
-🦅 ➕1️⃣ ↕ 🎭 🐍 🛠️, ⚫️ 🔧 ⭐, & 👷 🏛 🎏 🛠️ 💖 🤗.
-
-⚫️ 🏗 ✔️ 🔢 👈 📨 2️⃣ 🔢, 1️⃣ "📨" & 1️⃣ "📨". ⤴️ 👆 "✍" 🍕 ⚪️➡️ 📨, & "✍" 🍕 📨. ↩️ 👉 🔧, ⚫️ 🚫 💪 📣 📨 🔢 & 💪 ⏮️ 🐩 🐍 🆎 🔑 🔢 🔢.
-
-, 💽 🔬, 🛠️, & 🧾, ✔️ ⌛ 📟, 🚫 🔁. ⚖️ 👫 ✔️ 🛠️ 🛠️ 🔛 🔝 🦅, 💖 🤗. 👉 🎏 🔺 🔨 🎏 🛠️ 👈 😮 🦅 🔧, ✔️ 1️⃣ 📨 🎚 & 1️⃣ 📨 🎚 🔢.
-
-/// check | 😮 **FastAPI**
-
-🔎 🌌 🤚 👑 🎭.
-
-⤴️ ⏮️ 🤗 (🤗 ⚓️ 🔛 🦅) 😮 **FastAPI** 📣 `response` 🔢 🔢.
-
-👐 FastAPI ⚫️ 📦, & ⚙️ ✴️ ⚒ 🎚, 🍪, & 🎛 👔 📟.
-
-///
-
-### ♨
-
-👤 🔎 ♨ 🥇 ▶️ 🏗 **FastAPI**. & ⚫️ ✔️ 🎏 💭:
-
-* ⚓️ 🔛 🐍 🆎 🔑.
-* 🔬 & 🧾 ⚪️➡️ 👫 🆎.
-* 🔗 💉 ⚙️.
-
-⚫️ 🚫 ⚙️ 💽 🔬, 🛠️ & 🧾 🥉-🥳 🗃 💖 Pydantic, ⚫️ ✔️ 🚮 👍. , 👫 💽 🆎 🔑 🔜 🚫 ♻ 💪.
-
-⚫️ 🚚 🐥 🍖 🌅 🔁 📳. & ⚫️ ⚓️ 🔛 🇨🇻 (↩️ 🔫), ⚫️ 🚫 🔧 ✊ 📈 ↕-🎭 🚚 🧰 💖 Uvicorn, 💃 & 🤣.
-
-🔗 💉 ⚙️ 🚚 🏤-® 🔗 & 🔗 ❎ 🧢 🔛 📣 🆎. , ⚫️ 🚫 💪 📣 🌅 🌘 1️⃣ "🦲" 👈 🚚 🎯 🆎.
-
-🛣 📣 👁 🥉, ⚙️ 🔢 📣 🎏 🥉 (↩️ ⚙️ 👨🎨 👈 💪 🥉 ▶️️ 🔛 🔝 🔢 👈 🍵 🔗). 👉 🔐 ❔ ✳ 🔨 ⚫️ 🌘 ❔ 🏺 (& 💃) 🔨 ⚫️. ⚫️ 🎏 📟 👜 👈 📶 😆 🔗.
-
-/// check | 😮 **FastAPI**
-
-🔬 ➕ 🔬 💽 🆎 ⚙️ "🔢" 💲 🏷 🔢. 👉 📉 👨🎨 🐕🦺, & ⚫️ 🚫 💪 Pydantic ⏭.
-
-👉 🤙 😮 🛠️ 🍕 Pydantic, 🐕🦺 🎏 🔬 📄 👗 (🌐 👉 🛠️ 🔜 ⏪ 💪 Pydantic).
-
-///
-
-### 🤗
-
-🤗 🕐 🥇 🛠️ 🛠️ 📄 🛠️ 🔢 🆎 ⚙️ 🐍 🆎 🔑. 👉 👑 💭 👈 😮 🎏 🧰 🎏.
-
-⚫️ ⚙️ 🛃 🆎 🚮 📄 ↩️ 🐩 🐍 🆎, ✋️ ⚫️ 🦏 🔁 ⏩.
-
-⚫️ 🕐 🥇 🛠️ 🏗 🛃 🔗 📣 🎂 🛠️ 🎻.
-
-⚫️ 🚫 ⚓️ 🔛 🐩 💖 🗄 & 🎻 🔗. ⚫️ 🚫🔜 🎯 🛠️ ⚫️ ⏮️ 🎏 🧰, 💖 🦁 🎚. ✋️ 🔄, ⚫️ 📶 💡 💭.
-
-⚫️ ✔️ 😌, ⭐ ⚒: ⚙️ 🎏 🛠️, ⚫️ 💪 ✍ 🔗 & 🇳🇨.
-
-⚫️ ⚓️ 🔛 ⏮️ 🐩 🔁 🐍 🕸 🛠️ (🇨🇻), ⚫️ 💪 🚫 🍵 *️⃣ & 🎏 👜, 👐 ⚫️ ✔️ ↕ 🎭 💁♂️.
-
-/// info
-
-🤗 ✍ ✡ 🗄, 🎏 👼 `isort`, 👑 🧰 🔁 😇 🗄 🐍 📁.
-
-///
-
-/// check | 💭 😮 **FastAPI**
-
-🤗 😮 🍕 APIStar, & 1️⃣ 🧰 👤 🔎 🏆 👍, 🌟 APIStar.
-
-🤗 ℹ 😍 **FastAPI** ⚙️ 🐍 🆎 🔑 📣 🔢, & 🏗 🔗 ⚖ 🛠️ 🔁.
-
-🤗 😮 **FastAPI** 📣 `response` 🔢 🔢 ⚒ 🎚 & 🍪.
-
-///
-
-### APIStar (<= 0️⃣.5️⃣)
-
-▶️️ ⏭ 🤔 🏗 **FastAPI** 👤 🔎 **APIStar** 💽. ⚫️ ✔️ 🌖 🌐 👤 👀 & ✔️ 👑 🔧.
-
-⚫️ 🕐 🥇 🛠️ 🛠️ ⚙️ 🐍 🆎 🔑 📣 🔢 & 📨 👈 👤 ⏱ 👀 (⏭ NestJS & ♨). 👤 🔎 ⚫️ 🌅 ⚖️ 🌘 🎏 🕰 🤗. ✋️ APIStar ⚙️ 🗄 🐩.
-
-⚫️ ✔️ 🏧 💽 🔬, 💽 🛠️ & 🗄 🔗 ⚡ ⚓️ 🔛 🎏 🆎 🔑 📚 🥉.
-
-💪 🔗 🔑 🚫 ⚙️ 🎏 🐍 🆎 🔑 💖 Pydantic, ⚫️ 🍖 🌅 🎏 🍭,, 👨🎨 🐕🦺 🚫🔜 👍, ✋️, APIStar 🏆 💪 🎛.
-
-⚫️ ✔️ 🏆 🎭 📇 🕰 (🕴 💥 💃).
-
-🥇, ⚫️ 🚫 ✔️ 🏧 🛠️ 🧾 🕸 🎚, ✋️ 👤 💭 👤 💪 🚮 🦁 🎚 ⚫️.
-
-⚫️ ✔️ 🔗 💉 ⚙️. ⚫️ ✔ 🏤-® 🦲, 🎏 🧰 🔬 🔛. ✋️, ⚫️ 👑 ⚒.
-
-👤 🙅 💪 ⚙️ ⚫️ 🌕 🏗, ⚫️ 🚫 ✔️ 💂♂ 🛠️,, 👤 🚫 🚫 ❎ 🌐 ⚒ 👤 ✔️ ⏮️ 🌕-📚 🚂 ⚓️ 🔛 🏺-Apispec. 👤 ✔️ 👇 📈 🏗 ✍ 🚲 📨 ❎ 👈 🛠️.
-
-✋️ ⤴️, 🏗 🎯 🔀.
-
-⚫️ 🙅♂ 📏 🛠️ 🕸 🛠️, 👼 💪 🎯 🔛 💃.
-
-🔜 APIStar ⚒ 🧰 ✔ 🗄 🔧, 🚫 🕸 🛠️.
-
-/// info
-
-APIStar ✍ ✡ 🇺🇸🏛. 🎏 👨 👈 ✍:
-
-* ✳ 🎂 🛠️
-* 💃 (❔ **FastAPI** ⚓️)
-* Uvicorn (⚙️ 💃 & **FastAPI**)
-
-///
-
-/// check | 😮 **FastAPI**
-
-🔀.
-
-💭 📣 💗 👜 (💽 🔬, 🛠️ & 🧾) ⏮️ 🎏 🐍 🆎, 👈 🎏 🕰 🚚 👑 👨🎨 🐕🦺, 🕳 👤 🤔 💎 💭.
-
- & ⏮️ 🔎 📏 🕰 🎏 🛠️ & 🔬 📚 🎏 🎛, APIStar 🏆 🎛 💪.
-
-⤴️ APIStar ⛔️ 🔀 💽 & 💃 ✍, & 🆕 👻 🏛 ✅ ⚙️. 👈 🏁 🌈 🏗 **FastAPI**.
-
-👤 🤔 **FastAPI** "🛐 👨💼" APIStar, ⏪ 📉 & 📈 ⚒, ⌨ ⚙️, & 🎏 🍕, ⚓️ 🔛 🏫 ⚪️➡️ 🌐 👉 ⏮️ 🧰.
-
-///
-
-## ⚙️ **FastAPI**
-
-### Pydantic
-
-Pydantic 🗃 🔬 💽 🔬, 🛠️ & 🧾 (⚙️ 🎻 🔗) ⚓️ 🔛 🐍 🆎 🔑.
-
-👈 ⚒ ⚫️ 📶 🏋️.
-
-⚫️ ⭐ 🍭. 👐 ⚫️ ⏩ 🌘 🍭 📇. & ⚫️ ⚓️ 🔛 🎏 🐍 🆎 🔑, 👨🎨 🐕🦺 👑.
-
-/// check | **FastAPI** ⚙️ ⚫️
-
-🍵 🌐 💽 🔬, 💽 🛠️ & 🏧 🏷 🧾 (⚓️ 🔛 🎻 🔗).
-
-**FastAPI** ⤴️ ✊ 👈 🎻 🔗 💽 & 🚮 ⚫️ 🗄, ↖️ ⚪️➡️ 🌐 🎏 👜 ⚫️ 🔨.
-
-///
-
-### 💃
-
-💃 💿 🔫 🛠️/🧰, ❔ 💯 🏗 ↕-🎭 ✳ 🐕🦺.
-
-⚫️ 📶 🙅 & 🏋️. ⚫️ 🔧 💪 🏧, & ✔️ 🔧 🦲.
-
-⚫️ ✔️:
-
-* 🤙 🎆 🎭.
-* *️⃣ 🐕🦺.
-* -🛠️ 🖥 📋.
-* 🕴 & 🤫 🎉.
-* 💯 👩💻 🏗 🔛 🇸🇲.
-* ⚜, 🗜, 🎻 📁, 🎏 📨.
-* 🎉 & 🍪 🐕🦺.
-* 1️⃣0️⃣0️⃣ 💯 💯 💰.
-* 1️⃣0️⃣0️⃣ 💯 🆎 ✍ ✍.
-* 👩❤👨 🏋️ 🔗.
-
-💃 ⏳ ⏩ 🐍 🛠️ 💯. 🕴 💥 Uvicorn, ❔ 🚫 🛠️, ✋️ 💽.
-
-💃 🚚 🌐 🔰 🕸 🕸 🛠️.
-
-✋️ ⚫️ 🚫 🚚 🏧 💽 🔬, 🛠️ ⚖️ 🧾.
-
-👈 1️⃣ 👑 👜 👈 **FastAPI** 🚮 🔛 🔝, 🌐 ⚓️ 🔛 🐍 🆎 🔑 (⚙️ Pydantic). 👈, ➕ 🔗 💉 ⚙️, 💂♂ 🚙, 🗄 🔗 ⚡, ♒️.
-
-/// note | 📡 ℹ
-
-🔫 🆕 "🐩" ➖ 🛠️ ✳ 🐚 🏉 👨🎓. ⚫️ 🚫 "🐍 🐩" (🇩🇬), 👐 👫 🛠️ 🔨 👈.
-
-👐, ⚫️ ⏪ ➖ ⚙️ "🐩" 📚 🧰. 👉 📉 📉 🛠️, 👆 💪 🎛 Uvicorn 🙆 🎏 🔫 💽 (💖 👸 ⚖️ Hypercorn), ⚖️ 👆 💪 🚮 🔫 🔗 🧰, 💖 `python-socketio`.
-
-///
-
-/// check | **FastAPI** ⚙️ ⚫️
-
-🍵 🌐 🐚 🕸 🍕. ❎ ⚒ 🔛 🔝.
-
-🎓 `FastAPI` ⚫️ 😖 🔗 ⚪️➡️ 🎓 `Starlette`.
-
-, 🕳 👈 👆 💪 ⏮️ 💃, 👆 💪 ⚫️ 🔗 ⏮️ **FastAPI**, ⚫️ 🌖 💃 🔛 💊.
-
-///
-
-### Uvicorn
-
-Uvicorn 🌩-⏩ 🔫 💽, 🏗 🔛 uvloop & httptool.
-
-⚫️ 🚫 🕸 🛠️, ✋️ 💽. 🖼, ⚫️ 🚫 🚚 🧰 🕹 ➡. 👈 🕳 👈 🛠️ 💖 💃 (⚖️ **FastAPI**) 🔜 🚚 🔛 🔝.
-
-⚫️ 👍 💽 💃 & **FastAPI**.
-
-/// check | **FastAPI** 👍 ⚫️
-
-👑 🕸 💽 🏃 **FastAPI** 🈸.
-
-👆 💪 🌀 ⚫️ ⏮️ 🐁, ✔️ 🔁 👁-🛠️ 💽.
-
-✅ 🌅 ℹ [🛠️](deployment/index.md){.internal-link target=_blank} 📄.
-
-///
-
-## 📇 & 🚅
-
-🤔, 🔬, & 👀 🔺 🖖 Uvicorn, 💃 & FastAPI, ✅ 📄 🔃 [📇](benchmarks.md){.internal-link target=_blank}.
diff --git a/docs/em/docs/async.md b/docs/em/docs/async.md
deleted file mode 100644
index cde778b0ff..0000000000
--- a/docs/em/docs/async.md
+++ /dev/null
@@ -1,442 +0,0 @@
-# 🛠️ & 🔁 / ⌛
-
-ℹ 🔃 `async def` ❕ *➡ 🛠️ 🔢* & 🖥 🔃 🔁 📟, 🛠️, & 🔁.
-
-## 🏃 ❓
-
-🆑;👩⚕️:
-
-🚥 👆 ⚙️ 🥉 🥳 🗃 👈 💬 👆 🤙 👫 ⏮️ `await`, 💖:
-
-```Python
-results = await some_library()
-```
-
-⤴️, 📣 👆 *➡ 🛠️ 🔢* ⏮️ `async def` 💖:
-
-```Python hl_lines="2"
-@app.get('/')
-async def read_results():
- results = await some_library()
- return results
-```
-
-/// note
-
-👆 💪 🕴 ⚙️ `await` 🔘 🔢 ✍ ⏮️ `async def`.
-
-///
-
----
-
-🚥 👆 ⚙️ 🥉 🥳 🗃 👈 🔗 ⏮️ 🕳 (💽, 🛠️, 📁 ⚙️, ♒️.) & 🚫 ✔️ 🐕🦺 ⚙️ `await`, (👉 ⏳ 💼 🌅 💽 🗃), ⤴️ 📣 👆 *➡ 🛠️ 🔢* 🛎, ⏮️ `def`, 💖:
-
-```Python hl_lines="2"
-@app.get('/')
-def results():
- results = some_library()
- return results
-```
-
----
-
-🚥 👆 🈸 (😫) 🚫 ✔️ 🔗 ⏮️ 🕳 🙆 & ⌛ ⚫️ 📨, ⚙️ `async def`.
-
----
-
-🚥 👆 🚫 💭, ⚙️ 😐 `def`.
-
----
-
-**🗒**: 👆 💪 🌀 `def` & `async def` 👆 *➡ 🛠️ 🔢* 🌅 👆 💪 & 🔬 🔠 1️⃣ ⚙️ 🏆 🎛 👆. FastAPI 🔜 ▶️️ 👜 ⏮️ 👫.
-
-😆, 🙆 💼 🔛, FastAPI 🔜 👷 🔁 & 📶 ⏩.
-
-✋️ 📄 📶 🔛, ⚫️ 🔜 💪 🎭 🛠️.
-
-## 📡 ℹ
-
-🏛 ⏬ 🐍 ✔️ 🐕🦺 **"🔁 📟"** ⚙️ 🕳 🤙 **"🔁"**, ⏮️ **`async` & `await`** ❕.
-
-➡️ 👀 👈 🔤 🍕 📄 🔛:
-
-* **🔁 📟**
-* **`async` & `await`**
-* **🔁**
-
-## 🔁 📟
-
-🔁 📟 ⛓ 👈 🇪🇸 👶 ✔️ 🌌 💬 💻 / 📋 👶 👈 ☝ 📟, ⚫️ 👶 🔜 ✔️ ⌛ *🕳 🙆* 🏁 👱 🙆. ➡️ 💬 👈 *🕳 🙆* 🤙 "🐌-📁" 👶.
-
-, ⏮️ 👈 🕰, 💻 💪 🚶 & 🎏 👷, ⏪ "🐌-📁" 👶 🏁.
-
-⤴️ 💻 / 📋 👶 🔜 👟 🔙 🔠 🕰 ⚫️ ✔️ 🤞 ↩️ ⚫️ ⌛ 🔄, ⚖️ 🕐❔ ⚫️ 👶 🏁 🌐 👷 ⚫️ ✔️ 👈 ☝. & ⚫️ 👶 🔜 👀 🚥 🙆 📋 ⚫️ ⌛ ✔️ ⏪ 🏁, 🤸 ⚫️❔ ⚫️ ✔️.
-
-⏭, ⚫️ 👶 ✊ 🥇 📋 🏁 (➡️ 💬, 👆 "🐌-📁" 👶) & 😣 ⚫️❔ ⚫️ ✔️ ⏮️ ⚫️.
-
-👈 "⌛ 🕳 🙆" 🛎 🔗 👤/🅾 🛠️ 👈 📶 "🐌" (🔬 🚅 🕹 & 💾 💾), 💖 ⌛:
-
-* 📊 ⚪️➡️ 👩💻 📨 🔘 🕸
-* 📊 📨 👆 📋 📨 👩💻 🔘 🕸
-* 🎚 📁 💾 ✍ ⚙️ & 🤝 👆 📋
-* 🎚 👆 📋 🤝 ⚙️ ✍ 💾
-* 🛰 🛠️ 🛠️
-* 💽 🛠️ 🏁
-* 💽 🔢 📨 🏁
-* ♒️.
-
-🛠️ 🕰 🍴 ✴️ ⌛ 👤/🅾 🛠️, 👫 🤙 👫 "👤/🅾 🔗" 🛠️.
-
-⚫️ 🤙 "🔁" ↩️ 💻 / 📋 🚫 ✔️ "🔁" ⏮️ 🐌 📋, ⌛ ☑ 🙍 👈 📋 🏁, ⏪ 🔨 🕳, 💪 ✊ 📋 🏁 & 😣 👷.
-
-↩️ 👈, 💆♂ "🔁" ⚙️, 🕐 🏁, 📋 💪 ⌛ ⏸ 🐥 👄 (⏲) 💻 / 📋 🏁 ⚫️❔ ⚫️ 🚶, & ⤴️ 👟 🔙 ✊ 🏁 & 😣 👷 ⏮️ 👫.
-
-"🔁" (👽 "🔁") 👫 🛎 ⚙️ ⚖ "🔁", ↩️ 💻 / 📋 ⏩ 🌐 📶 🔁 ⏭ 🔀 🎏 📋, 🚥 👈 🔁 🔌 ⌛.
-
-### 🛠️ & 🍔
-
-👉 💭 **🔁** 📟 🔬 🔛 🕣 🤙 **"🛠️"**. ⚫️ 🎏 ⚪️➡️ **"🔁"**.
-
-**🛠️** & **🔁** 👯♂️ 🔗 "🎏 👜 😥 🌅 ⚖️ 🌘 🎏 🕰".
-
-✋️ ℹ 🖖 *🛠️* & *🔁* 🎏.
-
-👀 🔺, 🌈 📄 📖 🔃 🍔:
-
-### 🛠️ 🍔
-
-👆 🚶 ⏮️ 👆 🥰 🤚 ⏩ 🥕, 👆 🧍 ⏸ ⏪ 🏧 ✊ ✔ ⚪️➡️ 👫👫 🚪 👆. 👶
-
-
-
-⤴️ ⚫️ 👆 🔄, 👆 🥉 👆 ✔ 2️⃣ 📶 🎀 🍔 👆 🥰 & 👆. 👶 👶
-
-
-
-🏧 💬 🕳 🍳 👨🍳 👫 💭 👫 ✔️ 🏗 👆 🍔 (✋️ 👫 ⏳ 🏗 🕐 ⏮️ 👩💻).
-
-
-
-👆 💸. 👶
-
-🏧 🤝 👆 🔢 👆 🔄.
-
-
-
-⏪ 👆 ⌛, 👆 🚶 ⏮️ 👆 🥰 & ⚒ 🏓, 👆 🧎 & 💬 ⏮️ 👆 🥰 📏 🕰 (👆 🍔 📶 🎀 & ✊ 🕰 🏗).
-
-👆 🏖 🏓 ⏮️ 👆 🥰, ⏪ 👆 ⌛ 🍔, 👆 💪 💸 👈 🕰 😮 ❔ 👌, 🐨 & 🙃 👆 🥰 👶 👶 👶.
-
-
-
-⏪ ⌛ & 💬 👆 🥰, ⚪️➡️ 🕰 🕰, 👆 ✅ 🔢 🖥 🔛 ⏲ 👀 🚥 ⚫️ 👆 🔄 ⏪.
-
-⤴️ ☝, ⚫️ 😒 👆 🔄. 👆 🚶 ⏲, 🤚 👆 🍔 & 👟 🔙 🏓.
-
-
-
-👆 & 👆 🥰 🍴 🍔 & ✔️ 👌 🕰. 👶
-
-
-
-/// info
-
-🌹 🖼 👯 🍏. 👶
-
-///
-
----
-
-🌈 👆 💻 / 📋 👶 👈 📖.
-
-⏪ 👆 ⏸, 👆 ⛽ 👶, ⌛ 👆 🔄, 🚫 🔨 🕳 📶 "😌". ✋️ ⏸ ⏩ ↩️ 🏧 🕴 ✊ ✔ (🚫 🏗 👫), 👈 👌.
-
-⤴️, 🕐❔ ⚫️ 👆 🔄, 👆 ☑ "😌" 👷, 👆 🛠️ 🍣, 💭 ⚫️❔ 👆 💚, 🤚 👆 🥰 ⚒, 💸, ✅ 👈 👆 🤝 ☑ 💵 ⚖️ 💳, ✅ 👈 👆 🈚 ☑, ✅ 👈 ✔ ✔️ ☑ 🏬, ♒️.
-
-✋️ ⤴️, ✋️ 👆 🚫 ✔️ 👆 🍔, 👆 👷 ⏮️ 🏧 "🔛 ⏸" ⏸, ↩️ 👆 ✔️ ⌛ 👶 👆 🍔 🔜.
-
-✋️ 👆 🚶 ↖️ ⚪️➡️ ⏲ & 🧎 🏓 ⏮️ 🔢 👆 🔄, 👆 💪 🎛 👶 👆 🙋 👆 🥰, & "👷" 👶 👶 🔛 👈. ⤴️ 👆 🔄 🔨 🕳 📶 "😌" 😏 ⏮️ 👆 🥰 👶.
-
-⤴️ 🏧 👶 💬 "👤 🏁 ⏮️ 🔨 🍔" 🚮 👆 🔢 🔛 ⏲ 🖥, ✋️ 👆 🚫 🦘 💖 😜 ⏪ 🕐❔ 🖥 🔢 🔀 👆 🔄 🔢. 👆 💭 🙅♂ 1️⃣ 🔜 📎 👆 🍔 ↩️ 👆 ✔️ 🔢 👆 🔄, & 👫 ✔️ 👫.
-
-👆 ⌛ 👆 🥰 🏁 📖 (🏁 ⏮️ 👷 👶 / 📋 ➖ 🛠️ 👶), 😀 🖐 & 💬 👈 👆 🔜 🍔 ⏸.
-
-⤴️ 👆 🚶 ⏲ 👶, ▶️ 📋 👈 🔜 🏁 👶, ⚒ 🍔, 💬 👏 & ✊ 👫 🏓. 👈 🏁 👈 🔁 / 📋 🔗 ⏮️ ⏲ ⏹. 👈 🔄, ✍ 🆕 📋, "🍴 🍔" 👶 👶, ✋️ ⏮️ 1️⃣ "🤚 🍔" 🏁 ⏹.
-
-### 🔗 🍔
-
-🔜 ➡️ 🌈 👫 ➖🚫 🚫 "🛠️ 🍔", ✋️ "🔗 🍔".
-
-👆 🚶 ⏮️ 👆 🥰 🤚 🔗 ⏩ 🥕.
-
-👆 🧍 ⏸ ⏪ 📚 (➡️ 💬 8️⃣) 🏧 👈 🎏 🕰 🍳 ✊ ✔ ⚪️➡️ 👫👫 🚪 👆.
-
-👱 ⏭ 👆 ⌛ 👫 🍔 🔜 ⏭ 🍂 ⏲ ↩️ 🔠 8️⃣ 🏧 🚶 & 🏗 🍔 ▶️️ ↖️ ⏭ 💆♂ ⏭ ✔.
-
-
-
-⤴️ ⚫️ 😒 👆 🔄, 👆 🥉 👆 ✔ 2️⃣ 📶 🎀 🍔 👆 🥰 & 👆.
-
-👆 💸 👶.
-
-
-
-🏧 🚶 👨🍳.
-
-👆 ⌛, 🧍 🚪 ⏲ 👶, 👈 🙅♂ 1️⃣ 🙆 ✊ 👆 🍔 ⏭ 👆, 📤 🙅♂ 🔢 🔄.
-
-
-
-👆 & 👆 🥰 😩 🚫 ➡️ 🙆 🤚 🚪 👆 & ✊ 👆 🍔 🕐❔ 👫 🛬, 👆 🚫🔜 💸 🙋 👆 🥰. 👶
-
-👉 "🔁" 👷, 👆 "🔁" ⏮️ 🏧/🍳 👶 👶. 👆 ✔️ ⌛ 👶 & 📤 ☑ 🙍 👈 🏧/🍳 👶 👶 🏁 🍔 & 🤝 👫 👆, ⚖️ ⏪, 👱 🙆 💪 ✊ 👫.
-
-
-
-⤴️ 👆 🏧/🍳 👶 👶 😒 👟 🔙 ⏮️ 👆 🍔, ⏮️ 📏 🕰 ⌛ 👶 📤 🚪 ⏲.
-
-
-
-👆 ✊ 👆 🍔 & 🚶 🏓 ⏮️ 👆 🥰.
-
-👆 🍴 👫, & 👆 🔨. ⏹
-
-
-
-📤 🚫 🌅 💬 ⚖️ 😏 🌅 🕰 💸 ⌛ 👶 🚪 ⏲. 👶
-
-/// info
-
-🌹 🖼 👯 🍏. 👶
-
-///
-
----
-
-👉 😐 🔗 🍔, 👆 💻 / 📋 👶 ⏮️ 2️⃣ 🕹 (👆 & 👆 🥰), 👯♂️ ⌛ 👶 & 💡 👫 🙋 👶 "⌛ 🔛 ⏲" 👶 📏 🕰.
-
-⏩ 🥕 🏪 ✔️ 8️⃣ 🕹 (🏧/🍳). ⏪ 🛠️ 🍔 🏪 💪 ✔️ ✔️ 🕴 2️⃣ (1️⃣ 🏧 & 1️⃣ 🍳).
-
-✋️, 🏁 💡 🚫 🏆. 👶
-
----
-
-👉 🔜 🔗 🌓 📖 🍔. 👶
-
-🌅 "🎰 👨❤👨" 🖼 👉, 🌈 🏦.
-
-🆙 ⏳, 🏆 🏦 ✔️ 💗 🏧 👶 👶 👶 👶 👶 👶 👶 👶 & 🦏 ⏸ 👶 👶 👶 👶 👶 👶 👶 👶.
-
-🌐 🏧 🔨 🌐 👷 ⏮️ 1️⃣ 👩💻 ⏮️ 🎏 👶 👶 👶.
-
-& 👆 ✔️ ⌛ 👶 ⏸ 📏 🕰 ⚖️ 👆 💸 👆 🔄.
-
-👆 🎲 🚫🔜 💚 ✊ 👆 🥰 👶 ⏮️ 👆 👷 🏦 👶.
-
-### 🍔 🏁
-
-👉 😐 "⏩ 🥕 🍔 ⏮️ 👆 🥰", 📤 📚 ⌛ 👶, ⚫️ ⚒ 📚 🌅 🔑 ✔️ 🛠️ ⚙️ ⏸ 👶 👶.
-
-👉 💼 🌅 🕸 🈸.
-
-📚, 📚 👩💻, ✋️ 👆 💽 ⌛ 👶 👫 🚫--👍 🔗 📨 👫 📨.
-
-& ⤴️ ⌛ 👶 🔄 📨 👟 🔙.
-
-👉 "⌛" 👶 ⚖ ⏲, ✋️, ⚖ ⚫️ 🌐, ⚫️ 📚 ⌛ 🔚.
-
-👈 ⚫️❔ ⚫️ ⚒ 📚 🔑 ⚙️ 🔁 ⏸ 👶 👶 📟 🕸 🔗.
-
-👉 😇 🔀 ⚫️❔ ⚒ ✳ 🌟 (✋️ ✳ 🚫 🔗) & 👈 💪 🚶 🛠️ 🇪🇸.
-
-& 👈 🎏 🎚 🎭 👆 🤚 ⏮️ **FastAPI**.
-
-& 👆 💪 ✔️ 🔁 & 🔀 🎏 🕰, 👆 🤚 ↕ 🎭 🌘 🌅 💯 ✳ 🛠️ & 🔛 🇷🇪 ⏮️ 🚶, ❔ ✍ 🇪🇸 🔐 🅱 (🌐 👏 💃).
-
-### 🛠️ 👍 🌘 🔁 ❓
-
-😆 ❗ 👈 🚫 🛐 📖.
-
-🛠️ 🎏 🌘 🔁. & ⚫️ 👻 🔛 **🎯** 😐 👈 🔌 📚 ⌛. ↩️ 👈, ⚫️ 🛎 📚 👍 🌘 🔁 🕸 🈸 🛠️. ✋️ 🚫 🌐.
-
-, ⚖ 👈 👅, 🌈 📄 📏 📖:
-
-> 👆 ✔️ 🧹 🦏, 💩 🏠.
-
-*😆, 👈 🎂 📖*.
-
----
-
-📤 🙅♂ ⌛ 👶 🙆, 📚 👷 🔨, 🔛 💗 🥉 🏠.
-
-👆 💪 ✔️ 🔄 🍔 🖼, 🥇 🏠 🧖♂, ⤴️ 👨🍳, ✋️ 👆 🚫 ⌛ 👶 🕳, 🧹 & 🧹, 🔄 🚫🔜 📉 🕳.
-
-⚫️ 🔜 ✊ 🎏 💸 🕰 🏁 ⏮️ ⚖️ 🍵 🔄 (🛠️) & 👆 🔜 ✔️ ⌛ 🎏 💸 👷.
-
-✋️ 👉 💼, 🚥 👆 💪 ✊️ 8️⃣ 👰-🏧/🍳/🔜-🧹, & 🔠 1️⃣ 👫 (➕ 👆) 💪 ✊ 🏒 🏠 🧹 ⚫️, 👆 💪 🌐 👷 **🔗**, ⏮️ ➕ ℹ, & 🏁 🌅 🔜.
-
-👉 😐, 🔠 1️⃣ 🧹 (🔌 👆) 🔜 🕹, 🤸 👫 🍕 👨🏭.
-
-& 🏆 🛠️ 🕰 ✊ ☑ 👷 (↩️ ⌛), & 👷 💻 ⌛ 💽, 👫 🤙 👫 ⚠ "💽 🎁".
-
----
-
-⚠ 🖼 💽 🔗 🛠️ 👜 👈 🚚 🏗 🧪 🏭.
-
-🖼:
-
-* **🎧** ⚖️ **🖼 🏭**.
-* **💻 👓**: 🖼 ✍ 💯 🔅, 🔠 🔅 ✔️ 3️⃣ 💲 / 🎨, 🏭 👈 🛎 🚚 💻 🕳 🔛 📚 🔅, 🌐 🎏 🕰.
-* **🎰 🏫**: ⚫️ 🛎 🚚 📚 "✖" & "🖼" ✖. 💭 🦏 📋 ⏮️ 🔢 & ✖ 🌐 👫 👯♂️ 🎏 🕰.
-* **⏬ 🏫**: 👉 🎧-🏑 🎰 🏫,, 🎏 ✔. ⚫️ 👈 📤 🚫 👁 📋 🔢 ✖, ✋️ 🦏 ⚒ 👫, & 📚 💼, 👆 ⚙️ 🎁 🕹 🏗 & / ⚖️ ⚙️ 👈 🏷.
-
-### 🛠️ ➕ 🔁: 🕸 ➕ 🎰 🏫
-
-⏮️ **FastAPI** 👆 💪 ✊ 📈 🛠️ 👈 📶 ⚠ 🕸 🛠️ (🎏 👑 🧲 ✳).
-
-✋️ 👆 💪 🐄 💰 🔁 & 💾 (✔️ 💗 🛠️ 🏃♂ 🔗) **💽 🎁** ⚖ 💖 👈 🎰 🏫 ⚙️.
-
-👈, ➕ 🙅 👐 👈 🐍 👑 🇪🇸 **💽 🧪**, 🎰 🏫 & ✴️ ⏬ 🏫, ⚒ FastAPI 📶 👍 🏏 💽 🧪 / 🎰 🏫 🕸 🔗 & 🈸 (👪 📚 🎏).
-
-👀 ❔ 🏆 👉 🔁 🏭 👀 📄 🔃 [🛠️](deployment/index.md){.internal-link target=_blank}.
-
-## `async` & `await`
-
-🏛 ⏬ 🐍 ✔️ 📶 🏋️ 🌌 🔬 🔁 📟. 👉 ⚒ ⚫️ 👀 💖 😐 "🔁" 📟 & "⌛" 👆 ▶️️ 🙍.
-
-🕐❔ 📤 🛠️ 👈 🔜 🚚 ⌛ ⏭ 🤝 🏁 & ✔️ 🐕🦺 👉 🆕 🐍 ⚒, 👆 💪 📟 ⚫️ 💖:
-
-```Python
-burgers = await get_burgers(2)
-```
-
-🔑 📥 `await`. ⚫️ 💬 🐍 👈 ⚫️ ✔️ ⌛ ⏸ `get_burgers(2)` 🏁 🔨 🚮 👜 👶 ⏭ ♻ 🏁 `burgers`. ⏮️ 👈, 🐍 🔜 💭 👈 ⚫️ 💪 🚶 & 🕳 🙆 👶 👶 👐 (💖 📨 ➕1️⃣ 📨).
-
-`await` 👷, ⚫️ ✔️ 🔘 🔢 👈 🐕🦺 👉 🔀. 👈, 👆 📣 ⚫️ ⏮️ `async def`:
-
-```Python hl_lines="1"
-async def get_burgers(number: int):
- # Do some asynchronous stuff to create the burgers
- return burgers
-```
-
-...↩️ `def`:
-
-```Python hl_lines="2"
-# This is not asynchronous
-def get_sequential_burgers(number: int):
- # Do some sequential stuff to create the burgers
- return burgers
-```
-
-⏮️ `async def`, 🐍 💭 👈, 🔘 👈 🔢, ⚫️ ✔️ 🤔 `await` 🧬, & 👈 ⚫️ 💪 "⏸" ⏸ 🛠️ 👈 🔢 & 🚶 🕳 🙆 👶 ⏭ 👟 🔙.
-
-🕐❔ 👆 💚 🤙 `async def` 🔢, 👆 ✔️ "⌛" ⚫️. , 👉 🏆 🚫 👷:
-
-```Python
-# This won't work, because get_burgers was defined with: async def
-burgers = get_burgers(2)
-```
-
----
-
-, 🚥 👆 ⚙️ 🗃 👈 💬 👆 👈 👆 💪 🤙 ⚫️ ⏮️ `await`, 👆 💪 ✍ *➡ 🛠️ 🔢* 👈 ⚙️ ⚫️ ⏮️ `async def`, 💖:
-
-```Python hl_lines="2-3"
-@app.get('/burgers')
-async def read_burgers():
- burgers = await get_burgers(2)
- return burgers
-```
-
-### 🌅 📡 ℹ
-
-👆 💪 ✔️ 👀 👈 `await` 💪 🕴 ⚙️ 🔘 🔢 🔬 ⏮️ `async def`.
-
-✋️ 🎏 🕰, 🔢 🔬 ⏮️ `async def` ✔️ "⌛". , 🔢 ⏮️ `async def` 💪 🕴 🤙 🔘 🔢 🔬 ⏮️ `async def` 💁♂️.
-
-, 🔃 🥚 & 🐔, ❔ 👆 🤙 🥇 `async` 🔢 ❓
-
-🚥 👆 👷 ⏮️ **FastAPI** 👆 🚫 ✔️ 😟 🔃 👈, ↩️ 👈 "🥇" 🔢 🔜 👆 *➡ 🛠️ 🔢*, & FastAPI 🔜 💭 ❔ ▶️️ 👜.
-
-✋️ 🚥 👆 💚 ⚙️ `async` / `await` 🍵 FastAPI, 👆 💪 ⚫️ 👍.
-
-### ✍ 👆 👍 🔁 📟
-
-💃 (& **FastAPI**) ⚓️ 🔛 AnyIO, ❔ ⚒ ⚫️ 🔗 ⏮️ 👯♂️ 🐍 🐩 🗃 ✳ & 🎻.
-
-🎯, 👆 💪 🔗 ⚙️ AnyIO 👆 🏧 🛠️ ⚙️ 💼 👈 🚚 🌅 🏧 ⚓ 👆 👍 📟.
-
-& 🚥 👆 🚫 ⚙️ FastAPI, 👆 💪 ✍ 👆 👍 🔁 🈸 ⏮️ AnyIO 🏆 🔗 & 🤚 🚮 💰 (✅ *📊 🛠️*).
-
-### 🎏 📨 🔁 📟
-
-👉 👗 ⚙️ `async` & `await` 📶 🆕 🇪🇸.
-
-✋️ ⚫️ ⚒ 👷 ⏮️ 🔁 📟 📚 ⏩.
-
-👉 🎏 ❕ (⚖️ 🌖 🌓) 🔌 ⏳ 🏛 ⏬ 🕸 (🖥 & ✳).
-
-✋️ ⏭ 👈, 🚚 🔁 📟 🌖 🏗 & ⚠.
-
-⏮️ ⏬ 🐍, 👆 💪 ✔️ ⚙️ 🧵 ⚖️ 🐁. ✋️ 📟 🌌 🌖 🏗 🤔, ℹ, & 💭 🔃.
-
-⏮️ ⏬ ✳ / 🖥 🕸, 👆 🔜 ✔️ ⚙️ "⏲". ❔ ↘️ "⏲ 🔥😈".
-
-## 🔁
-
-**🔁** 📶 🎀 ⚖ 👜 📨 `async def` 🔢. 🐍 💭 👈 ⚫️ 🕳 💖 🔢 👈 ⚫️ 💪 ▶️ & 👈 ⚫️ 🔜 🔚 ☝, ✋️ 👈 ⚫️ 5️⃣📆 ⏸ ⏸ 🔘 💁♂️, 🕐❔ 📤 `await` 🔘 ⚫️.
-
-✋️ 🌐 👉 🛠️ ⚙️ 🔁 📟 ⏮️ `async` & `await` 📚 🕰 🔬 ⚙️ "🔁". ⚫️ ⭐ 👑 🔑 ⚒ 🚶, "🔁".
-
-## 🏁
-
-➡️ 👀 🎏 🔤 ⚪️➡️ 🔛:
-
-> 🏛 ⏬ 🐍 ✔️ 🐕🦺 **"🔁 📟"** ⚙️ 🕳 🤙 **"🔁"**, ⏮️ **`async` & `await`** ❕.
-
-👈 🔜 ⚒ 🌅 🔑 🔜. 👶
-
-🌐 👈 ⚫️❔ 🏋️ FastAPI (🔘 💃) & ⚫️❔ ⚒ ⚫️ ✔️ ✅ 🎆 🎭.
-
-## 📶 📡 ℹ
-
-/// warning
-
-👆 💪 🎲 🚶 👉.
-
-👉 📶 📡 ℹ ❔ **FastAPI** 👷 🔘.
-
-🚥 👆 ✔️ 📡 💡 (🈶-🏋, 🧵, 🍫, ♒️.) & 😟 🔃 ❔ FastAPI 🍵 `async def` 🆚 😐 `def`, 🚶 ⤴️.
-
-///
-
-### ➡ 🛠️ 🔢
-
-🕐❔ 👆 📣 *➡ 🛠️ 🔢* ⏮️ 😐 `def` ↩️ `async def`, ⚫️ 🏃 🔢 🧵 👈 ⤴️ ⌛, ↩️ ➖ 🤙 🔗 (⚫️ 🔜 🍫 💽).
-
-🚥 👆 👟 ⚪️➡️ ➕1️⃣ 🔁 🛠️ 👈 🔨 🚫 👷 🌌 🔬 🔛 & 👆 ⚙️ ⚖ 🙃 📊-🕴 *➡ 🛠️ 🔢* ⏮️ ✅ `def` 🤪 🎭 📈 (🔃 1️⃣0️⃣0️⃣ 💓), 🙏 🗒 👈 **FastAPI** ⭐ 🔜 🔄. 👫 💼, ⚫️ 👻 ⚙️ `async def` 🚥 👆 *➡ 🛠️ 🔢* ⚙️ 📟 👈 🎭 🚧 👤/🅾.
-
-, 👯♂️ ⚠, 🤞 👈 **FastAPI** 🔜 [⏩](index.md#_15){.internal-link target=_blank} 🌘 (⚖️ 🌘 ⭐) 👆 ⏮️ 🛠️.
-
-### 🔗
-
-🎏 ✔ [🔗](tutorial/dependencies/index.md){.internal-link target=_blank}. 🚥 🔗 🐩 `def` 🔢 ↩️ `async def`, ⚫️ 🏃 🔢 🧵.
-
-### 🎧-🔗
-
-👆 💪 ✔️ 💗 🔗 & [🎧-🔗](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} 🚫 🔠 🎏 (🔢 🔢 🔑), 👫 💪 ✍ ⏮️ `async def` & ⏮️ 😐 `def`. ⚫️ 🔜 👷, & 🕐 ✍ ⏮️ 😐 `def` 🔜 🤙 🔛 🔢 🧵 (⚪️➡️ 🧵) ↩️ ➖ "⌛".
-
-### 🎏 🚙 🔢
-
-🙆 🎏 🚙 🔢 👈 👆 🤙 🔗 💪 ✍ ⏮️ 😐 `def` ⚖️ `async def` & FastAPI 🏆 🚫 📉 🌌 👆 🤙 ⚫️.
-
-👉 🔅 🔢 👈 FastAPI 🤙 👆: *➡ 🛠️ 🔢* & 🔗.
-
-🚥 👆 🚙 🔢 😐 🔢 ⏮️ `def`, ⚫️ 🔜 🤙 🔗 (👆 ✍ ⚫️ 👆 📟), 🚫 🧵, 🚥 🔢 ✍ ⏮️ `async def` ⤴️ 👆 🔜 `await` 👈 🔢 🕐❔ 👆 🤙 ⚫️ 👆 📟.
-
----
-
-🔄, 👉 📶 📡 ℹ 👈 🔜 🎲 ⚠ 🚥 👆 👟 🔎 👫.
-
-⏪, 👆 🔜 👍 ⏮️ 📄 ⚪️➡️ 📄 🔛: 🏃 ❓.
diff --git a/docs/em/docs/benchmarks.md b/docs/em/docs/benchmarks.md
deleted file mode 100644
index 003c3f62de..0000000000
--- a/docs/em/docs/benchmarks.md
+++ /dev/null
@@ -1,34 +0,0 @@
-# 📇
-
-🔬 🇸🇲 📇 🎦 **FastAPI** 🈸 🏃♂ 🔽 Uvicorn 1️⃣ ⏩ 🐍 🛠️ 💪, 🕴 🔛 💃 & Uvicorn 👫 (⚙️ 🔘 FastAPI). (*)
-
-✋️ 🕐❔ ✅ 📇 & 🔺 👆 🔜 ✔️ 📄 🤯.
-
-## 📇 & 🚅
-
-🕐❔ 👆 ✅ 📇, ⚫️ ⚠ 👀 📚 🧰 🎏 🆎 🔬 🌓.
-
-🎯, 👀 Uvicorn, 💃 & FastAPI 🔬 👯♂️ (👪 📚 🎏 🧰).
-
-🙅 ⚠ ❎ 🧰, 👍 🎭 ⚫️ 🔜 🤚. & 🏆 📇 🚫 💯 🌖 ⚒ 🚚 🧰.
-
-🔗 💖:
-
-* **Uvicorn**: 🔫 💽
- * **💃**: (⚙️ Uvicorn) 🕸 🕸
- * **FastAPI**: (⚙️ 💃) 🛠️ 🕸 ⏮️ 📚 🌖 ⚒ 🏗 🔗, ⏮️ 💽 🔬, ♒️.
-
-* **Uvicorn**:
- * 🔜 ✔️ 🏆 🎭, ⚫️ 🚫 ✔️ 🌅 ➕ 📟 ↖️ ⚪️➡️ 💽 ⚫️.
- * 👆 🚫🔜 ✍ 🈸 Uvicorn 🔗. 👈 🔜 ⛓ 👈 👆 📟 🔜 ✔️ 🔌 🌖 ⚖️ 🌘, 🌘, 🌐 📟 🚚 💃 (⚖️ **FastAPI**). & 🚥 👆 👈, 👆 🏁 🈸 🔜 ✔️ 🎏 🌥 ✔️ ⚙️ 🛠️ & 📉 👆 📱 📟 & 🐛.
- * 🚥 👆 ⚖ Uvicorn, 🔬 ⚫️ 🛡 👸, Hypercorn, ✳, ♒️. 🈸 💽.
-* **💃**:
- * 🔜 ✔️ ⏭ 🏆 🎭, ⏮️ Uvicorn. 👐, 💃 ⚙️ Uvicorn 🏃. , ⚫️ 🎲 💪 🕴 🤚 "🐌" 🌘 Uvicorn ✔️ 🛠️ 🌅 📟.
- * ✋️ ⚫️ 🚚 👆 🧰 🏗 🙅 🕸 🈸, ⏮️ 🕹 ⚓️ 🔛 ➡, ♒️.
- * 🚥 👆 ⚖ 💃, 🔬 ⚫️ 🛡 🤣, 🏺, ✳, ♒️. 🕸 🛠️ (⚖️ 🕸).
-* **FastAPI**:
- * 🎏 🌌 👈 💃 ⚙️ Uvicorn & 🚫🔜 ⏩ 🌘 ⚫️, **FastAPI** ⚙️ 💃, ⚫️ 🚫🔜 ⏩ 🌘 ⚫️.
- * FastAPI 🚚 🌅 ⚒ 🔛 🔝 💃. ⚒ 👈 👆 🌖 🕧 💪 🕐❔ 🏗 🔗, 💖 💽 🔬 & 🛠️. & ⚙️ ⚫️, 👆 🤚 🏧 🧾 🆓 (🏧 🧾 🚫 🚮 🌥 🏃♂ 🈸, ⚫️ 🏗 🔛 🕴).
- * 🚥 👆 🚫 ⚙️ FastAPI & ⚙️ 💃 🔗 (⚖️ ➕1️⃣ 🧰, 💖 🤣, 🏺, 🆘, ♒️) 👆 🔜 ✔️ 🛠️ 🌐 💽 🔬 & 🛠️ 👆. , 👆 🏁 🈸 🔜 ✔️ 🎏 🌥 🚥 ⚫️ 🏗 ⚙️ FastAPI. & 📚 💼, 👉 💽 🔬 & 🛠️ 🦏 💸 📟 ✍ 🈸.
- * , ⚙️ FastAPI 👆 ♻ 🛠️ 🕰, 🐛, ⏸ 📟, & 👆 🔜 🎲 🤚 🎏 🎭 (⚖️ 👍) 👆 🔜 🚥 👆 🚫 ⚙️ ⚫️ (👆 🔜 ✔️ 🛠️ ⚫️ 🌐 👆 📟).
- * 🚥 👆 ⚖ FastAPI, 🔬 ⚫️ 🛡 🕸 🈸 🛠️ (⚖️ ⚒ 🧰) 👈 🚚 💽 🔬, 🛠️ & 🧾, 💖 🏺-apispec, NestJS, ♨, ♒️. 🛠️ ⏮️ 🛠️ 🏧 💽 🔬, 🛠️ & 🧾.
diff --git a/docs/em/docs/deployment/concepts.md b/docs/em/docs/deployment/concepts.md
deleted file mode 100644
index bbb017277c..0000000000
--- a/docs/em/docs/deployment/concepts.md
+++ /dev/null
@@ -1,323 +0,0 @@
-# 🛠️ 🔧
-
-🕐❔ 🛠️ **FastAPI** 🈸, ⚖️ 🤙, 🙆 🆎 🕸 🛠️, 📤 📚 🔧 👈 👆 🎲 💅 🔃, & ⚙️ 👫 👆 💪 🔎 **🏆 ☑** 🌌 **🛠️ 👆 🈸**.
-
-⚠ 🔧:
-
-* 💂♂ - 🇺🇸🔍
-* 🏃♂ 🔛 🕴
-* ⏏
-* 🧬 (🔢 🛠️ 🏃)
-* 💾
-* ⏮️ 🔁 ⏭ ▶️
-
-👥 🔜 👀 ❔ 👫 🔜 📉 **🛠️**.
-
-🔚, 🏆 🎯 💪 **🍦 👆 🛠️ 👩💻** 🌌 👈 **🔐**, **❎ 📉**, & ⚙️ **📊 ℹ** (🖼 🛰 💽/🕹 🎰) ♻ 💪. 👶
-
-👤 🔜 💬 👆 🍖 🌖 🔃 👫 **🔧** 📥, & 👈 🔜 🤞 🤝 👆 **🤔** 👆 🔜 💪 💭 ❔ 🛠️ 👆 🛠️ 📶 🎏 🌐, 🎲 **🔮** 🕐 👈 🚫 🔀.
-
-🤔 👫 🔧, 👆 🔜 💪 **🔬 & 🔧** 🏆 🌌 🛠️ **👆 👍 🔗**.
-
-⏭ 📃, 👤 🔜 🤝 👆 🌅 **🧱 🍮** 🛠️ FastAPI 🈸.
-
-✋️ 🔜, ➡️ ✅ 👉 ⚠ **⚛ 💭**. 👫 🔧 ✔ 🙆 🎏 🆎 🕸 🛠️. 👶
-
-## 💂♂ - 🇺🇸🔍
-
-[⏮️ 📃 🔃 🇺🇸🔍](https.md){.internal-link target=_blank} 👥 🇭🇲 🔃 ❔ 🇺🇸🔍 🚚 🔐 👆 🛠️.
-
-👥 👀 👈 🇺🇸🔍 🛎 🚚 🦲 **🔢** 👆 🈸 💽, **🤝 ❎ 🗳**.
-
-& 📤 ✔️ 🕳 🈚 **♻ 🇺🇸🔍 📄**, ⚫️ 💪 🎏 🦲 ⚖️ ⚫️ 💪 🕳 🎏.
-
-### 🖼 🧰 🇺🇸🔍
-
-🧰 👆 💪 ⚙️ 🤝 ❎ 🗳:
-
-* Traefik
- * 🔁 🍵 📄 🔕 👶
-* 📥
- * 🔁 🍵 📄 🔕 👶
-* 👌
- * ⏮️ 🔢 🦲 💖 Certbot 📄 🔕
-* ✳
- * ⏮️ 🔢 🦲 💖 Certbot 📄 🔕
-* Kubernetes ⏮️ 🚧 🕹 💖 👌
- * ⏮️ 🔢 🦲 💖 🛂-👨💼 📄 🔕
-* 🍵 🔘 ☁ 🐕🦺 🍕 👫 🐕🦺 (✍ 🔛 👶)
-
-➕1️⃣ 🎛 👈 👆 💪 ⚙️ **☁ 🐕🦺** 👈 🔨 🌖 👷 ✅ ⚒ 🆙 🇺🇸🔍. ⚫️ 💪 ✔️ 🚫 ⚖️ 🈚 👆 🌅, ♒️. ✋️ 👈 💼, 👆 🚫🔜 ✔️ ⚒ 🆙 🤝 ❎ 🗳 👆.
-
-👤 🔜 🎦 👆 🧱 🖼 ⏭ 📃.
-
----
-
-⤴️ ⏭ 🔧 🤔 🌐 🔃 📋 🏃 👆 ☑ 🛠️ (✅ Uvicorn).
-
-## 📋 & 🛠️
-
-👥 🔜 💬 📚 🔃 🏃 "**🛠️**", ⚫️ ⚠ ✔️ ☯ 🔃 ⚫️❔ ⚫️ ⛓, & ⚫️❔ 🔺 ⏮️ 🔤 "**📋**".
-
-### ⚫️❔ 📋
-
-🔤 **📋** 🛎 ⚙️ 🔬 📚 👜:
-
-* **📟** 👈 👆 ✍, **🐍 📁**.
-* **📁** 👈 💪 **🛠️** 🏃♂ ⚙️, 🖼: `python`, `python.exe` ⚖️ `uvicorn`.
-* 🎯 📋 ⏪ ⚫️ **🏃♂** 🔛 🏗 ⚙️, ⚙️ 💽, & ♻ 👜 🔛 💾. 👉 🤙 **🛠️**.
-
-### ⚫️❔ 🛠️
-
-🔤 **🛠️** 🛎 ⚙️ 🌖 🎯 🌌, 🕴 🔗 👜 👈 🏃 🏃♂ ⚙️ (💖 🏁 ☝ 🔛):
-
-* 🎯 📋 ⏪ ⚫️ **🏃♂** 🔛 🏃♂ ⚙️.
- * 👉 🚫 🔗 📁, 🚫 📟, ⚫️ 🔗 **🎯** 👜 👈 ➖ **🛠️** & 🔄 🏃♂ ⚙️.
-* 🙆 📋, 🙆 📟, **💪 🕴 👜** 🕐❔ ⚫️ ➖ **🛠️**. , 🕐❔ 📤 **🛠️ 🏃**.
-* 🛠️ 💪 **❎** (⚖️ "💥") 👆, ⚖️ 🏃♂ ⚙️. 👈 ☝, ⚫️ ⛔️ 🏃/➖ 🛠️, & ⚫️ 💪 **🙅♂ 📏 👜**.
-* 🔠 🈸 👈 👆 ✔️ 🏃 🔛 👆 💻 ✔️ 🛠️ ⛅ ⚫️, 🔠 🏃♂ 📋, 🔠 🚪, ♒️. & 📤 🛎 📚 🛠️ 🏃 **🎏 🕰** ⏪ 💻 🔛.
-* 📤 💪 **💗 🛠️** **🎏 📋** 🏃 🎏 🕰.
-
-🚥 👆 ✅ 👅 "📋 👨💼" ⚖️ "⚙️ 🖥" (⚖️ 🎏 🧰) 👆 🏃♂ ⚙️, 👆 🔜 💪 👀 📚 👈 🛠️ 🏃♂.
-
-& , 🖼, 👆 🔜 🎲 👀 👈 📤 💗 🛠️ 🏃 🎏 🖥 📋 (🦎, 💄, 📐, ♒️). 👫 🛎 🏃 1️⃣ 🛠️ 📍 📑, ➕ 🎏 ➕ 🛠️.
-
-
-
----
-
-🔜 👈 👥 💭 🔺 🖖 ⚖ **🛠️** & **📋**, ➡️ 😣 💬 🔃 🛠️.
-
-## 🏃♂ 🔛 🕴
-
-🌅 💼, 🕐❔ 👆 ✍ 🕸 🛠️, 👆 💚 ⚫️ **🕧 🏃♂**, ➡, 👈 👆 👩💻 💪 🕧 🔐 ⚫️. 👉 ↗️, 🚥 👆 ✔️ 🎯 🤔 ⚫️❔ 👆 💚 ⚫️ 🏃 🕴 🎯 ⚠, ✋️ 🌅 🕰 👆 💚 ⚫️ 🕧 🏃♂ & **💪**.
-
-### 🛰 💽
-
-🕐❔ 👆 ⚒ 🆙 🛰 💽 (☁ 💽, 🕹 🎰, ♒️.) 🙅 👜 👆 💪 🏃 Uvicorn (⚖️ 🎏) ❎, 🎏 🌌 👆 🕐❔ 🛠️ 🌐.
-
-& ⚫️ 🔜 👷 & 🔜 ⚠ **⏮️ 🛠️**.
-
-✋️ 🚥 👆 🔗 💽 💸, **🏃♂ 🛠️** 🔜 🎲 ☠️.
-
-& 🚥 💽 ⏏ (🖼 ⏮️ ℹ, ⚖️ 🛠️ ⚪️➡️ ☁ 🐕🦺) 👆 🎲 **🏆 🚫 👀 ⚫️**. & ↩️ 👈, 👆 🏆 🚫 💭 👈 👆 ✔️ ⏏ 🛠️ ❎. , 👆 🛠️ 🔜 🚧 ☠️. 👶
-
-### 🏃 🔁 🔛 🕴
-
-🏢, 👆 🔜 🎲 💚 💽 📋 (✅ Uvicorn) ▶️ 🔁 🔛 💽 🕴, & 🍵 💪 🙆 **🗿 🏥**, ✔️ 🛠️ 🕧 🏃 ⏮️ 👆 🛠️ (✅ Uvicorn 🏃♂ 👆 FastAPI 📱).
-
-### 🎏 📋
-
-🏆 👉, 👆 🔜 🛎 ✔️ **🎏 📋** 👈 🔜 ⚒ 💭 👆 🈸 🏃 🔛 🕴. & 📚 💼, ⚫️ 🔜 ⚒ 💭 🎏 🦲 ⚖️ 🈸 🏃, 🖼, 💽.
-
-### 🖼 🧰 🏃 🕴
-
-🖼 🧰 👈 💪 👉 👨🏭:
-
-* ☁
-* Kubernetes
-* ☁ ✍
-* ☁ 🐝 📳
-* ✳
-* 👨💻
-* 🍵 🔘 ☁ 🐕🦺 🍕 👫 🐕🦺
-* 🎏...
-
-👤 🔜 🤝 👆 🌅 🧱 🖼 ⏭ 📃.
-
-## ⏏
-
-🎏 ⚒ 💭 👆 🈸 🏃 🔛 🕴, 👆 🎲 💚 ⚒ 💭 ⚫️ **⏏** ⏮️ ❌.
-
-### 👥 ⚒ ❌
-
-👥, 🗿, ⚒ **❌**, 🌐 🕰. 🖥 🌖 *🕧* ✔️ **🐛** 🕵♂ 🎏 🥉. 👶
-
-& 👥 👩💻 🚧 📉 📟 👥 🔎 👈 🐛 & 👥 🛠️ 🆕 ⚒ (🎲 ❎ 🆕 🐛 💁♂️ 👶).
-
-### 🤪 ❌ 🔁 🍵
-
-🕐❔ 🏗 🕸 🔗 ⏮️ FastAPI, 🚥 📤 ❌ 👆 📟, FastAPI 🔜 🛎 🔌 ⚫️ 👁 📨 👈 ⏲ ❌. 🛡
-
-👩💻 🔜 🤚 **5️⃣0️⃣0️⃣ 🔗 💽 ❌** 👈 📨, ✋️ 🈸 🔜 😣 👷 ⏭ 📨 ↩️ 💥 🍕.
-
-### 🦏 ❌ - 💥
-
-👐, 📤 5️⃣📆 💼 🌐❔ 👥 ✍ 📟 👈 **💥 🎂 🈸** ⚒ Uvicorn & 🐍 💥. 👶
-
-& , 👆 🔜 🎲 🚫 💚 🈸 🚧 ☠️ ↩️ 📤 ❌ 1️⃣ 🥉, 👆 🎲 💚 ⚫️ **😣 🏃** 🌘 *➡ 🛠️* 👈 🚫 💔.
-
-### ⏏ ⏮️ 💥
-
-✋️ 👈 💼 ⏮️ 🤙 👎 ❌ 👈 💥 🏃♂ **🛠️**, 👆 🔜 💚 🔢 🦲 👈 🈚 **🔁** 🛠️, 🌘 👩❤👨 🕰...
-
-/// tip
-
-...👐 🚥 🎂 🈸 **💥 ⏪** ⚫️ 🎲 🚫 ⚒ 🔑 🚧 🔁 ⚫️ ♾. ✋️ 📚 💼, 👆 🔜 🎲 👀 ⚫️ ⏮️ 🛠️, ⚖️ 🌘 ▶️️ ⏮️ 🛠️.
-
-➡️ 🎯 🔛 👑 💼, 🌐❔ ⚫️ 💪 💥 🍕 🎯 💼 **🔮**, & ⚫️ ⚒ 🔑 ⏏ ⚫️.
-
-///
-
-👆 🔜 🎲 💚 ✔️ 👜 🈚 🔁 👆 🈸 **🔢 🦲**, ↩️ 👈 ☝, 🎏 🈸 ⏮️ Uvicorn & 🐍 ⏪ 💥, 📤 🕳 🎏 📟 🎏 📱 👈 💪 🕳 🔃 ⚫️.
-
-### 🖼 🧰 ⏏ 🔁
-
-🏆 💼, 🎏 🧰 👈 ⚙️ **🏃 📋 🔛 🕴** ⚙️ 🍵 🏧 **⏏**.
-
-🖼, 👉 💪 🍵:
-
-* ☁
-* Kubernetes
-* ☁ ✍
-* ☁ 🐝 📳
-* ✳
-* 👨💻
-* 🍵 🔘 ☁ 🐕🦺 🍕 👫 🐕🦺
-* 🎏...
-
-## 🧬 - 🛠️ & 💾
-
-⏮️ FastAPI 🈸, ⚙️ 💽 📋 💖 Uvicorn, 🏃♂ ⚫️ 🕐 **1️⃣ 🛠️** 💪 🍦 💗 👩💻 🔁.
-
-✋️ 📚 💼, 👆 🔜 💚 🏃 📚 👨🏭 🛠️ 🎏 🕰.
-
-### 💗 🛠️ - 👨🏭
-
-🚥 👆 ✔️ 🌅 👩💻 🌘 ⚫️❔ 👁 🛠️ 💪 🍵 (🖼 🚥 🕹 🎰 🚫 💁♂️ 🦏) & 👆 ✔️ **💗 🐚** 💽 💽, ⤴️ 👆 💪 ✔️ **💗 🛠️** 🏃♂ ⏮️ 🎏 🈸 🎏 🕰, & 📎 🌐 📨 👪 👫.
-
-🕐❔ 👆 🏃 **💗 🛠️** 🎏 🛠️ 📋, 👫 🛎 🤙 **👨🏭**.
-
-### 👨🏭 🛠️ & ⛴
-
-💭 ⚪️➡️ 🩺 [🔃 🇺🇸🔍](https.md){.internal-link target=_blank} 👈 🕴 1️⃣ 🛠️ 💪 👂 🔛 1️⃣ 🌀 ⛴ & 📢 📢 💽 ❓
-
-👉 ☑.
-
-, 💪 ✔️ **💗 🛠️** 🎏 🕰, 📤 ✔️ **👁 🛠️ 👂 🔛 ⛴** 👈 ⤴️ 📶 📻 🔠 👨🏭 🛠️ 🌌.
-
-### 💾 📍 🛠️
-
-🔜, 🕐❔ 📋 📐 👜 💾, 🖼, 🎰 🏫 🏷 🔢, ⚖️ 🎚 ⭕ 📁 🔢, 🌐 👈 **🍴 👄 💾 (💾)** 💽.
-
-& 💗 🛠️ 🛎 **🚫 💰 🙆 💾**. 👉 ⛓ 👈 🔠 🏃 🛠️ ✔️ 🚮 👍 👜, 🔢, & 💾. & 🚥 👆 😩 ⭕ 💸 💾 👆 📟, **🔠 🛠️** 🔜 🍴 🌓 💸 💾.
-
-### 💽 💾
-
-🖼, 🚥 👆 📟 📐 🎰 🏫 🏷 ⏮️ **1️⃣ 💾 📐**, 🕐❔ 👆 🏃 1️⃣ 🛠️ ⏮️ 👆 🛠️, ⚫️ 🔜 🍴 🌘 1️⃣ 💾 💾. & 🚥 👆 ▶️ **4️⃣ 🛠️** (4️⃣ 👨🏭), 🔠 🔜 🍴 1️⃣ 💾 💾. 🌐, 👆 🛠️ 🔜 🍴 **4️⃣ 💾 💾**.
-
-& 🚥 👆 🛰 💽 ⚖️ 🕹 🎰 🕴 ✔️ 3️⃣ 💾 💾, 🔄 📐 🌅 🌘 4️⃣ 💾 💾 🔜 🤕 ⚠. 👶
-
-### 💗 🛠️ - 🖼
-
-👉 🖼, 📤 **👨💼 🛠️** 👈 ▶️ & 🎛 2️⃣ **👨🏭 🛠️**.
-
-👉 👨💼 🛠️ 🔜 🎲 1️⃣ 👂 🔛 **⛴** 📢. & ⚫️ 🔜 📶 🌐 📻 👨🏭 🛠️.
-
-👈 👨🏭 🛠️ 🔜 🕐 🏃♂ 👆 🈸, 👫 🔜 🎭 👑 📊 📨 **📨** & 📨 **📨**, & 👫 🔜 📐 🕳 👆 🚮 🔢 💾.
-
-- -**FastAPI** 🚫🔜 🔀 🚥 🚫 ⏮️ 👷 🎏. - -📤 ✔️ 📚 🧰 ✍ ⏭ 👈 ✔️ ℹ 😮 🚮 🏗. - -👤 ✔️ ❎ 🏗 🆕 🛠️ 📚 1️⃣2️⃣🗓️. 🥇 👤 🔄 ❎ 🌐 ⚒ 📔 **FastAPI** ⚙️ 📚 🎏 🛠️, 🔌-🔌, & 🧰. - -✋️ ☝, 📤 🙅♂ 🎏 🎛 🌘 🏗 🕳 👈 🚚 🌐 👫 ⚒, ✊ 🏆 💭 ⚪️➡️ ⏮️ 🧰, & 🌀 👫 🏆 🌌 💪, ⚙️ 🇪🇸 ⚒ 👈 ➖🚫 💪 ⏭ (🐍 3️⃣.6️⃣ ➕ 🆎 🔑). - -- -## 🔬 - -⚙️ 🌐 ⏮️ 🎛 👤 ✔️ 🤞 💡 ⚪️➡️ 🌐 👫, ✊ 💭, & 🌀 👫 🏆 🌌 👤 💪 🔎 👤 & 🏉 👩💻 👤 ✔️ 👷 ⏮️. - -🖼, ⚫️ 🆑 👈 🎲 ⚫️ 🔜 ⚓️ 🔛 🐩 🐍 🆎 🔑. - -, 🏆 🎯 ⚙️ ⏪ ♻ 🐩. - -, ⏭ ▶️ 📟 **FastAPI**, 👤 💸 📚 🗓️ 🎓 🔌 🗄, 🎻 🔗, Oauth2️⃣, ♒️. 🎯 👫 💛, 🔀, & 🔺. - -## 🔧 - -⤴️ 👤 💸 🕰 🔧 👩💻 "🛠️" 👤 💚 ✔️ 👩💻 (👩💻 ⚙️ FastAPI). - -👤 💯 📚 💭 🏆 🌟 🐍 👨🎨: 🗒, 🆚 📟, 🎠 🧢 👨🎨. - -🏁 🐍 👩💻 🔬, 👈 📔 🔃 8️⃣0️⃣ 💯 👩💻. - -⚫️ ⛓ 👈 **FastAPI** 🎯 💯 ⏮️ 👨🎨 ⚙️ 8️⃣0️⃣ 💯 🐍 👩💻. & 🏆 🎏 👨🎨 😑 👷 ➡, 🌐 🚮 💰 🔜 👷 🌖 🌐 👨🎨. - -👈 🌌 👤 💪 🔎 🏆 🌌 📉 📟 ❎ 🌅 💪, ✔️ 🛠️ 🌐, 🆎 & ❌ ✅, ♒️. - -🌐 🌌 👈 🚚 🏆 🛠️ 💡 🌐 👩💻. - -## 📄 - -⏮️ 🔬 📚 🎛, 👤 💭 👈 👤 🔜 ⚙️ **Pydantic** 🚮 📈. - -⤴️ 👤 📉 ⚫️, ⚒ ⚫️ 🍕 🛠️ ⏮️ 🎻 🔗, 🐕🦺 🎏 🌌 🔬 ⚛ 📄, & 📉 👨🎨 🐕🦺 (🆎 ✅, ✍) ⚓️ 🔛 💯 📚 👨🎨. - -⏮️ 🛠️, 👤 📉 **💃**, 🎏 🔑 📄. - -## 🛠️ - -🕰 👤 ▶️ 🏗 **FastAPI** ⚫️, 🏆 🍖 ⏪ 🥉, 🔧 🔬, 📄 & 🧰 🔜, & 💡 🔃 🐩 & 🔧 🆑 & 🍋. - -## 🔮 - -👉 ☝, ⚫️ ⏪ 🆑 👈 **FastAPI** ⏮️ 🚮 💭 ➖ ⚠ 📚 👫👫. - -⚫️ 💆♂ 👐 🤭 ⏮️ 🎛 ♣ 📚 ⚙️ 💼 👍. - -📚 👩💻 & 🏉 ⏪ 🪀 🔛 **FastAPI** 👫 🏗 (🔌 👤 & 👇 🏉). - -✋️, 📤 📚 📈 & ⚒ 👟. - -**FastAPI** ✔️ 👑 🔮 ⤴️. - -& [👆 ℹ](help-fastapi.md){.internal-link target=_blank} 📉 👍. diff --git a/docs/em/docs/how-to/conditional-openapi.md b/docs/em/docs/how-to/conditional-openapi.md deleted file mode 100644 index e47ea0c35c..0000000000 --- a/docs/em/docs/how-to/conditional-openapi.md +++ /dev/null @@ -1,56 +0,0 @@ -# 🎲 🗄 - -🚥 👆 💪, 👆 💪 ⚙️ ⚒ & 🌐 🔢 🔗 🗄 ✔ ⚓️ 🔛 🌐, & ❎ ⚫️ 🍕. - -## 🔃 💂♂, 🔗, & 🩺 - -🕵♂ 👆 🧾 👩💻 🔢 🏭 *🚫🔜 🚫* 🌌 🛡 👆 🛠️. - -👈 🚫 🚮 🙆 ➕ 💂♂ 👆 🛠️, *➡ 🛠️* 🔜 💪 🌐❔ 👫. - -🚥 📤 💂♂ ⚠ 👆 📟, ⚫️ 🔜 🔀. - -🕵♂ 🧾 ⚒ ⚫️ 🌅 ⚠ 🤔 ❔ 🔗 ⏮️ 👆 🛠️, & 💪 ⚒ ⚫️ 🌅 ⚠ 👆 ℹ ⚫️ 🏭. ⚫️ 💪 🤔 🎯 📨 💂♂ 🔘 🌌. - -🚥 👆 💚 🔐 👆 🛠️, 📤 📚 👍 👜 👆 💪, 🖼: - -* ⚒ 💭 👆 ✔️ 👍 🔬 Pydantic 🏷 👆 📨 💪 & 📨. -* 🔗 🙆 ✔ ✔ & 🔑 ⚙️ 🔗. -* 🙅 🏪 🔢 🔐, 🕴 🔐#️⃣. -* 🛠️ & ⚙️ 👍-💭 🔐 🧰, 💖 🇸🇲 & 🥙 🤝, ♒️. -* 🚮 🌅 🧽 ✔ 🎛 ⏮️ Oauth2️⃣ ↔ 🌐❔ 💪. -* ...♒️. - -👐, 👆 5️⃣📆 ✔️ 📶 🎯 ⚙️ 💼 🌐❔ 👆 🤙 💪 ❎ 🛠️ 🩺 🌐 (✅ 🏭) ⚖️ ⚓️ 🔛 📳 ⚪️➡️ 🌐 🔢. - -## 🎲 🗄 ⚪️➡️ ⚒ & 🇨🇻 { - -👆 💪 💪 ⚙️ 🎏 Pydantic ⚒ 🔗 👆 🏗 🗄 & 🩺 ⚜. - -🖼: - -{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} - -📥 👥 📣 ⚒ `openapi_url` ⏮️ 🎏 🔢 `"/openapi.json"`. - -& ⤴️ 👥 ⚙️ ⚫️ 🕐❔ 🏗 `FastAPI` 📱. - -⤴️ 👆 💪 ❎ 🗄 (✅ 🎚 🩺) ⚒ 🌐 🔢 `OPENAPI_URL` 🛁 🎻, 💖: - -
diff --git a/docs/em/docs/how-to/graphql.md b/docs/em/docs/how-to/graphql.md
deleted file mode 100644
index 083e9ebd28..0000000000
--- a/docs/em/docs/how-to/graphql.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# 🕹
-
-**FastAPI** ⚓️ 🔛 **🔫** 🐩, ⚫️ 📶 ⏩ 🛠️ 🙆 **🕹** 🗃 🔗 ⏮️ 🔫.
-
-👆 💪 🌀 😐 FastAPI *➡ 🛠️* ⏮️ 🕹 🔛 🎏 🈸.
-
-/// tip
-
-**🕹** ❎ 📶 🎯 ⚙️ 💼.
-
-⚫️ ✔️ **📈** & **⚠** 🕐❔ 🔬 ⚠ **🕸 🔗**.
-
-⚒ 💭 👆 🔬 🚥 **💰** 👆 ⚙️ 💼 ⚖ **👐**. 👶
-
-///
-
-## 🕹 🗃
-
-📥 **🕹** 🗃 👈 ✔️ **🔫** 🐕🦺. 👆 💪 ⚙️ 👫 ⏮️ **FastAPI**:
-
-* 🍓 👶
- * ⏮️ 🩺 FastAPI
-* 👸
- * ⏮️ 🩺 FastAPI
-* 🍟
- * ⏮️ 🍟 🔫 🚚 🔫 🛠️
-* ⚗
- * ⏮️ 💃-Graphene3️⃣
-
-## 🕹 ⏮️ 🍓
-
-🚥 👆 💪 ⚖️ 💚 👷 ⏮️ **🕹**, **🍓** **👍** 🗃 ⚫️ ✔️ 🔧 🔐 **FastAPI** 🔧, ⚫️ 🌐 ⚓️ 🔛 **🆎 ✍**.
-
-⚓️ 🔛 👆 ⚙️ 💼, 👆 5️⃣📆 💖 ⚙️ 🎏 🗃, ✋️ 🚥 👆 💭 👤, 👤 🔜 🎲 🤔 👆 🔄 **🍓**.
-
-📥 🤪 🎮 ❔ 👆 💪 🛠️ 🍓 ⏮️ FastAPI:
-
-{* ../../docs_src/graphql/tutorial001.py hl[3,22,25:26] *}
-
-👆 💪 💡 🌅 🔃 🍓 🍓 🧾.
-
-& 🩺 🔃 🍓 ⏮️ FastAPI.
-
-## 🗝 `GraphQLApp` ⚪️➡️ 💃
-
-⏮️ ⏬ 💃 🔌 `GraphQLApp` 🎓 🛠️ ⏮️ ⚗.
-
-⚫️ 😢 ⚪️➡️ 💃, ✋️ 🚥 👆 ✔️ 📟 👈 ⚙️ ⚫️, 👆 💪 💪 **↔** 💃-Graphene3️⃣, 👈 📔 🎏 ⚙️ 💼 & ✔️ **🌖 🌓 🔢**.
-
-/// tip
-
-🚥 👆 💪 🕹, 👤 🔜 👍 👆 ✅ 👅 🍓, ⚫️ ⚓️ 🔛 🆎 ✍ ↩️ 🛃 🎓 & 🆎.
-
-///
-
-## 💡 🌅
-
-👆 💪 💡 🌅 🔃 **🕹** 🛂 🕹 🧾.
-
-👆 💪 ✍ 🌅 🔃 🔠 👈 🗃 🔬 🔛 👫 🔗.
diff --git a/docs/em/docs/index.md b/docs/em/docs/index.md
deleted file mode 100644
index 5f5fc2e393..0000000000
--- a/docs/em/docs/index.md
+++ /dev/null
@@ -1,474 +0,0 @@
-# FastAPI
-
-
-
-
-- FastAPI 🛠️, ↕ 🎭, ⏩ 💡, ⏩ 📟, 🔜 🏭 -
- - ---- - -**🧾**: https://fastapi.tiangolo.com - -**ℹ 📟**: https://github.com/fastapi/fastapi - ---- - -FastAPI 🏛, ⏩ (↕-🎭), 🕸 🛠️ 🏗 🛠️ ⏮️ 🐍 3️⃣.8️⃣ ➕ ⚓️ 🔛 🐩 🐍 🆎 🔑. - -🔑 ⚒: - -* **⏩**: 📶 ↕ 🎭, 🔛 🇷🇪 ⏮️ **✳** & **🚶** (👏 💃 & Pydantic). [1️⃣ ⏩ 🐍 🛠️ 💪](#_15). -* **⏩ 📟**: 📈 🚅 🛠️ ⚒ 🔃 2️⃣0️⃣0️⃣ 💯 3️⃣0️⃣0️⃣ 💯. * -* **👩❤👨 🐛**: 📉 🔃 4️⃣0️⃣ 💯 🗿 (👩💻) 📉 ❌. * -* **🏋️**: 👑 👨🎨 🐕🦺. 🛠️ 🌐. 🌘 🕰 🛠️. -* **⏩**: 🔧 ⏩ ⚙️ & 💡. 🌘 🕰 👂 🩺. -* **📏**: 📉 📟 ❎. 💗 ⚒ ⚪️➡️ 🔠 🔢 📄. 👩❤👨 🐛. -* **🏋️**: 🤚 🏭-🔜 📟. ⏮️ 🏧 🎓 🧾. -* **🐩-⚓️**: ⚓️ 🔛 (& 🍕 🔗 ⏮️) 📂 🐩 🔗: 🗄 (⏪ 💭 🦁) & 🎻 🔗. - -* ⚖ ⚓️ 🔛 💯 🔛 🔗 🛠️ 🏉, 🏗 🏭 🈸. - -## 💰 - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} -async def...uvicorn main:app --reload...email-validator - 📧 🔬.
-
-⚙️ 💃:
-
-* httpx - ✔ 🚥 👆 💚 ⚙️ `TestClient`.
-* jinja2 - ✔ 🚥 👆 💚 ⚙️ 🔢 📄 📳.
-* python-multipart - ✔ 🚥 👆 💚 🐕🦺 📨 "✍", ⏮️ `request.form()`.
-* itsdangerous - ✔ `SessionMiddleware` 🐕🦺.
-* pyyaml - ✔ 💃 `SchemaGenerator` 🐕🦺 (👆 🎲 🚫 💪 ⚫️ ⏮️ FastAPI).
-
-⚙️ FastAPI / 💃:
-
-* uvicorn - 💽 👈 📐 & 🍦 👆 🈸.
-* orjson - ✔ 🚥 👆 💚 ⚙️ `ORJSONResponse`.
-* ujson - ✔ 🚥 👆 💚 ⚙️ `UJSONResponse`.
-
-👆 💪 ❎ 🌐 👫 ⏮️ `pip install "fastapi[all]"`.
-
-## 🛂
-
-👉 🏗 ® 🔽 ⚖ 🇩🇪 🛂.
diff --git a/docs/em/docs/project-generation.md b/docs/em/docs/project-generation.md
deleted file mode 100644
index ef6a218215..0000000000
--- a/docs/em/docs/project-generation.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# 🏗 ⚡ - 📄
-
-👆 💪 ⚙️ 🏗 🚂 🤚 ▶️, ⚫️ 🔌 📚 ▶️ ⚒ 🆙, 💂♂, 💽 & 🛠️ 🔗 ⏪ ⌛ 👆.
-
-🏗 🚂 🔜 🕧 ✔️ 📶 🙃 🖥 👈 👆 🔜 ℹ & 🛠️ 👆 👍 💪, ✋️ ⚫️ 💪 👍 ▶️ ☝ 👆 🏗.
-
-## 🌕 📚 FastAPI ✳
-
-📂: https://github.com/tiangolo/full-stack-fastapi-postgresql
-
-### 🌕 📚 FastAPI ✳ - ⚒
-
-* 🌕 **☁** 🛠️ (☁ 🧢).
-* ☁ 🐝 📳 🛠️.
-* **☁ ✍** 🛠️ & 🛠️ 🇧🇿 🛠️.
-* **🏭 🔜** 🐍 🕸 💽 ⚙️ Uvicorn & 🐁.
-* 🐍 **FastAPI** 👩💻:
- * **⏩**: 📶 ↕ 🎭, 🔛 🇷🇪 ⏮️ **✳** & **🚶** (👏 💃 & Pydantic).
- * **🏋️**: 👑 👨🎨 🐕🦺. 🛠️ 🌐. 🌘 🕰 🛠️.
- * **⏩**: 🔧 ⏩ ⚙️ & 💡. 🌘 🕰 👂 🩺.
- * **📏**: 📉 📟 ❎. 💗 ⚒ ⚪️➡️ 🔠 🔢 📄.
- * **🏋️**: 🤚 🏭-🔜 📟. ⏮️ 🏧 🎓 🧾.
- * **🐩-⚓️**: ⚓️ 🔛 (& 🍕 🔗 ⏮️) 📂 🐩 🔗: 🗄 & 🎻 🔗.
- * **📚 🎏 ⚒** 🔌 🏧 🔬, 🛠️, 🎓 🧾, 🤝 ⏮️ Oauth2️⃣ 🥙 🤝, ♒️.
-* **🔐 🔐** 🔁 🔢.
-* **🥙 🤝** 🤝.
-* **🇸🇲** 🏷 (🔬 🏺 ↔, 👫 💪 ⚙️ ⏮️ 🥒 👨🏭 🔗).
-* 🔰 ▶️ 🏷 👩💻 (🔀 & ❎ 👆 💪).
-* **⚗** 🛠️.
-* **⚜** (✖️ 🇨🇳 ℹ 🤝).
-* **🥒** 👨🏭 👈 💪 🗄 & ⚙️ 🏷 & 📟 ⚪️➡️ 🎂 👩💻 🍕.
-* 🎂 👩💻 💯 ⚓️ 🔛 **✳**, 🛠️ ⏮️ ☁, 👆 💪 💯 🌕 🛠️ 🔗, 🔬 🔛 💽. ⚫️ 🏃 ☁, ⚫️ 💪 🏗 🆕 💽 🏪 ⚪️➡️ 🖌 🔠 🕰 (👆 💪 ⚙️ ✳, ✳, ✳, ⚖️ ⚫️❔ 👆 💚, & 💯 👈 🛠️ 👷).
-* ⏩ 🐍 🛠️ ⏮️ **📂 💾** 🛰 ⚖️-☁ 🛠️ ⏮️ ↔ 💖 ⚛ ⚗ ⚖️ 🎙 🎙 📟 📂.
-* **🎦** 🕸:
- * 🏗 ⏮️ 🎦 ✳.
- * **🥙 🤝** 🚚.
- * 💳 🎑.
- * ⏮️ 💳, 👑 🕹 🎑.
- * 👑 🕹 ⏮️ 👩💻 🏗 & 📕.
- * 👤 👩💻 📕.
- * **🇷🇪**.
- * **🎦-📻**.
- * **Vuetify** 🌹 🧽 🔧 🦲.
- * **📕**.
- * ☁ 💽 ⚓️ 🔛 **👌** (📶 🤾 🎆 ⏮️ 🎦-📻).
- * ☁ 👁-▶️ 🏗, 👆 🚫 💪 🖊 ⚖️ 💕 ✍ 📟.
- * 🕸 💯 🏃 🏗 🕰 (💪 🔕 💁♂️).
- * ⚒ 🔧 💪, ⚫️ 👷 👅 📦, ✋️ 👆 💪 🏤-🏗 ⏮️ 🎦 ✳ ⚖️ ✍ ⚫️ 👆 💪, & 🏤-⚙️ ⚫️❔ 👆 💚.
-* ** *️⃣ ** ✳ 💽, 👆 💪 🔀 ⚫️ ⚙️ 📁 & ✳ 💪.
-* **🥀** 🥒 👨🏭 ⚖.
-* 📐 ⚖ 🖖 🕸 & 👩💻 ⏮️ **Traefik**, 👆 💪 ✔️ 👯♂️ 🔽 🎏 🆔, 👽 ➡, ✋️ 🍦 🎏 📦.
-* Traefik 🛠️, ✅ ➡️ 🗜 **🇺🇸🔍** 📄 🏧 ⚡.
-* ✳ **🆑** (🔁 🛠️), 🔌 🕸 & 👩💻 🔬.
-
-## 🌕 📚 FastAPI 🗄
-
-📂: https://github.com/tiangolo/full-stack-fastapi-couchbase
-
-👶 👶 **⚠** 👶 👶
-
-🚥 👆 ▶️ 🆕 🏗 ⚪️➡️ 🖌, ✅ 🎛 📥.
-
-🖼, 🏗 🚂 🌕 📚 FastAPI ✳ 💪 👍 🎛, ⚫️ 🎯 🚧 & ⚙️. & ⚫️ 🔌 🌐 🆕 ⚒ & 📈.
-
-👆 🆓 ⚙️ 🗄-⚓️ 🚂 🚥 👆 💚, ⚫️ 🔜 🎲 👷 👌, & 🚥 👆 ⏪ ✔️ 🏗 🏗 ⏮️ ⚫️ 👈 👌 👍 (& 👆 🎲 ⏪ ℹ ⚫️ ♣ 👆 💪).
-
-👆 💪 ✍ 🌅 🔃 ⚫️ 🩺 🏦.
-
-## 🌕 📚 FastAPI ✳
-
-...💪 👟 ⏪, ⚓️ 🔛 👇 🕰 🚚 & 🎏 ⚖. 👶 👶
-
-## 🎰 🏫 🏷 ⏮️ 🌈 & FastAPI
-
-📂: https://github.com/microsoft/cookiecutter-spacy-fastapi
-
-### 🎰 🏫 🏷 ⏮️ 🌈 & FastAPI - ⚒
-
-* **🌈** 🕜 🏷 🛠️.
-* **☁ 🧠 🔎** 📨 📁 🏗.
-* **🏭 🔜** 🐍 🕸 💽 ⚙️ Uvicorn & 🐁.
-* **☁ 👩💻** Kubernetes (🦲) 🆑/💿 🛠️ 🏗.
-* **🤸♂** 💪 ⚒ 1️⃣ 🌈 🏗 🇪🇸 ⏮️ 🏗 🖥.
-* **💪 🏧** 🎏 🏷 🛠️ (Pytorch, 🇸🇲), 🚫 🌈.
diff --git a/docs/em/docs/python-types.md b/docs/em/docs/python-types.md
deleted file mode 100644
index d2af23bb9a..0000000000
--- a/docs/em/docs/python-types.md
+++ /dev/null
@@ -1,542 +0,0 @@
-# 🐍 🆎 🎶
-
-🐍 ✔️ 🐕🦺 📦 "🆎 🔑".
-
-👫 **"🆎 🔑"** 🎁 ❕ 👈 ✔ 📣 🆎 🔢.
-
-📣 🆎 👆 🔢, 👨🎨 & 🧰 💪 🤝 👆 👍 🐕🦺.
-
-👉 **⏩ 🔰 / ↗️** 🔃 🐍 🆎 🔑. ⚫️ 📔 🕴 💯 💪 ⚙️ 👫 ⏮️ **FastAPI**... ❔ 🤙 📶 🐥.
-
-**FastAPI** 🌐 ⚓️ 🔛 👫 🆎 🔑, 👫 🤝 ⚫️ 📚 📈 & 💰.
-
-✋️ 🚥 👆 🙅 ⚙️ **FastAPI**, 👆 🔜 💰 ⚪️➡️ 🏫 🍖 🔃 👫.
-
-/// note
-
-🚥 👆 🐍 🕴, & 👆 ⏪ 💭 🌐 🔃 🆎 🔑, 🚶 ⏭ 📃.
-
-///
-
-## 🎯
-
-➡️ ▶️ ⏮️ 🙅 🖼:
-
-```Python
-{!../../docs_src/python_types/tutorial001.py!}
-```
-
-🤙 👉 📋 🔢:
-
-```
-John Doe
-```
-
-🔢 🔨 📄:
-
-* ✊ `first_name` & `last_name`.
-* 🗜 🥇 🔤 🔠 1️⃣ ↖ 💼 ⏮️ `title()`.
-* 🔢 👫 ⏮️ 🚀 🖕.
-
-```Python hl_lines="2"
-{!../../docs_src/python_types/tutorial001.py!}
-```
-
-### ✍ ⚫️
-
-⚫️ 📶 🙅 📋.
-
-✋️ 🔜 🌈 👈 👆 ✍ ⚫️ ⚪️➡️ 🖌.
-
-☝ 👆 🔜 ✔️ ▶️ 🔑 🔢, 👆 ✔️ 🔢 🔜...
-
-✋️ ⤴️ 👆 ✔️ 🤙 "👈 👩🔬 👈 🗜 🥇 🔤 ↖ 💼".
-
-⚫️ `upper`❓ ⚫️ `uppercase`❓ `first_uppercase`❓ `capitalize`❓
-
-⤴️, 👆 🔄 ⏮️ 🗝 👩💻 👨👧👦, 👨🎨 ✍.
-
-👆 🆎 🥇 🔢 🔢, `first_name`, ⤴️ ❣ (`.`) & ⤴️ 🎯 `Ctrl+Space` ⏲ 🛠️.
-
-✋️, 😞, 👆 🤚 🕳 ⚠:
-
-
-
-### 🚮 🆎
-
-➡️ 🔀 👁 ⏸ ⚪️➡️ ⏮️ ⏬.
-
-👥 🔜 🔀 ⚫️❔ 👉 🧬, 🔢 🔢, ⚪️➡️:
-
-```Python
- first_name, last_name
-```
-
-:
-
-```Python
- first_name: str, last_name: str
-```
-
-👈 ⚫️.
-
-👈 "🆎 🔑":
-
-```Python hl_lines="1"
-{!../../docs_src/python_types/tutorial002.py!}
-```
-
-👈 🚫 🎏 📣 🔢 💲 💖 🔜 ⏮️:
-
-```Python
- first_name="john", last_name="doe"
-```
-
-⚫️ 🎏 👜.
-
-👥 ⚙️ ❤ (`:`), 🚫 🌓 (`=`).
-
-& ❎ 🆎 🔑 🛎 🚫 🔀 ⚫️❔ 🔨 ⚪️➡️ ⚫️❔ 🔜 🔨 🍵 👫.
-
-✋️ 🔜, 🌈 👆 🔄 🖕 🏗 👈 🔢, ✋️ ⏮️ 🆎 🔑.
-
-🎏 ☝, 👆 🔄 ⏲ 📋 ⏮️ `Ctrl+Space` & 👆 👀:
-
-
-
-⏮️ 👈, 👆 💪 📜, 👀 🎛, ⏭ 👆 🔎 1️⃣ 👈 "💍 🔔":
-
-
-
-## 🌅 🎯
-
-✅ 👉 🔢, ⚫️ ⏪ ✔️ 🆎 🔑:
-
-```Python hl_lines="1"
-{!../../docs_src/python_types/tutorial003.py!}
-```
-
-↩️ 👨🎨 💭 🆎 🔢, 👆 🚫 🕴 🤚 🛠️, 👆 🤚 ❌ ✅:
-
-
-
-🔜 👆 💭 👈 👆 ✔️ 🔧 ⚫️, 🗜 `age` 🎻 ⏮️ `str(age)`:
-
-```Python hl_lines="2"
-{!../../docs_src/python_types/tutorial004.py!}
-```
-
-## 📣 🆎
-
-👆 👀 👑 🥉 📣 🆎 🔑. 🔢 🔢.
-
-👉 👑 🥉 👆 🔜 ⚙️ 👫 ⏮️ **FastAPI**.
-
-### 🙅 🆎
-
-👆 💪 📣 🌐 🐩 🐍 🆎, 🚫 🕴 `str`.
-
-👆 💪 ⚙️, 🖼:
-
-* `int`
-* `float`
-* `bool`
-* `bytes`
-
-```Python hl_lines="1"
-{!../../docs_src/python_types/tutorial005.py!}
-```
-
-### 💊 🆎 ⏮️ 🆎 🔢
-
-📤 📊 📊 👈 💪 🔌 🎏 💲, 💖 `dict`, `list`, `set` & `tuple`. & 🔗 💲 💪 ✔️ 👫 👍 🆎 💁♂️.
-
-👉 🆎 👈 ✔️ 🔗 🆎 🤙 "**💊**" 🆎. & ⚫️ 💪 📣 👫, ⏮️ 👫 🔗 🆎.
-
-📣 👈 🆎 & 🔗 🆎, 👆 💪 ⚙️ 🐩 🐍 🕹 `typing`. ⚫️ 🔀 🎯 🐕🦺 👫 🆎 🔑.
-
-#### 🆕 ⏬ 🐍
-
-❕ ⚙️ `typing` **🔗** ⏮️ 🌐 ⏬, ⚪️➡️ 🐍 3️⃣.6️⃣ ⏪ 🕐, ✅ 🐍 3️⃣.9️⃣, 🐍 3️⃣.1️⃣0️⃣, ♒️.
-
-🐍 🏧, **🆕 ⏬** 👟 ⏮️ 📉 🐕🦺 👉 🆎 ✍ & 📚 💼 👆 🏆 🚫 💪 🗄 & ⚙️ `typing` 🕹 📣 🆎 ✍.
-
-🚥 👆 💪 ⚒ 🌖 ⏮️ ⏬ 🐍 👆 🏗, 👆 🔜 💪 ✊ 📈 👈 ➕ 🦁. 👀 🖼 🔛.
-
-#### 📇
-
-🖼, ➡️ 🔬 🔢 `list` `str`.
-
-//// tab | 🐍 3️⃣.6️⃣ & 🔛
-
-⚪️➡️ `typing`, 🗄 `List` (⏮️ 🔠 `L`):
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-📣 🔢, ⏮️ 🎏 ❤ (`:`) ❕.
-
-🆎, 🚮 `List` 👈 👆 🗄 ⚪️➡️ `typing`.
-
-📇 🆎 👈 🔌 🔗 🆎, 👆 🚮 👫 ⬜ 🗜:
-
-```Python hl_lines="4"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-////
-
-//// tab | 🐍 3️⃣.9️⃣ & 🔛
-
-📣 🔢, ⏮️ 🎏 ❤ (`:`) ❕.
-
-🆎, 🚮 `list`.
-
-📇 🆎 👈 🔌 🔗 🆎, 👆 🚮 👫 ⬜ 🗜:
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006_py39.py!}
-```
-
-////
-
-/// info
-
-👈 🔗 🆎 ⬜ 🗜 🤙 "🆎 🔢".
-
-👉 💼, `str` 🆎 🔢 🚶♀️ `List` (⚖️ `list` 🐍 3️⃣.9️⃣ & 🔛).
-
-///
-
-👈 ⛓: "🔢 `items` `list`, & 🔠 🏬 👉 📇 `str`".
-
-/// tip
-
-🚥 👆 ⚙️ 🐍 3️⃣.9️⃣ ⚖️ 🔛, 👆 🚫 ✔️ 🗄 `List` ⚪️➡️ `typing`, 👆 💪 ⚙️ 🎏 🥔 `list` 🆎 ↩️.
-
-///
-
-🔨 👈, 👆 👨🎨 💪 🚚 🐕🦺 ⏪ 🏭 🏬 ⚪️➡️ 📇:
-
-
-
-🍵 🆎, 👈 🌖 💪 🏆.
-
-👀 👈 🔢 `item` 1️⃣ 🔣 📇 `items`.
-
-& , 👨🎨 💭 ⚫️ `str`, & 🚚 🐕🦺 👈.
-
-#### 🔢 & ⚒
-
-👆 🔜 🎏 📣 `tuple`Ⓜ & `set`Ⓜ:
-
-//// tab | 🐍 3️⃣.6️⃣ & 🔛
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial007.py!}
-```
-
-////
-
-//// tab | 🐍 3️⃣.9️⃣ & 🔛
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial007_py39.py!}
-```
-
-////
-
-👉 ⛓:
-
-* 🔢 `items_t` `tuple` ⏮️ 3️⃣ 🏬, `int`, ➕1️⃣ `int`, & `str`.
-* 🔢 `items_s` `set`, & 🔠 🚮 🏬 🆎 `bytes`.
-
-#### #️⃣
-
-🔬 `dict`, 👆 🚶♀️ 2️⃣ 🆎 🔢, 🎏 ❕.
-
-🥇 🆎 🔢 🔑 `dict`.
-
-🥈 🆎 🔢 💲 `dict`:
-
-//// tab | 🐍 3️⃣.6️⃣ & 🔛
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008.py!}
-```
-
-////
-
-//// tab | 🐍 3️⃣.9️⃣ & 🔛
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008_py39.py!}
-```
-
-////
-
-👉 ⛓:
-
-* 🔢 `prices` `dict`:
- * 🔑 👉 `dict` 🆎 `str` (➡️ 💬, 📛 🔠 🏬).
- * 💲 👉 `dict` 🆎 `float` (➡️ 💬, 🔖 🔠 🏬).
-
-#### 🇪🇺
-
-👆 💪 📣 👈 🔢 💪 🙆 **📚 🆎**, 🖼, `int` ⚖️ `str`.
-
-🐍 3️⃣.6️⃣ & 🔛 (✅ 🐍 3️⃣.1️⃣0️⃣) 👆 💪 ⚙️ `Union` 🆎 ⚪️➡️ `typing` & 🚮 🔘 ⬜ 🗜 💪 🆎 🚫.
-
-🐍 3️⃣.1️⃣0️⃣ 📤 **🎛 ❕** 🌐❔ 👆 💪 🚮 💪 🆎 👽 ⏸ ⏸ (`|`).
-
-//// tab | 🐍 3️⃣.6️⃣ & 🔛
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008b.py!}
-```
-
-////
-
-//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008b_py310.py!}
-```
-
-////
-
-👯♂️ 💼 👉 ⛓ 👈 `item` 💪 `int` ⚖️ `str`.
-
-#### 🎲 `None`
-
-👆 💪 📣 👈 💲 💪 ✔️ 🆎, 💖 `str`, ✋️ 👈 ⚫️ 💪 `None`.
-
-🐍 3️⃣.6️⃣ & 🔛 (✅ 🐍 3️⃣.1️⃣0️⃣) 👆 💪 📣 ⚫️ 🏭 & ⚙️ `Optional` ⚪️➡️ `typing` 🕹.
-
-```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
-```
-
-⚙️ `Optional[str]` ↩️ `str` 🔜 ➡️ 👨🎨 ℹ 👆 🔍 ❌ 🌐❔ 👆 💪 🤔 👈 💲 🕧 `str`, 🕐❔ ⚫️ 💪 🤙 `None` 💁♂️.
-
-`Optional[Something]` 🤙 ⌨ `Union[Something, None]`, 👫 🌓.
-
-👉 ⛓ 👈 🐍 3️⃣.1️⃣0️⃣, 👆 💪 ⚙️ `Something | None`:
-
-//// tab | 🐍 3️⃣.6️⃣ & 🔛
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009.py!}
-```
-
-////
-
-//// tab | 🐍 3️⃣.6️⃣ & 🔛 - 🎛
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009b.py!}
-```
-
-////
-
-//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial009_py310.py!}
-```
-
-////
-
-#### ⚙️ `Union` ⚖️ `Optional`
-
-🚥 👆 ⚙️ 🐍 ⏬ 🔛 3️⃣.1️⃣0️⃣, 📥 💁♂ ⚪️➡️ 👇 📶 **🤔** ☝ 🎑:
-
-* 👶 ❎ ⚙️ `Optional[SomeType]`
-* ↩️ 👶 **⚙️ `Union[SomeType, None]`** 👶.
-
-👯♂️ 🌓 & 🔘 👫 🎏, ✋️ 👤 🔜 👍 `Union` ↩️ `Optional` ↩️ 🔤 "**📦**" 🔜 😑 🔑 👈 💲 📦, & ⚫️ 🤙 ⛓ "⚫️ 💪 `None`", 🚥 ⚫️ 🚫 📦 & ✔.
-
-👤 💭 `Union[SomeType, None]` 🌖 🔑 🔃 ⚫️❔ ⚫️ ⛓.
-
-⚫️ 🔃 🔤 & 📛. ✋️ 👈 🔤 💪 📉 ❔ 👆 & 👆 🤽♂ 💭 🔃 📟.
-
-🖼, ➡️ ✊ 👉 🔢:
-
-```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009c.py!}
-```
-
-🔢 `name` 🔬 `Optional[str]`, ✋️ ⚫️ **🚫 📦**, 👆 🚫🔜 🤙 🔢 🍵 🔢:
-
-```Python
-say_hi() # Oh, no, this throws an error! 😱
-```
-
-`name` 🔢 **✔** (🚫 *📦*) ↩️ ⚫️ 🚫 ✔️ 🔢 💲. , `name` 🚫 `None` 💲:
-
-```Python
-say_hi(name=None) # This works, None is valid 🎉
-```
-
-👍 📰, 🕐 👆 🔛 🐍 3️⃣.1️⃣0️⃣ 👆 🏆 🚫 ✔️ 😟 🔃 👈, 👆 🔜 💪 🎯 ⚙️ `|` 🔬 🇪🇺 🆎:
-
-```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009c_py310.py!}
-```
-
-& ⤴️ 👆 🏆 🚫 ✔️ 😟 🔃 📛 💖 `Optional` & `Union`. 👶
-
-#### 💊 🆎
-
-👉 🆎 👈 ✊ 🆎 🔢 ⬜ 🗜 🤙 **💊 🆎** ⚖️ **💊**, 🖼:
-
-//// tab | 🐍 3️⃣.6️⃣ & 🔛
-
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Union`
-* `Optional`
-* ...& 🎏.
-
-////
-
-//// tab | 🐍 3️⃣.9️⃣ & 🔛
-
-👆 💪 ⚙️ 🎏 💽 🆎 💊 (⏮️ ⬜ 🗜 & 🆎 🔘):
-
-* `list`
-* `tuple`
-* `set`
-* `dict`
-
- & 🎏 ⏮️ 🐍 3️⃣.6️⃣, ⚪️➡️ `typing` 🕹:
-
-* `Union`
-* `Optional`
-* ...& 🎏.
-
-////
-
-//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛
-
-👆 💪 ⚙️ 🎏 💽 🆎 💊 (⏮️ ⬜ 🗜 & 🆎 🔘):
-
-* `list`
-* `tuple`
-* `set`
-* `dict`
-
- & 🎏 ⏮️ 🐍 3️⃣.6️⃣, ⚪️➡️ `typing` 🕹:
-
-* `Union`
-* `Optional` (🎏 ⏮️ 🐍 3️⃣.6️⃣)
-* ...& 🎏.
-
-🐍 3️⃣.1️⃣0️⃣, 🎛 ⚙️ 💊 `Union` & `Optional`, 👆 💪 ⚙️ ⏸ ⏸ (`|`) 📣 🇪🇺 🆎.
-
-////
-
-### 🎓 🆎
-
-👆 💪 📣 🎓 🆎 🔢.
-
-➡️ 💬 👆 ✔️ 🎓 `Person`, ⏮️ 📛:
-
-```Python hl_lines="1-3"
-{!../../docs_src/python_types/tutorial010.py!}
-```
-
-⤴️ 👆 💪 📣 🔢 🆎 `Person`:
-
-```Python hl_lines="6"
-{!../../docs_src/python_types/tutorial010.py!}
-```
-
-& ⤴️, 🔄, 👆 🤚 🌐 👨🎨 🐕🦺:
-
-
-
-## Pydantic 🏷
-
-Pydantic 🐍 🗃 🎭 📊 🔬.
-
-👆 📣 "💠" 💽 🎓 ⏮️ 🔢.
-
-& 🔠 🔢 ✔️ 🆎.
-
-⤴️ 👆 ✍ 👐 👈 🎓 ⏮️ 💲 & ⚫️ 🔜 ✔ 💲, 🗜 👫 ☑ 🆎 (🚥 👈 💼) & 🤝 👆 🎚 ⏮️ 🌐 💽.
-
-& 👆 🤚 🌐 👨🎨 🐕🦺 ⏮️ 👈 📉 🎚.
-
-🖼 ⚪️➡️ 🛂 Pydantic 🩺:
-
-//// tab | 🐍 3️⃣.6️⃣ & 🔛
-
-```Python
-{!> ../../docs_src/python_types/tutorial011.py!}
-```
-
-////
-
-//// tab | 🐍 3️⃣.9️⃣ & 🔛
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py39.py!}
-```
-
-////
-
-//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py310.py!}
-```
-
-////
-
-/// info
-
-💡 🌖 🔃 Pydantic, ✅ 🚮 🩺.
-
-///
-
-**FastAPI** 🌐 ⚓️ 🔛 Pydantic.
-
-👆 🔜 👀 📚 🌅 🌐 👉 💡 [🔰 - 👩💻 🦮](tutorial/index.md){.internal-link target=_blank}.
-
-/// tip
-
-Pydantic ✔️ 🎁 🎭 🕐❔ 👆 ⚙️ `Optional` ⚖️ `Union[Something, None]` 🍵 🔢 💲, 👆 💪 ✍ 🌅 🔃 ⚫️ Pydantic 🩺 🔃 ✔ 📦 🏑.
-
-///
-
-## 🆎 🔑 **FastAPI**
-
-**FastAPI** ✊ 📈 👫 🆎 🔑 📚 👜.
-
-⏮️ **FastAPI** 👆 📣 🔢 ⏮️ 🆎 🔑 & 👆 🤚:
-
-* **👨🎨 🐕🦺**.
-* **🆎 ✅**.
-
-...and **FastAPI** uses the same declarations :
-
-* **🔬 📄**: ⚪️➡️ 📨 ➡ 🔢, 🔢 🔢, 🎚, 💪, 🔗, ♒️.
-* **🗜 💽**: ⚪️➡️ 📨 🚚 🆎.
-* **✔ 💽**: 👟 ⚪️➡️ 🔠 📨:
- * 🏭 **🏧 ❌** 📨 👩💻 🕐❔ 📊 ❌.
-* **📄** 🛠️ ⚙️ 🗄:
- * ❔ ⤴️ ⚙️ 🏧 🎓 🧾 👩💻 🔢.
-
-👉 5️⃣📆 🌐 🔊 📝. 🚫 😟. 👆 🔜 👀 🌐 👉 🎯 [🔰 - 👩💻 🦮](tutorial/index.md){.internal-link target=_blank}.
-
-⚠ 👜 👈 ⚙️ 🐩 🐍 🆎, 👁 🥉 (↩️ ❎ 🌖 🎓, 👨🎨, ♒️), **FastAPI** 🔜 📚 👷 👆.
-
-/// info
-
-🚥 👆 ⏪ 🚶 🔘 🌐 🔰 & 👟 🔙 👀 🌅 🔃 🆎, 👍 ℹ "🎮 🎼" ⚪️➡️ `mypy`.
-
-///
diff --git a/docs/em/docs/tutorial/background-tasks.md b/docs/em/docs/tutorial/background-tasks.md
deleted file mode 100644
index 4cbcbc710d..0000000000
--- a/docs/em/docs/tutorial/background-tasks.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# 🖥 📋
-
-👆 💪 🔬 🖥 📋 🏃 *⏮️* 🛬 📨.
-
-👉 ⚠ 🛠️ 👈 💪 🔨 ⏮️ 📨, ✋️ 👈 👩💻 🚫 🤙 ✔️ ⌛ 🛠️ 🏁 ⏭ 📨 📨.
-
-👉 🔌, 🖼:
-
-* 📧 📨 📨 ⏮️ 🎭 🎯:
- * 🔗 📧 💽 & 📨 📧 😑 "🐌" (📚 🥈), 👆 💪 📨 📨 ▶️️ ↖️ & 📨 📧 📨 🖥.
-* 🏭 💽:
- * 🖼, ➡️ 💬 👆 📨 📁 👈 🔜 🚶 🔘 🐌 🛠️, 👆 💪 📨 📨 "🚫" (🇺🇸🔍 2️⃣0️⃣2️⃣) & 🛠️ ⚫️ 🖥.
-
-## ⚙️ `BackgroundTasks`
-
-🥇, 🗄 `BackgroundTasks` & 🔬 🔢 👆 *➡ 🛠️ 🔢* ⏮️ 🆎 📄 `BackgroundTasks`:
-
-{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
-
-**FastAPI** 🔜 ✍ 🎚 🆎 `BackgroundTasks` 👆 & 🚶♀️ ⚫️ 👈 🔢.
-
-## ✍ 📋 🔢
-
-✍ 🔢 🏃 🖥 📋.
-
-⚫️ 🐩 🔢 👈 💪 📨 🔢.
-
-⚫️ 💪 `async def` ⚖️ 😐 `def` 🔢, **FastAPI** 🔜 💭 ❔ 🍵 ⚫️ ☑.
-
-👉 💼, 📋 🔢 🔜 ✍ 📁 (⚖ 📨 📧).
-
-& ✍ 🛠️ 🚫 ⚙️ `async` & `await`, 👥 🔬 🔢 ⏮️ 😐 `def`:
-
-{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
-
-## 🚮 🖥 📋
-
-🔘 👆 *➡ 🛠️ 🔢*, 🚶♀️ 👆 📋 🔢 *🖥 📋* 🎚 ⏮️ 👩🔬 `.add_task()`:
-
-{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
-
-`.add_task()` 📨 ❌:
-
-* 📋 🔢 🏃 🖥 (`write_notification`).
-* 🙆 🔁 ❌ 👈 🔜 🚶♀️ 📋 🔢 ✔ (`email`).
-* 🙆 🇨🇻 ❌ 👈 🔜 🚶♀️ 📋 🔢 (`message="some notification"`).
-
-## 🔗 💉
-
-⚙️ `BackgroundTasks` 👷 ⏮️ 🔗 💉 ⚙️, 👆 💪 📣 🔢 🆎 `BackgroundTasks` 💗 🎚: *➡ 🛠️ 🔢*, 🔗 (☑), 🎧-🔗, ♒️.
-
-**FastAPI** 💭 ⚫️❔ 🔠 💼 & ❔ 🏤-⚙️ 🎏 🎚, 👈 🌐 🖥 📋 🔗 👯♂️ & 🏃 🖥 ⏮️:
-
-{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *}
-
-👉 🖼, 📧 🔜 ✍ `log.txt` 📁 *⏮️* 📨 📨.
-
-🚥 📤 🔢 📨, ⚫️ 🔜 ✍ 🕹 🖥 📋.
-
-& ⤴️ ➕1️⃣ 🖥 📋 🏗 *➡ 🛠️ 🔢* 🔜 ✍ 📧 ⚙️ `email` ➡ 🔢.
-
-## 📡 ℹ
-
-🎓 `BackgroundTasks` 👟 🔗 ⚪️➡️ `starlette.background`.
-
-⚫️ 🗄/🔌 🔗 🔘 FastAPI 👈 👆 💪 🗄 ⚫️ ⚪️➡️ `fastapi` & ❎ 😫 🗄 🎛 `BackgroundTask` (🍵 `s` 🔚) ⚪️➡️ `starlette.background`.
-
-🕴 ⚙️ `BackgroundTasks` (& 🚫 `BackgroundTask`), ⚫️ ⤴️ 💪 ⚙️ ⚫️ *➡ 🛠️ 🔢* 🔢 & ✔️ **FastAPI** 🍵 🎂 👆, 💖 🕐❔ ⚙️ `Request` 🎚 🔗.
-
-⚫️ 💪 ⚙️ `BackgroundTask` 😞 FastAPI, ✋️ 👆 ✔️ ✍ 🎚 👆 📟 & 📨 💃 `Response` 🔌 ⚫️.
-
-👆 💪 👀 🌖 ℹ 💃 🛂 🩺 🖥 📋.
-
-## ⚠
-
-🚥 👆 💪 🎭 🏋️ 🖥 📊 & 👆 🚫 🎯 💪 ⚫️ 🏃 🎏 🛠️ (🖼, 👆 🚫 💪 💰 💾, 🔢, ♒️), 👆 💪 💰 ⚪️➡️ ⚙️ 🎏 🦏 🧰 💖 🥒.
-
-👫 😑 🚚 🌖 🏗 📳, 📧/👨🏭 📤 👨💼, 💖 ✳ ⚖️ ✳, ✋️ 👫 ✔ 👆 🏃 🖥 📋 💗 🛠️, & ✴️, 💗 💽.
-
-✋️ 🚥 👆 💪 🔐 🔢 & 🎚 ⚪️➡️ 🎏 **FastAPI** 📱, ⚖️ 👆 💪 🎭 🤪 🖥 📋 (💖 📨 📧 📨), 👆 💪 🎯 ⚙️ `BackgroundTasks`.
-
-## 🌃
-
-🗄 & ⚙️ `BackgroundTasks` ⏮️ 🔢 *➡ 🛠️ 🔢* & 🔗 🚮 🖥 📋.
diff --git a/docs/em/docs/tutorial/bigger-applications.md b/docs/em/docs/tutorial/bigger-applications.md
deleted file mode 100644
index 78a321ae6a..0000000000
--- a/docs/em/docs/tutorial/bigger-applications.md
+++ /dev/null
@@ -1,530 +0,0 @@
-# 🦏 🈸 - 💗 📁
-
-🚥 👆 🏗 🈸 ⚖️ 🕸 🛠️, ⚫️ 🛎 💼 👈 👆 💪 🚮 🌐 🔛 👁 📁.
-
-**FastAPI** 🚚 🏪 🧰 📊 👆 🈸 ⏪ 🚧 🌐 💪.
-
-/// info
-
-🚥 👆 👟 ⚪️➡️ 🏺, 👉 🔜 🌓 🏺 📗.
-
-///
-
-## 🖼 📁 📊
-
-➡️ 💬 👆 ✔️ 📁 📊 💖 👉:
-
-```
-.
-├── app
-│ ├── __init__.py
-│ ├── main.py
-│ ├── dependencies.py
-│ └── routers
-│ │ ├── __init__.py
-│ │ ├── items.py
-│ │ └── users.py
-│ └── internal
-│ ├── __init__.py
-│ └── admin.py
-```
-
-/// tip
-
-📤 📚 `__init__.py` 📁: 1️⃣ 🔠 📁 ⚖️ 📁.
-
-👉 ⚫️❔ ✔ 🏭 📟 ⚪️➡️ 1️⃣ 📁 🔘 ➕1️⃣.
-
-🖼, `app/main.py` 👆 💪 ✔️ ⏸ 💖:
-
-```
-from app.routers import items
-```
-
-///
-
-* `app` 📁 🔌 🌐. & ⚫️ ✔️ 🛁 📁 `app/__init__.py`, ⚫️ "🐍 📦" (🗃 "🐍 🕹"): `app`.
-* ⚫️ 🔌 `app/main.py` 📁. ⚫️ 🔘 🐍 📦 (📁 ⏮️ 📁 `__init__.py`), ⚫️ "🕹" 👈 📦: `app.main`.
-* 📤 `app/dependencies.py` 📁, 💖 `app/main.py`, ⚫️ "🕹": `app.dependencies`.
-* 📤 📁 `app/routers/` ⏮️ ➕1️⃣ 📁 `__init__.py`, ⚫️ "🐍 📦": `app.routers`.
-* 📁 `app/routers/items.py` 🔘 📦, `app/routers/`,, ⚫️ 🔁: `app.routers.items`.
-* 🎏 ⏮️ `app/routers/users.py`, ⚫️ ➕1️⃣ 🔁: `app.routers.users`.
-* 📤 📁 `app/internal/` ⏮️ ➕1️⃣ 📁 `__init__.py`, ⚫️ ➕1️⃣ "🐍 📦": `app.internal`.
-* & 📁 `app/internal/admin.py` ➕1️⃣ 🔁: `app.internal.admin`.
-
-
-
-## 🔌 🎏 📻 💗 🕰 ⏮️ 🎏 `prefix`
-
-👆 💪 ⚙️ `.include_router()` 💗 🕰 ⏮️ *🎏* 📻 ⚙️ 🎏 🔡.
-
-👉 💪 ⚠, 🖼, 🎦 🎏 🛠️ 🔽 🎏 🔡, ✅ `/api/v1` & `/api/latest`.
-
-👉 🏧 ⚙️ 👈 👆 5️⃣📆 🚫 🤙 💪, ✋️ ⚫️ 📤 💼 👆.
-
-## 🔌 `APIRouter` ➕1️⃣
-
-🎏 🌌 👆 💪 🔌 `APIRouter` `FastAPI` 🈸, 👆 💪 🔌 `APIRouter` ➕1️⃣ `APIRouter` ⚙️:
-
-```Python
-router.include_router(other_router)
-```
-
-⚒ 💭 👆 ⚫️ ⏭ 🔌 `router` `FastAPI` 📱, 👈 *➡ 🛠️* ⚪️➡️ `other_router` 🔌.
diff --git a/docs/em/docs/tutorial/body-fields.md b/docs/em/docs/tutorial/body-fields.md
deleted file mode 100644
index f202284b5b..0000000000
--- a/docs/em/docs/tutorial/body-fields.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# 💪 - 🏑
-
-🎏 🌌 👆 💪 📣 🌖 🔬 & 🗃 *➡ 🛠️ 🔢* 🔢 ⏮️ `Query`, `Path` & `Body`, 👆 💪 📣 🔬 & 🗃 🔘 Pydantic 🏷 ⚙️ Pydantic `Field`.
-
-## 🗄 `Field`
-
-🥇, 👆 ✔️ 🗄 ⚫️:
-
-{* ../../docs_src/body_fields/tutorial001.py hl[4] *}
-
-/// warning
-
-👀 👈 `Field` 🗄 🔗 ⚪️➡️ `pydantic`, 🚫 ⚪️➡️ `fastapi` 🌐 🎂 (`Query`, `Path`, `Body`, ♒️).
-
-///
-
-## 📣 🏷 🔢
-
-👆 💪 ⤴️ ⚙️ `Field` ⏮️ 🏷 🔢:
-
-{* ../../docs_src/body_fields/tutorial001.py hl[11:14] *}
-
-`Field` 👷 🎏 🌌 `Query`, `Path` & `Body`, ⚫️ ✔️ 🌐 🎏 🔢, ♒️.
-
-/// note | 📡 ℹ
-
-🤙, `Query`, `Path` & 🎏 👆 🔜 👀 ⏭ ✍ 🎚 🏿 ⚠ `Param` 🎓, ❔ ⚫️ 🏿 Pydantic `FieldInfo` 🎓.
-
- & Pydantic `Field` 📨 👐 `FieldInfo` 👍.
-
-`Body` 📨 🎚 🏿 `FieldInfo` 🔗. & 📤 🎏 👆 🔜 👀 ⏪ 👈 🏿 `Body` 🎓.
-
-💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓.
-
-///
-
-/// tip
-
-👀 ❔ 🔠 🏷 🔢 ⏮️ 🆎, 🔢 💲 & `Field` ✔️ 🎏 📊 *➡ 🛠️ 🔢* 🔢, ⏮️ `Field` ↩️ `Path`, `Query` & `Body`.
-
-///
-
-## 🚮 ➕ ℹ
-
-👆 💪 📣 ➕ ℹ `Field`, `Query`, `Body`, ♒️. & ⚫️ 🔜 🔌 🏗 🎻 🔗.
-
-👆 🔜 💡 🌅 🔃 ❎ ➕ ℹ ⏪ 🩺, 🕐❔ 🏫 📣 🖼.
-
-/// warning
-
-➕ 🔑 🚶♀️ `Field` 🔜 🎁 📉 🗄 🔗 👆 🈸.
-👫 🔑 5️⃣📆 🚫 🎯 🍕 🗄 🔧, 🗄 🧰, 🖼 [🗄 💳](https://validator.swagger.io/), 5️⃣📆 🚫 👷 ⏮️ 👆 🏗 🔗.
-
-///
-
-## 🌃
-
-👆 💪 ⚙️ Pydantic `Field` 📣 ➕ 🔬 & 🗃 🏷 🔢.
-
-👆 💪 ⚙️ ➕ 🇨🇻 ❌ 🚶♀️ 🌖 🎻 🔗 🗃.
diff --git a/docs/em/docs/tutorial/body-multiple-params.md b/docs/em/docs/tutorial/body-multiple-params.md
deleted file mode 100644
index 3a2f2bd547..0000000000
--- a/docs/em/docs/tutorial/body-multiple-params.md
+++ /dev/null
@@ -1,171 +0,0 @@
-# 💪 - 💗 🔢
-
-🔜 👈 👥 ✔️ 👀 ❔ ⚙️ `Path` & `Query`, ➡️ 👀 🌅 🏧 ⚙️ 📨 💪 📄.
-
-## 🌀 `Path`, `Query` & 💪 🔢
-
-🥇, ↗️, 👆 💪 🌀 `Path`, `Query` & 📨 💪 🔢 📄 ➡ & **FastAPI** 🔜 💭 ⚫️❔.
-
-& 👆 💪 📣 💪 🔢 📦, ⚒ 🔢 `None`:
-
-{* ../../docs_src/body_multiple_params/tutorial001.py hl[19:21] *}
-
-/// note
-
-👀 👈, 👉 💼, `item` 👈 🔜 ✊ ⚪️➡️ 💪 📦. ⚫️ ✔️ `None` 🔢 💲.
-
-///
-
-## 💗 💪 🔢
-
-⏮️ 🖼, *➡ 🛠️* 🔜 ⌛ 🎻 💪 ⏮️ 🔢 `Item`, 💖:
-
-```JSON
-{
- "name": "Foo",
- "description": "The pretender",
- "price": 42.0,
- "tax": 3.2
-}
-```
-
-✋️ 👆 💪 📣 💗 💪 🔢, ✅ `item` & `user`:
-
-{* ../../docs_src/body_multiple_params/tutorial002.py hl[22] *}
-
-👉 💼, **FastAPI** 🔜 👀 👈 📤 🌅 🌘 1️⃣ 💪 🔢 🔢 (2️⃣ 🔢 👈 Pydantic 🏷).
-
-, ⚫️ 🔜 ⤴️ ⚙️ 🔢 📛 🔑 (🏑 📛) 💪, & ⌛ 💪 💖:
-
-```JSON
-{
- "item": {
- "name": "Foo",
- "description": "The pretender",
- "price": 42.0,
- "tax": 3.2
- },
- "user": {
- "username": "dave",
- "full_name": "Dave Grohl"
- }
-}
-```
-
-/// note
-
-👀 👈 ✋️ `item` 📣 🎏 🌌 ⏭, ⚫️ 🔜 ⌛ 🔘 💪 ⏮️ 🔑 `item`.
-
-///
-
-**FastAPI** 🔜 🏧 🛠️ ⚪️➡️ 📨, 👈 🔢 `item` 📨 ⚫️ 🎯 🎚 & 🎏 `user`.
-
-⚫️ 🔜 🎭 🔬 ⚗ 💽, & 🔜 📄 ⚫️ 💖 👈 🗄 🔗 & 🏧 🩺.
-
-## ⭐ 💲 💪
-
-🎏 🌌 📤 `Query` & `Path` 🔬 ➕ 💽 🔢 & ➡ 🔢, **FastAPI** 🚚 🌓 `Body`.
-
-🖼, ↔ ⏮️ 🏷, 👆 💪 💭 👈 👆 💚 ✔️ ➕1️⃣ 🔑 `importance` 🎏 💪, 🥈 `item` & `user`.
-
-🚥 👆 📣 ⚫️, ↩️ ⚫️ ⭐ 💲, **FastAPI** 🔜 🤔 👈 ⚫️ 🔢 🔢.
-
-✋️ 👆 💪 💡 **FastAPI** 😥 ⚫️ ➕1️⃣ 💪 🔑 ⚙️ `Body`:
-
-{* ../../docs_src/body_multiple_params/tutorial003.py hl[22] *}
-
-👉 💼, **FastAPI** 🔜 ⌛ 💪 💖:
-
-```JSON
-{
- "item": {
- "name": "Foo",
- "description": "The pretender",
- "price": 42.0,
- "tax": 3.2
- },
- "user": {
- "username": "dave",
- "full_name": "Dave Grohl"
- },
- "importance": 5
-}
-```
-
-🔄, ⚫️ 🔜 🗜 📊 🆎, ✔, 📄, ♒️.
-
-## 💗 💪 = & 🔢
-
-↗️, 👆 💪 📣 🌖 🔢 🔢 🕐❔ 👆 💪, 🌖 🙆 💪 🔢.
-
-, 🔢, ⭐ 💲 🔬 🔢 🔢, 👆 🚫 ✔️ 🎯 🚮 `Query`, 👆 💪:
-
-```Python
-q: Union[str, None] = None
-```
-
-⚖️ 🐍 3️⃣.1️⃣0️⃣ & 🔛:
-
-```Python
-q: str | None = None
-```
-
-🖼:
-
-{* ../../docs_src/body_multiple_params/tutorial004.py hl[27] *}
-
-/// info
-
-`Body` ✔️ 🌐 🎏 ➕ 🔬 & 🗃 🔢 `Query`,`Path` & 🎏 👆 🔜 👀 ⏪.
-
-///
-
-## ⏯ 👁 💪 🔢
-
-➡️ 💬 👆 🕴 ✔️ 👁 `item` 💪 🔢 ⚪️➡️ Pydantic 🏷 `Item`.
-
-🔢, **FastAPI** 🔜 ⤴️ ⌛ 🚮 💪 🔗.
-
-✋️ 🚥 👆 💚 ⚫️ ⌛ 🎻 ⏮️ 🔑 `item` & 🔘 ⚫️ 🏷 🎚, ⚫️ 🔨 🕐❔ 👆 📣 ➕ 💪 🔢, 👆 💪 ⚙️ 🎁 `Body` 🔢 `embed`:
-
-```Python
-item: Item = Body(embed=True)
-```
-
-:
-
-{* ../../docs_src/body_multiple_params/tutorial005.py hl[17] *}
-
-👉 💼 **FastAPI** 🔜 ⌛ 💪 💖:
-
-```JSON hl_lines="2"
-{
- "item": {
- "name": "Foo",
- "description": "The pretender",
- "price": 42.0,
- "tax": 3.2
- }
-}
-```
-
-↩️:
-
-```JSON
-{
- "name": "Foo",
- "description": "The pretender",
- "price": 42.0,
- "tax": 3.2
-}
-```
-
-## 🌃
-
-👆 💪 🚮 💗 💪 🔢 👆 *➡ 🛠️ 🔢*, ✋️ 📨 💪 🕴 ✔️ 👁 💪.
-
-✋️ **FastAPI** 🔜 🍵 ⚫️, 🤝 👆 ☑ 📊 👆 🔢, & ✔ & 📄 ☑ 🔗 *➡ 🛠️*.
-
-👆 💪 📣 ⭐ 💲 📨 🍕 💪.
-
-& 👆 💪 💡 **FastAPI** ⏯ 💪 🔑 🕐❔ 📤 🕴 👁 🔢 📣.
diff --git a/docs/em/docs/tutorial/body-nested-models.md b/docs/em/docs/tutorial/body-nested-models.md
deleted file mode 100644
index 6c8d5a6100..0000000000
--- a/docs/em/docs/tutorial/body-nested-models.md
+++ /dev/null
@@ -1,247 +0,0 @@
-# 💪 - 🔁 🏷
-
-⏮️ **FastAPI**, 👆 💪 🔬, ✔, 📄, & ⚙️ 🎲 🙇 🐦 🏷 (👏 Pydantic).
-
-## 📇 🏑
-
-👆 💪 🔬 🔢 🏾. 🖼, 🐍 `list`:
-
-{* ../../docs_src/body_nested_models/tutorial001.py hl[14] *}
-
-👉 🔜 ⚒ `tags` 📇, 👐 ⚫️ 🚫 📣 🆎 🔣 📇.
-
-## 📇 🏑 ⏮️ 🆎 🔢
-
-✋️ 🐍 ✔️ 🎯 🌌 📣 📇 ⏮️ 🔗 🆎, ⚖️ "🆎 🔢":
-
-### 🗄 ⌨ `List`
-
-🐍 3️⃣.9️⃣ & 🔛 👆 💪 ⚙️ 🐩 `list` 📣 👫 🆎 ✍ 👥 🔜 👀 🔛. 👶
-
-✋️ 🐍 ⏬ ⏭ 3️⃣.9️⃣ (3️⃣.6️⃣ & 🔛), 👆 🥇 💪 🗄 `List` ⚪️➡️ 🐩 🐍 `typing` 🕹:
-
-{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
-
-### 📣 `list` ⏮️ 🆎 🔢
-
-📣 🆎 👈 ✔️ 🆎 🔢 (🔗 🆎), 💖 `list`, `dict`, `tuple`:
-
-* 🚥 👆 🐍 ⏬ 🔅 🌘 3️⃣.9️⃣, 🗄 👫 🌓 ⏬ ⚪️➡️ `typing` 🕹
-* 🚶♀️ 🔗 🆎(Ⓜ) "🆎 🔢" ⚙️ ⬜ 🗜: `[` & `]`
-
-🐍 3️⃣.9️⃣ ⚫️ 🔜:
-
-```Python
-my_list: list[str]
-```
-
-⏬ 🐍 ⏭ 3️⃣.9️⃣, ⚫️ 🔜:
-
-```Python
-from typing import List
-
-my_list: List[str]
-```
-
-👈 🌐 🐩 🐍 ❕ 🆎 📄.
-
-⚙️ 👈 🎏 🐩 ❕ 🏷 🔢 ⏮️ 🔗 🆎.
-
-, 👆 🖼, 👥 💪 ⚒ `tags` 🎯 "📇 🎻":
-
-{* ../../docs_src/body_nested_models/tutorial002.py hl[14] *}
-
-## ⚒ 🆎
-
-✋️ ⤴️ 👥 💭 🔃 ⚫️, & 🤔 👈 🔖 🚫🔜 🚫 🔁, 👫 🔜 🎲 😍 🎻.
-
-& 🐍 ✔️ 🎁 💽 🆎 ⚒ 😍 🏬, `set`.
-
-⤴️ 👥 💪 📣 `tags` ⚒ 🎻:
-
-{* ../../docs_src/body_nested_models/tutorial003.py hl[1,14] *}
-
-⏮️ 👉, 🚥 👆 📨 📨 ⏮️ ❎ 📊, ⚫️ 🔜 🗜 ⚒ 😍 🏬.
-
-& 🕐❔ 👆 🔢 👈 📊, 🚥 ℹ ✔️ ❎, ⚫️ 🔜 🔢 ⚒ 😍 🏬.
-
-& ⚫️ 🔜 ✍ / 📄 ➡️ 💁♂️.
-
-## 🐦 🏷
-
-🔠 🔢 Pydantic 🏷 ✔️ 🆎.
-
-✋️ 👈 🆎 💪 ⚫️ ➕1️⃣ Pydantic 🏷.
-
-, 👆 💪 📣 🙇 🐦 🎻 "🎚" ⏮️ 🎯 🔢 📛, 🆎 & 🔬.
-
-🌐 👈, 🎲 🐦.
-
-### 🔬 📊
-
-🖼, 👥 💪 🔬 `Image` 🏷:
-
-{* ../../docs_src/body_nested_models/tutorial004.py hl[9:11] *}
-
-### ⚙️ 📊 🆎
-
-& ⤴️ 👥 💪 ⚙️ ⚫️ 🆎 🔢:
-
-{* ../../docs_src/body_nested_models/tutorial004.py hl[20] *}
-
-👉 🔜 ⛓ 👈 **FastAPI** 🔜 ⌛ 💪 🎏:
-
-```JSON
-{
- "name": "Foo",
- "description": "The pretender",
- "price": 42.0,
- "tax": 3.2,
- "tags": ["rock", "metal", "bar"],
- "image": {
- "url": "http://example.com/baz.jpg",
- "name": "The Foo live"
- }
-}
-```
-
-🔄, 🤸 👈 📄, ⏮️ **FastAPI** 👆 🤚:
-
-* 👨🎨 🐕🦺 (🛠️, ♒️), 🐦 🏷
-* 💽 🛠️
-* 💽 🔬
-* 🏧 🧾
-
-## 🎁 🆎 & 🔬
-
-↖️ ⚪️➡️ 😐 ⭐ 🆎 💖 `str`, `int`, `float`, ♒️. 👆 💪 ⚙️ 🌅 🏗 ⭐ 🆎 👈 😖 ⚪️➡️ `str`.
-
-👀 🌐 🎛 👆 ✔️, 🛒 🩺 Pydantic 😍 🆎. 👆 🔜 👀 🖼 ⏭ 📃.
-
-🖼, `Image` 🏷 👥 ✔️ `url` 🏑, 👥 💪 📣 ⚫️ ↩️ `str`, Pydantic `HttpUrl`:
-
-{* ../../docs_src/body_nested_models/tutorial005.py hl[4,10] *}
-
-🎻 🔜 ✅ ☑ 📛, & 📄 🎻 🔗 / 🗄 ✅.
-
-## 🔢 ⏮️ 📇 📊
-
-👆 💪 ⚙️ Pydantic 🏷 🏾 `list`, `set`, ♒️:
-
-{* ../../docs_src/body_nested_models/tutorial006.py hl[20] *}
-
-👉 🔜 ⌛ (🗜, ✔, 📄, ♒️) 🎻 💪 💖:
-
-```JSON hl_lines="11"
-{
- "name": "Foo",
- "description": "The pretender",
- "price": 42.0,
- "tax": 3.2,
- "tags": [
- "rock",
- "metal",
- "bar"
- ],
- "images": [
- {
- "url": "http://example.com/baz.jpg",
- "name": "The Foo live"
- },
- {
- "url": "http://example.com/dave.jpg",
- "name": "The Baz"
- }
- ]
-}
-```
-
-/// info
-
-👀 ❔ `images` 🔑 🔜 ✔️ 📇 🖼 🎚.
-
-///
-
-## 🙇 🐦 🏷
-
-👆 💪 🔬 🎲 🙇 🐦 🏷:
-
-{* ../../docs_src/body_nested_models/tutorial007.py hl[9,14,20,23,27] *}
-
-/// info
-
-👀 ❔ `Offer` ✔️ 📇 `Item`Ⓜ, ❔ 🔄 ✔️ 📦 📇 `Image`Ⓜ
-
-///
-
-## 💪 😁 📇
-
-🚥 🔝 🎚 💲 🎻 💪 👆 ⌛ 🎻 `array` (🐍 `list`), 👆 💪 📣 🆎 🔢 🔢, 🎏 Pydantic 🏷:
-
-```Python
-images: List[Image]
-```
-
-⚖️ 🐍 3️⃣.9️⃣ & 🔛:
-
-```Python
-images: list[Image]
-```
-
-:
-
-{* ../../docs_src/body_nested_models/tutorial008.py hl[15] *}
-
-## 👨🎨 🐕🦺 🌐
-
-& 👆 🤚 👨🎨 🐕🦺 🌐.
-
-🏬 🔘 📇:
-
-
-
-👆 🚫 🚫 🤚 👉 😇 👨🎨 🐕🦺 🚥 👆 👷 🔗 ⏮️ `dict` ↩️ Pydantic 🏷.
-
-✋️ 👆 🚫 ✔️ 😟 🔃 👫 👯♂️, 📨 #️⃣ 🗜 🔁 & 👆 🔢 🗜 🔁 🎻 💁♂️.
-
-## 💪 ❌ `dict`Ⓜ
-
-👆 💪 📣 💪 `dict` ⏮️ 🔑 🆎 & 💲 🎏 🆎.
-
-🍵 ✔️ 💭 ⏪ ⚫️❔ ☑ 🏑/🔢 📛 (🔜 💼 ⏮️ Pydantic 🏷).
-
-👉 🔜 ⚠ 🚥 👆 💚 📨 🔑 👈 👆 🚫 ⏪ 💭.
-
----
-
-🎏 ⚠ 💼 🕐❔ 👆 💚 ✔️ 🔑 🎏 🆎, ✅ `int`.
-
-👈 ⚫️❔ 👥 🔜 👀 📥.
-
-👉 💼, 👆 🔜 🚫 🙆 `dict` 📏 ⚫️ ✔️ `int` 🔑 ⏮️ `float` 💲:
-
-{* ../../docs_src/body_nested_models/tutorial009.py hl[9] *}
-
-/// tip
-
-✔️ 🤯 👈 🎻 🕴 🐕🦺 `str` 🔑.
-
-✋️ Pydantic ✔️ 🏧 💽 🛠️.
-
-👉 ⛓ 👈, ✋️ 👆 🛠️ 👩💻 💪 🕴 📨 🎻 🔑, 📏 👈 🎻 🔌 😁 🔢, Pydantic 🔜 🗜 👫 & ✔ 👫.
-
- & `dict` 👆 📨 `weights` 🔜 🤙 ✔️ `int` 🔑 & `float` 💲.
-
-///
-
-## 🌃
-
-⏮️ **FastAPI** 👆 ✔️ 🔆 💪 🚚 Pydantic 🏷, ⏪ 🚧 👆 📟 🙅, 📏 & 😍.
-
-✋️ ⏮️ 🌐 💰:
-
-* 👨🎨 🐕🦺 (🛠️ 🌐 ❗)
-* 💽 🛠️ (.Ⓜ.. ✍ / 🛠️)
-* 💽 🔬
-* 🔗 🧾
-* 🏧 🩺
diff --git a/docs/em/docs/tutorial/body-updates.md b/docs/em/docs/tutorial/body-updates.md
deleted file mode 100644
index 7e2fbfaf79..0000000000
--- a/docs/em/docs/tutorial/body-updates.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# 💪 - ℹ
-
-## ℹ ❎ ⏮️ `PUT`
-
-ℹ 🏬 👆 💪 ⚙️ 🇺🇸🔍 `PUT` 🛠️.
-
-👆 💪 ⚙️ `jsonable_encoder` 🗜 🔢 💽 📊 👈 💪 🏪 🎻 (✅ ⏮️ ☁ 💽). 🖼, 🏭 `datetime` `str`.
-
-{* ../../docs_src/body_updates/tutorial001.py hl[30:35] *}
-
-`PUT` ⚙️ 📨 💽 👈 🔜 ❎ ♻ 💽.
-
-### ⚠ 🔃 ❎
-
-👈 ⛓ 👈 🚥 👆 💚 ℹ 🏬 `bar` ⚙️ `PUT` ⏮️ 💪 ⚗:
-
-```Python
-{
- "name": "Barz",
- "price": 3,
- "description": None,
-}
-```
-
-↩️ ⚫️ 🚫 🔌 ⏪ 🏪 🔢 `"tax": 20.2`, 🔢 🏷 🔜 ✊ 🔢 💲 `"tax": 10.5`.
-
-& 📊 🔜 🖊 ⏮️ 👈 "🆕" `tax` `10.5`.
-
-## 🍕 ℹ ⏮️ `PATCH`
-
-👆 💪 ⚙️ 🇺🇸🔍 `PATCH` 🛠️ *🍕* ℹ 💽.
-
-👉 ⛓ 👈 👆 💪 📨 🕴 💽 👈 👆 💚 ℹ, 🍂 🎂 🐣.
-
-/// note
-
-`PATCH` 🌘 🛎 ⚙️ & 💭 🌘 `PUT`.
-
- & 📚 🏉 ⚙️ 🕴 `PUT`, 🍕 ℹ.
-
-👆 **🆓** ⚙️ 👫 👐 👆 💚, **FastAPI** 🚫 🚫 🙆 🚫.
-
-✋️ 👉 🦮 🎦 👆, 🌖 ⚖️ 🌘, ❔ 👫 🎯 ⚙️.
-
-///
-
-### ⚙️ Pydantic `exclude_unset` 🔢
-
-🚥 👆 💚 📨 🍕 ℹ, ⚫️ 📶 ⚠ ⚙️ 🔢 `exclude_unset` Pydantic 🏷 `.dict()`.
-
-💖 `item.dict(exclude_unset=True)`.
-
-👈 🔜 🏗 `dict` ⏮️ 🕴 💽 👈 ⚒ 🕐❔ 🏗 `item` 🏷, 🚫 🔢 💲.
-
-⤴️ 👆 💪 ⚙️ 👉 🏗 `dict` ⏮️ 🕴 💽 👈 ⚒ (📨 📨), 🚫 🔢 💲:
-
-{* ../../docs_src/body_updates/tutorial002.py hl[34] *}
-
-### ⚙️ Pydantic `update` 🔢
-
-🔜, 👆 💪 ✍ 📁 ♻ 🏷 ⚙️ `.copy()`, & 🚶♀️ `update` 🔢 ⏮️ `dict` ⚗ 💽 ℹ.
-
-💖 `stored_item_model.copy(update=update_data)`:
-
-{* ../../docs_src/body_updates/tutorial002.py hl[35] *}
-
-### 🍕 ℹ 🌃
-
-📄, ✔ 🍕 ℹ 👆 🔜:
-
-* (⚗) ⚙️ `PATCH` ↩️ `PUT`.
-* 🗃 🏪 💽.
-* 🚮 👈 💽 Pydantic 🏷.
-* 🏗 `dict` 🍵 🔢 💲 ⚪️➡️ 🔢 🏷 (⚙️ `exclude_unset`).
- * 👉 🌌 👆 💪 ℹ 🕴 💲 🤙 ⚒ 👩💻, ↩️ 🔐 💲 ⏪ 🏪 ⏮️ 🔢 💲 👆 🏷.
-* ✍ 📁 🏪 🏷, 🛠️ ⚫️ 🔢 ⏮️ 📨 🍕 ℹ (⚙️ `update` 🔢).
-* 🗜 📁 🏷 🕳 👈 💪 🏪 👆 💽 (🖼, ⚙️ `jsonable_encoder`).
- * 👉 ⭐ ⚙️ 🏷 `.dict()` 👩🔬 🔄, ✋️ ⚫️ ⚒ 💭 (& 🗜) 💲 💽 🆎 👈 💪 🗜 🎻, 🖼, `datetime` `str`.
-* 🖊 💽 👆 💽.
-* 📨 ℹ 🏷.
-
-{* ../../docs_src/body_updates/tutorial002.py hl[30:37] *}
-
-/// tip
-
-👆 💪 🤙 ⚙️ 👉 🎏 ⚒ ⏮️ 🇺🇸🔍 `PUT` 🛠️.
-
-✋️ 🖼 📥 ⚙️ `PATCH` ↩️ ⚫️ ✍ 👫 ⚙️ 💼.
-
-///
-
-/// note
-
-👀 👈 🔢 🏷 ✔.
-
-, 🚥 👆 💚 📨 🍕 ℹ 👈 💪 🚫 🌐 🔢, 👆 💪 ✔️ 🏷 ⏮️ 🌐 🔢 ™ 📦 (⏮️ 🔢 💲 ⚖️ `None`).
-
-🔬 ⚪️➡️ 🏷 ⏮️ 🌐 📦 💲 **ℹ** & 🏷 ⏮️ ✔ 💲 **🏗**, 👆 💪 ⚙️ 💭 🔬 [➕ 🏷](extra-models.md){.internal-link target=_blank}.
-
-///
diff --git a/docs/em/docs/tutorial/body.md b/docs/em/docs/tutorial/body.md
deleted file mode 100644
index 09e1d7ccaa..0000000000
--- a/docs/em/docs/tutorial/body.md
+++ /dev/null
@@ -1,162 +0,0 @@
-# 📨 💪
-
-🕐❔ 👆 💪 📨 📊 ⚪️➡️ 👩💻 (➡️ 💬, 🖥) 👆 🛠️, 👆 📨 ⚫️ **📨 💪**.
-
-**📨** 💪 📊 📨 👩💻 👆 🛠️. **📨** 💪 💽 👆 🛠️ 📨 👩💻.
-
-👆 🛠️ 🌖 🕧 ✔️ 📨 **📨** 💪. ✋️ 👩💻 🚫 🎯 💪 📨 **📨** 💪 🌐 🕰.
-
-📣 **📨** 💪, 👆 ⚙️ Pydantic 🏷 ⏮️ 🌐 👫 🏋️ & 💰.
-
-/// info
-
-📨 💽, 👆 🔜 ⚙️ 1️⃣: `POST` (🌅 ⚠), `PUT`, `DELETE` ⚖️ `PATCH`.
-
-📨 💪 ⏮️ `GET` 📨 ✔️ ⚠ 🎭 🔧, 👐, ⚫️ 🐕🦺 FastAPI, 🕴 📶 🏗/😕 ⚙️ 💼.
-
-⚫️ 🚫, 🎓 🩺 ⏮️ 🦁 🎚 🏆 🚫 🎦 🧾 💪 🕐❔ ⚙️ `GET`, & 🗳 🖕 💪 🚫 🐕🦺 ⚫️.
-
-///
-
-## 🗄 Pydantic `BaseModel`
-
-🥇, 👆 💪 🗄 `BaseModel` ⚪️➡️ `pydantic`:
-
-{* ../../docs_src/body/tutorial001.py hl[4] *}
-
-## ✍ 👆 💽 🏷
-
-⤴️ 👆 📣 👆 💽 🏷 🎓 👈 😖 ⚪️➡️ `BaseModel`.
-
-⚙️ 🐩 🐍 🆎 🌐 🔢:
-
-{* ../../docs_src/body/tutorial001.py hl[7:11] *}
-
-🎏 🕐❔ 📣 🔢 🔢, 🕐❔ 🏷 🔢 ✔️ 🔢 💲, ⚫️ 🚫 ✔. ⏪, ⚫️ ✔. ⚙️ `None` ⚒ ⚫️ 📦.
-
-🖼, 👉 🏷 🔛 📣 🎻 "`object`" (⚖️ 🐍 `dict`) 💖:
-
-```JSON
-{
- "name": "Foo",
- "description": "An optional description",
- "price": 45.2,
- "tax": 3.5
-}
-```
-
-... `description` & `tax` 📦 (⏮️ 🔢 💲 `None`), 👉 🎻 "`object`" 🔜 ☑:
-
-```JSON
-{
- "name": "Foo",
- "price": 45.2
-}
-```
-
-## 📣 ⚫️ 🔢
-
-🚮 ⚫️ 👆 *➡ 🛠️*, 📣 ⚫️ 🎏 🌌 👆 📣 ➡ & 🔢 🔢:
-
-{* ../../docs_src/body/tutorial001.py hl[18] *}
-
-...& 📣 🚮 🆎 🏷 👆 ✍, `Item`.
-
-## 🏁
-
-⏮️ 👈 🐍 🆎 📄, **FastAPI** 🔜:
-
-* ✍ 💪 📨 🎻.
-* 🗜 🔗 🆎 (🚥 💪).
-* ✔ 💽.
- * 🚥 💽 ❌, ⚫️ 🔜 📨 👌 & 🆑 ❌, ☠️ ⚫️❔ 🌐❔ & ⚫️❔ ❌ 📊.
-* 🤝 👆 📨 📊 🔢 `item`.
- * 👆 📣 ⚫️ 🔢 🆎 `Item`, 👆 🔜 ✔️ 🌐 👨🎨 🐕🦺 (🛠️, ♒️) 🌐 🔢 & 👫 🆎.
-* 🏗 🎻 🔗 🔑 👆 🏷, 👆 💪 ⚙️ 👫 🙆 🙆 👆 💖 🚥 ⚫️ ⚒ 🔑 👆 🏗.
-* 👈 🔗 🔜 🍕 🏗 🗄 🔗, & ⚙️ 🏧 🧾 ⚜.
-
-## 🏧 🩺
-
-🎻 🔗 👆 🏷 🔜 🍕 👆 🗄 🏗 🔗, & 🔜 🎦 🎓 🛠️ 🩺:
-
-
-
-& 🔜 ⚙️ 🛠️ 🩺 🔘 🔠 *➡ 🛠️* 👈 💪 👫:
-
-
-
-## 👨🎨 🐕🦺
-
-👆 👨🎨, 🔘 👆 🔢 👆 🔜 🤚 🆎 🔑 & 🛠️ 🌐 (👉 🚫🔜 🔨 🚥 👆 📨 `dict` ↩️ Pydantic 🏷):
-
-
-
-👆 🤚 ❌ ✅ ❌ 🆎 🛠️:
-
-
-
-👉 🚫 🤞, 🎂 🛠️ 🏗 🤭 👈 🔧.
-
-& ⚫️ 🙇 💯 🔧 🌓, ⏭ 🙆 🛠️, 🚚 ⚫️ 🔜 👷 ⏮️ 🌐 👨🎨.
-
-📤 🔀 Pydantic ⚫️ 🐕🦺 👉.
-
-⏮️ 🖼 ✊ ⏮️ 🎙 🎙 📟.
-
-✋️ 👆 🔜 🤚 🎏 👨🎨 🐕🦺 ⏮️ 🗒 & 🌅 🎏 🐍 👨🎨:
-
-
-
-/// tip
-
-🚥 👆 ⚙️ 🗒 👆 👨🎨, 👆 💪 ⚙️ Pydantic 🗒 📁.
-
-⚫️ 📉 👨🎨 🐕🦺 Pydantic 🏷, ⏮️:
-
-* 🚘-🛠️
-* 🆎 ✅
-* 🛠️
-* 🔎
-* 🔬
-
-///
-
-## ⚙️ 🏷
-
-🔘 🔢, 👆 💪 🔐 🌐 🔢 🏷 🎚 🔗:
-
-{* ../../docs_src/body/tutorial002.py hl[21] *}
-
-## 📨 💪 ➕ ➡ 🔢
-
-👆 💪 📣 ➡ 🔢 & 📨 💪 🎏 🕰.
-
-**FastAPI** 🔜 🤔 👈 🔢 🔢 👈 🏏 ➡ 🔢 🔜 **✊ ⚪️➡️ ➡**, & 👈 🔢 🔢 👈 📣 Pydantic 🏷 🔜 **✊ ⚪️➡️ 📨 💪**.
-
-{* ../../docs_src/body/tutorial003.py hl[17:18] *}
-
-## 📨 💪 ➕ ➡ ➕ 🔢 🔢
-
-👆 💪 📣 **💪**, **➡** & **🔢** 🔢, 🌐 🎏 🕰.
-
-**FastAPI** 🔜 🤔 🔠 👫 & ✊ 📊 ⚪️➡️ ☑ 🥉.
-
-{* ../../docs_src/body/tutorial004.py hl[18] *}
-
-🔢 🔢 🔜 🤔 ⏩:
-
-* 🚥 🔢 📣 **➡**, ⚫️ 🔜 ⚙️ ➡ 🔢.
-* 🚥 🔢 **⭐ 🆎** (💖 `int`, `float`, `str`, `bool`, ♒️) ⚫️ 🔜 🔬 **🔢** 🔢.
-* 🚥 🔢 📣 🆎 **Pydantic 🏷**, ⚫️ 🔜 🔬 📨 **💪**.
-
-/// note
-
-FastAPI 🔜 💭 👈 💲 `q` 🚫 ✔ ↩️ 🔢 💲 `= None`.
-
- `Union` `Union[str, None]` 🚫 ⚙️ FastAPI, ✋️ 🔜 ✔ 👆 👨🎨 🤝 👆 👍 🐕🦺 & 🔍 ❌.
-
-///
-
-## 🍵 Pydantic
-
-🚥 👆 🚫 💚 ⚙️ Pydantic 🏷, 👆 💪 ⚙️ **💪** 🔢. 👀 🩺 [💪 - 💗 🔢: ⭐ 💲 💪](body-multiple-params.md#_2){.internal-link target=_blank}.
diff --git a/docs/em/docs/tutorial/cookie-params.md b/docs/em/docs/tutorial/cookie-params.md
deleted file mode 100644
index 4699fe2a54..0000000000
--- a/docs/em/docs/tutorial/cookie-params.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# 🍪 🔢
-
-👆 💪 🔬 🍪 🔢 🎏 🌌 👆 🔬 `Query` & `Path` 🔢.
-
-## 🗄 `Cookie`
-
-🥇 🗄 `Cookie`:
-
-{* ../../docs_src/cookie_params/tutorial001.py hl[3] *}
-
-## 📣 `Cookie` 🔢
-
-⤴️ 📣 🍪 🔢 ⚙️ 🎏 📊 ⏮️ `Path` & `Query`.
-
-🥇 💲 🔢 💲, 👆 💪 🚶♀️ 🌐 ➕ 🔬 ⚖️ ✍ 🔢:
-
-{* ../../docs_src/cookie_params/tutorial001.py hl[9] *}
-
-/// note | 📡 ℹ
-
-`Cookie` "👭" 🎓 `Path` & `Query`. ⚫️ 😖 ⚪️➡️ 🎏 ⚠ `Param` 🎓.
-
-✋️ 💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, `Cookie` & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓.
-
-///
-
-/// info
-
-📣 🍪, 👆 💪 ⚙️ `Cookie`, ↩️ ⏪ 🔢 🔜 🔬 🔢 🔢.
-
-///
-
-## 🌃
-
-📣 🍪 ⏮️ `Cookie`, ⚙️ 🎏 ⚠ ⚓ `Query` & `Path`.
diff --git a/docs/em/docs/tutorial/cors.md b/docs/em/docs/tutorial/cors.md
deleted file mode 100644
index 44ab4adc59..0000000000
--- a/docs/em/docs/tutorial/cors.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# ⚜ (✖️-🇨🇳 ℹ 🤝)
-
-⚜ ⚖️ "✖️-🇨🇳 ℹ 🤝" 🔗 ⚠ 🕐❔ 🕸 🏃♂ 🖥 ✔️ 🕸 📟 👈 🔗 ⏮️ 👩💻, & 👩💻 🎏 "🇨🇳" 🌘 🕸.
-
-## 🇨🇳
-
-🇨🇳 🌀 🛠️ (`http`, `https`), 🆔 (`myapp.com`, `localhost`, `localhost.tiangolo.com`), & ⛴ (`80`, `443`, `8080`).
-
-, 🌐 👫 🎏 🇨🇳:
-
-* `http://localhost`
-* `https://localhost`
-* `http://localhost:8080`
-
-🚥 👫 🌐 `localhost`, 👫 ⚙️ 🎏 🛠️ ⚖️ ⛴,, 👫 🎏 "🇨🇳".
-
-## 🔁
-
-, ➡️ 💬 👆 ✔️ 🕸 🏃 👆 🖥 `http://localhost:8080`, & 🚮 🕸 🔄 🔗 ⏮️ 👩💻 🏃 `http://localhost` (↩️ 👥 🚫 ✔ ⛴, 🖥 🔜 🤔 🔢 ⛴ `80`).
-
-⤴️, 🖥 🔜 📨 🇺🇸🔍 `OPTIONS` 📨 👩💻, & 🚥 👩💻 📨 ☑ 🎚 ✔ 📻 ⚪️➡️ 👉 🎏 🇨🇳 (`http://localhost:8080`) ⤴️ 🖥 🔜 ➡️ 🕸 🕸 📨 🚮 📨 👩💻.
-
-🏆 👉, 👩💻 🔜 ✔️ 📇 "✔ 🇨🇳".
-
-👉 💼, ⚫️ 🔜 ✔️ 🔌 `http://localhost:8080` 🕸 👷 ☑.
-
-## 🃏
-
-⚫️ 💪 📣 📇 `"*"` ("🃏") 💬 👈 🌐 ✔.
-
-✋️ 👈 🔜 🕴 ✔ 🎯 🆎 📻, 🚫 🌐 👈 🔌 🎓: 🍪, ✔ 🎚 💖 📚 ⚙️ ⏮️ 📨 🤝, ♒️.
-
-, 🌐 👷 ☑, ⚫️ 👻 ✔ 🎯 ✔ 🇨🇳.
-
-## ⚙️ `CORSMiddleware`
-
-👆 💪 🔗 ⚫️ 👆 **FastAPI** 🈸 ⚙️ `CORSMiddleware`.
-
-* 🗄 `CORSMiddleware`.
-* ✍ 📇 ✔ 🇨🇳 (🎻).
-* 🚮 ⚫️ "🛠️" 👆 **FastAPI** 🈸.
-
-👆 💪 ✔ 🚥 👆 👩💻 ✔:
-
-* 🎓 (✔ 🎚, 🍪, ♒️).
-* 🎯 🇺🇸🔍 👩🔬 (`POST`, `PUT`) ⚖️ 🌐 👫 ⏮️ 🃏 `"*"`.
-* 🎯 🇺🇸🔍 🎚 ⚖️ 🌐 👫 ⏮️ 🃏 `"*"`.
-
-{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
-
-🔢 🔢 ⚙️ `CORSMiddleware` 🛠️ 🚫 🔢, 👆 🔜 💪 🎯 🛠️ 🎯 🇨🇳, 👩🔬, ⚖️ 🎚, ✔ 🖥 ✔ ⚙️ 👫 ✖️-🆔 🔑.
-
-📄 ❌ 🐕🦺:
-
-* `allow_origins` - 📇 🇨🇳 👈 🔜 ✔ ⚒ ✖️-🇨🇳 📨. 🤶 Ⓜ. `['https://example.org', 'https://www.example.org']`. 👆 💪 ⚙️ `['*']` ✔ 🙆 🇨🇳.
-* `allow_origin_regex` - 🎻 🎻 🏏 🛡 🇨🇳 👈 🔜 ✔ ⚒ ✖️-🇨🇳 📨. ✅ `'https://.*\.example\.org'`.
-* `allow_methods` - 📇 🇺🇸🔍 👩🔬 👈 🔜 ✔ ✖️-🇨🇳 📨. 🔢 `['GET']`. 👆 💪 ⚙️ `['*']` ✔ 🌐 🐩 👩🔬.
-* `allow_headers` - 📇 🇺🇸🔍 📨 🎚 👈 🔜 🐕🦺 ✖️-🇨🇳 📨. 🔢 `[]`. 👆 💪 ⚙️ `['*']` ✔ 🌐 🎚. `Accept`, `Accept-Language`, `Content-Language` & `Content-Type` 🎚 🕧 ✔ 🙅 ⚜ 📨.
-* `allow_credentials` - 🎦 👈 🍪 🔜 🐕🦺 ✖️-🇨🇳 📨. 🔢 `False`. , `allow_origins` 🚫🔜 ⚒ `['*']` 🎓 ✔, 🇨🇳 🔜 ✔.
-* `expose_headers` - 🎦 🙆 📨 🎚 👈 🔜 ⚒ ♿ 🖥. 🔢 `[]`.
-* `max_age` - ⚒ 🔆 🕰 🥈 🖥 💾 ⚜ 📨. 🔢 `600`.
-
-🛠️ 📨 2️⃣ 🎯 🆎 🇺🇸🔍 📨...
-
-### ⚜ 🛫 📨
-
-👉 🙆 `OPTIONS` 📨 ⏮️ `Origin` & `Access-Control-Request-Method` 🎚.
-
-👉 💼 🛠️ 🔜 🆘 📨 📨 & 📨 ⏮️ ☑ ⚜ 🎚, & 👯♂️ `200` ⚖️ `400` 📨 🎓 🎯.
-
-### 🙅 📨
-
-🙆 📨 ⏮️ `Origin` 🎚. 👉 💼 🛠️ 🔜 🚶♀️ 📨 🔘 😐, ✋️ 🔜 🔌 ☑ ⚜ 🎚 🔛 📨.
-
-## 🌅 ℹ
-
-🌖 ℹ 🔃 ⚜, ✅ 🦎 ⚜ 🧾.
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.middleware.cors import CORSMiddleware`.
-
-**FastAPI** 🚚 📚 🛠️ `fastapi.middleware` 🏪 👆, 👩💻. ✋️ 🌅 💪 🛠️ 👟 🔗 ⚪️➡️ 💃.
-
-///
diff --git a/docs/em/docs/tutorial/debugging.md b/docs/em/docs/tutorial/debugging.md
deleted file mode 100644
index 97e61a763f..0000000000
--- a/docs/em/docs/tutorial/debugging.md
+++ /dev/null
@@ -1,113 +0,0 @@
-# 🛠️
-
-👆 💪 🔗 🕹 👆 👨🎨, 🖼 ⏮️ 🎙 🎙 📟 ⚖️ 🗒.
-
-## 🤙 `uvicorn`
-
-👆 FastAPI 🈸, 🗄 & 🏃 `uvicorn` 🔗:
-
-{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
-
-### 🔃 `__name__ == "__main__"`
-
-👑 🎯 `__name__ == "__main__"` ✔️ 📟 👈 🛠️ 🕐❔ 👆 📁 🤙 ⏮️:
-
-
-
----
-
-🚥 👆 ⚙️ 🗒, 👆 💪:
-
-* 📂 "🏃" 🍣.
-* 🖊 🎛 "ℹ...".
-* ⤴️ 🔑 🍣 🎦 🆙.
-* 🖊 📁 ℹ (👉 💼, `main.py`).
-
-⚫️ 🔜 ⤴️ ▶️ 💽 ⏮️ 👆 **FastAPI** 📟, ⛔️ 👆 0️⃣, ♒️.
-
-📥 ❔ ⚫️ 💪 👀:
-
-
diff --git a/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md
deleted file mode 100644
index 41938bc7b8..0000000000
--- a/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md
+++ /dev/null
@@ -1,180 +0,0 @@
-# 🎓 🔗
-
-⏭ 🤿 ⏬ 🔘 **🔗 💉** ⚙️, ➡️ ♻ ⏮️ 🖼.
-
-## `dict` ⚪️➡️ ⏮️ 🖼
-
-⏮️ 🖼, 👥 🛬 `dict` ⚪️➡️ 👆 🔗 ("☑"):
-
-{* ../../docs_src/dependencies/tutorial001.py hl[9] *}
-
-✋️ ⤴️ 👥 🤚 `dict` 🔢 `commons` *➡ 🛠️ 🔢*.
-
-& 👥 💭 👈 👨🎨 💪 🚫 🚚 📚 🐕🦺 (💖 🛠️) `dict`Ⓜ, ↩️ 👫 💪 🚫 💭 👫 🔑 & 💲 🆎.
-
-👥 💪 👍...
-
-## ⚫️❔ ⚒ 🔗
-
-🆙 🔜 👆 ✔️ 👀 🔗 📣 🔢.
-
-✋️ 👈 🚫 🕴 🌌 📣 🔗 (👐 ⚫️ 🔜 🎲 🌖 ⚠).
-
-🔑 ⚖ 👈 🔗 🔜 "🇧🇲".
-
-"**🇧🇲**" 🐍 🕳 👈 🐍 💪 "🤙" 💖 🔢.
-
-, 🚥 👆 ✔️ 🎚 `something` (👈 💪 _🚫_ 🔢) & 👆 💪 "🤙" ⚫️ (🛠️ ⚫️) 💖:
-
-```Python
-something()
-```
-
-⚖️
-
-```Python
-something(some_argument, some_keyword_argument="foo")
-```
-
-⤴️ ⚫️ "🇧🇲".
-
-## 🎓 🔗
-
-👆 5️⃣📆 👀 👈 ✍ 👐 🐍 🎓, 👆 ⚙️ 👈 🎏 ❕.
-
-🖼:
-
-```Python
-class Cat:
- def __init__(self, name: str):
- self.name = name
-
-
-fluffy = Cat(name="Mr Fluffy")
-```
-
-👉 💼, `fluffy` 👐 🎓 `Cat`.
-
-& ✍ `fluffy`, 👆 "🤙" `Cat`.
-
-, 🐍 🎓 **🇧🇲**.
-
-⤴️, **FastAPI**, 👆 💪 ⚙️ 🐍 🎓 🔗.
-
-⚫️❔ FastAPI 🤙 ✅ 👈 ⚫️ "🇧🇲" (🔢, 🎓 ⚖️ 🕳 🙆) & 🔢 🔬.
-
-🚥 👆 🚶♀️ "🇧🇲" 🔗 **FastAPI**, ⚫️ 🔜 🔬 🔢 👈 "🇧🇲", & 🛠️ 👫 🎏 🌌 🔢 *➡ 🛠️ 🔢*. ✅ 🎧-🔗.
-
-👈 ✔ 🇧🇲 ⏮️ 🙅♂ 🔢 🌐. 🎏 ⚫️ 🔜 *➡ 🛠️ 🔢* ⏮️ 🙅♂ 🔢.
-
-⤴️, 👥 💪 🔀 🔗 "☑" `common_parameters` ⚪️➡️ 🔛 🎓 `CommonQueryParams`:
-
-{* ../../docs_src/dependencies/tutorial002.py hl[11:15] *}
-
-💸 🙋 `__init__` 👩🔬 ⚙️ ✍ 👐 🎓:
-
-{* ../../docs_src/dependencies/tutorial002.py hl[12] *}
-
-...⚫️ ✔️ 🎏 🔢 👆 ⏮️ `common_parameters`:
-
-{* ../../docs_src/dependencies/tutorial001.py hl[9] *}
-
-📚 🔢 ⚫️❔ **FastAPI** 🔜 ⚙️ "❎" 🔗.
-
-👯♂️ 💼, ⚫️ 🔜 ✔️:
-
-* 📦 `q` 🔢 🔢 👈 `str`.
-* `skip` 🔢 🔢 👈 `int`, ⏮️ 🔢 `0`.
-* `limit` 🔢 🔢 👈 `int`, ⏮️ 🔢 `100`.
-
-👯♂️ 💼 💽 🔜 🗜, ✔, 📄 🔛 🗄 🔗, ♒️.
-
-## ⚙️ ⚫️
-
-🔜 👆 💪 📣 👆 🔗 ⚙️ 👉 🎓.
-
-{* ../../docs_src/dependencies/tutorial002.py hl[19] *}
-
-**FastAPI** 🤙 `CommonQueryParams` 🎓. 👉 ✍ "👐" 👈 🎓 & 👐 🔜 🚶♀️ 🔢 `commons` 👆 🔢.
-
-## 🆎 ✍ 🆚 `Depends`
-
-👀 ❔ 👥 ✍ `CommonQueryParams` 🕐 🔛 📟:
-
-```Python
-commons: CommonQueryParams = Depends(CommonQueryParams)
-```
-
-🏁 `CommonQueryParams`,:
-
-```Python
-... = Depends(CommonQueryParams)
-```
-
-...⚫️❔ **FastAPI** 🔜 🤙 ⚙️ 💭 ⚫️❔ 🔗.
-
-⚪️➡️ ⚫️ 👈 FastAPI 🔜 ⚗ 📣 🔢 & 👈 ⚫️❔ FastAPI 🔜 🤙 🤙.
-
----
-
-👉 💼, 🥇 `CommonQueryParams`,:
-
-```Python
-commons: CommonQueryParams ...
-```
-
-...🚫 ✔️ 🙆 🎁 🔑 **FastAPI**. FastAPI 🏆 🚫 ⚙️ ⚫️ 💽 🛠️, 🔬, ♒️. (⚫️ ⚙️ `= Depends(CommonQueryParams)` 👈).
-
-👆 💪 🤙 ✍:
-
-```Python
-commons = Depends(CommonQueryParams)
-```
-
-...:
-
-{* ../../docs_src/dependencies/tutorial003.py hl[19] *}
-
-✋️ 📣 🆎 💡 👈 🌌 👆 👨🎨 🔜 💭 ⚫️❔ 🔜 🚶♀️ 🔢 `commons`, & ⤴️ ⚫️ 💪 ℹ 👆 ⏮️ 📟 🛠️, 🆎 ✅, ♒️:
-
-
-
-## ⌨
-
-✋️ 👆 👀 👈 👥 ✔️ 📟 🔁 📥, ✍ `CommonQueryParams` 🕐:
-
-```Python
-commons: CommonQueryParams = Depends(CommonQueryParams)
-```
-
-**FastAPI** 🚚 ⌨ 👫 💼, 🌐❔ 🔗 *🎯* 🎓 👈 **FastAPI** 🔜 "🤙" ✍ 👐 🎓 ⚫️.
-
-📚 🎯 💼, 👆 💪 📄:
-
-↩️ ✍:
-
-```Python
-commons: CommonQueryParams = Depends(CommonQueryParams)
-```
-
-...👆 ✍:
-
-```Python
-commons: CommonQueryParams = Depends()
-```
-
-👆 📣 🔗 🆎 🔢, & 👆 ⚙️ `Depends()` 🚮 "🔢" 💲 (👈 ⏮️ `=`) 👈 🔢 🔢, 🍵 🙆 🔢 `Depends()`, ↩️ ✔️ ✍ 🌕 🎓 *🔄* 🔘 `Depends(CommonQueryParams)`.
-
-🎏 🖼 🔜 ⤴️ 👀 💖:
-
-{* ../../docs_src/dependencies/tutorial004.py hl[19] *}
-
-...& **FastAPI** 🔜 💭 ⚫️❔.
-
-/// tip
-
-🚥 👈 😑 🌅 😨 🌘 👍, 🤷♂ ⚫️, 👆 🚫 *💪* ⚫️.
-
-⚫️ ⌨. ↩️ **FastAPI** 💅 🔃 🤝 👆 📉 📟 🔁.
-
-///
diff --git a/docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
deleted file mode 100644
index ab144a497b..0000000000
--- a/docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# 🔗 ➡ 🛠️ 👨🎨
-
-💼 👆 🚫 🤙 💪 📨 💲 🔗 🔘 👆 *➡ 🛠️ 🔢*.
-
-⚖️ 🔗 🚫 📨 💲.
-
-✋️ 👆 💪 ⚫️ 🛠️/❎.
-
-📚 💼, ↩️ 📣 *➡ 🛠️ 🔢* 🔢 ⏮️ `Depends`, 👆 💪 🚮 `list` `dependencies` *➡ 🛠️ 👨🎨*.
-
-## 🚮 `dependencies` *➡ 🛠️ 👨🎨*
-
-*➡ 🛠️ 👨🎨* 📨 📦 ❌ `dependencies`.
-
-⚫️ 🔜 `list` `Depends()`:
-
-{* ../../docs_src/dependencies/tutorial006.py hl[17] *}
-
-👉 🔗 🔜 🛠️/❎ 🎏 🌌 😐 🔗. ✋️ 👫 💲 (🚥 👫 📨 🙆) 🏆 🚫 🚶♀️ 👆 *➡ 🛠️ 🔢*.
-
-/// tip
-
-👨🎨 ✅ ♻ 🔢 🔢, & 🎦 👫 ❌.
-
-⚙️ 👉 `dependencies` *➡ 🛠️ 👨🎨* 👆 💪 ⚒ 💭 👫 🛠️ ⏪ ❎ 👨🎨/🏭 ❌.
-
-⚫️ 💪 ℹ ❎ 😨 🆕 👩💻 👈 👀 ♻ 🔢 👆 📟 & 💪 💭 ⚫️ 🙃.
-
-///
-
-/// info
-
-👉 🖼 👥 ⚙️ 💭 🛃 🎚 `X-Key` & `X-Token`.
-
-✋️ 🎰 💼, 🕐❔ 🛠️ 💂♂, 👆 🔜 🤚 🌖 💰 ⚪️➡️ ⚙️ 🛠️ [💂♂ 🚙 (⏭ 📃)](../security/index.md){.internal-link target=_blank}.
-
-///
-
-## 🔗 ❌ & 📨 💲
-
-👆 💪 ⚙️ 🎏 🔗 *🔢* 👆 ⚙️ 🛎.
-
-### 🔗 📄
-
-👫 💪 📣 📨 📄 (💖 🎚) ⚖️ 🎏 🎧-🔗:
-
-{* ../../docs_src/dependencies/tutorial006.py hl[6,11] *}
-
-### 🤚 ⚠
-
-👫 🔗 💪 `raise` ⚠, 🎏 😐 🔗:
-
-{* ../../docs_src/dependencies/tutorial006.py hl[8,13] *}
-
-### 📨 💲
-
-& 👫 💪 📨 💲 ⚖️ 🚫, 💲 🏆 🚫 ⚙️.
-
-, 👆 💪 🏤-⚙️ 😐 🔗 (👈 📨 💲) 👆 ⏪ ⚙️ 👱 🙆, & ✋️ 💲 🏆 🚫 ⚙️, 🔗 🔜 🛠️:
-
-{* ../../docs_src/dependencies/tutorial006.py hl[9,14] *}
-
-## 🔗 👪 *➡ 🛠️*
-
-⏪, 🕐❔ 👂 🔃 ❔ 📊 🦏 🈸 ([🦏 🈸 - 💗 📁](../../tutorial/bigger-applications.md){.internal-link target=_blank}), 🎲 ⏮️ 💗 📁, 👆 🔜 💡 ❔ 📣 👁 `dependencies` 🔢 👪 *➡ 🛠️*.
-
-## 🌐 🔗
-
-⏭ 👥 🔜 👀 ❔ 🚮 🔗 🎂 `FastAPI` 🈸, 👈 👫 ✔ 🔠 *➡ 🛠️*.
diff --git a/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md
deleted file mode 100644
index 1b37b1cf29..0000000000
--- a/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md
+++ /dev/null
@@ -1,232 +0,0 @@
-# 🔗 ⏮️ 🌾
-
-FastAPI 🐕🦺 🔗 👈 ➕ 🔁 ⏮️ 🏁.
-
-👉, ⚙️ `yield` ↩️ `return`, & ✍ ➕ 🔁 ⏮️.
-
-/// tip
-
-⚒ 💭 ⚙️ `yield` 1️⃣ 👁 🕰.
-
-///
-
-/// note | 📡 ℹ
-
-🙆 🔢 👈 ☑ ⚙️ ⏮️:
-
-* `@contextlib.contextmanager` ⚖️
-* `@contextlib.asynccontextmanager`
-
-🔜 ☑ ⚙️ **FastAPI** 🔗.
-
-👐, FastAPI ⚙️ 📚 2️⃣ 👨🎨 🔘.
-
-///
-
-## 💽 🔗 ⏮️ `yield`
-
-🖼, 👆 💪 ⚙️ 👉 ✍ 💽 🎉 & 🔐 ⚫️ ⏮️ 🏁.
-
-🕴 📟 ⏭ & 🔌 `yield` 📄 🛠️ ⏭ 📨 📨:
-
-{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
-
-🌾 💲 ⚫️❔ 💉 🔘 *➡ 🛠️* & 🎏 🔗:
-
-{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
-
-📟 📄 `yield` 📄 🛠️ ⏮️ 📨 ✔️ 🚚:
-
-{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
-
-/// tip
-
-👆 💪 ⚙️ `async` ⚖️ 😐 🔢.
-
-**FastAPI** 🔜 ▶️️ 👜 ⏮️ 🔠, 🎏 ⏮️ 😐 🔗.
-
-///
-
-## 🔗 ⏮️ `yield` & `try`
-
-🚥 👆 ⚙️ `try` 🍫 🔗 ⏮️ `yield`, 👆 🔜 📨 🙆 ⚠ 👈 🚮 🕐❔ ⚙️ 🔗.
-
-🖼, 🚥 📟 ☝ 🖕, ➕1️⃣ 🔗 ⚖️ *➡ 🛠️*, ⚒ 💽 💵 "💾" ⚖️ ✍ 🙆 🎏 ❌, 👆 🔜 📨 ⚠ 👆 🔗.
-
-, 👆 💪 👀 👈 🎯 ⚠ 🔘 🔗 ⏮️ `except SomeException`.
-
-🎏 🌌, 👆 💪 ⚙️ `finally` ⚒ 💭 🚪 📶 🛠️, 🙅♂ 🤔 🚥 📤 ⚠ ⚖️ 🚫.
-
-{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
-
-## 🎧-🔗 ⏮️ `yield`
-
-👆 💪 ✔️ 🎧-🔗 & "🌲" 🎧-🔗 🙆 📐 & 💠, & 🙆 ⚖️ 🌐 👫 💪 ⚙️ `yield`.
-
-**FastAPI** 🔜 ⚒ 💭 👈 "🚪 📟" 🔠 🔗 ⏮️ `yield` 🏃 ☑ ✔.
-
-🖼, `dependency_c` 💪 ✔️ 🔗 🔛 `dependency_b`, & `dependency_b` 🔛 `dependency_a`:
-
-{* ../../docs_src/dependencies/tutorial008.py hl[4,12,20] *}
-
-& 🌐 👫 💪 ⚙️ `yield`.
-
-👉 💼 `dependency_c`, 🛠️ 🚮 🚪 📟, 💪 💲 ⚪️➡️ `dependency_b` (📥 📛 `dep_b`) 💪.
-
-& , 🔄, `dependency_b` 💪 💲 ⚪️➡️ `dependency_a` (📥 📛 `dep_a`) 💪 🚮 🚪 📟.
-
-{* ../../docs_src/dependencies/tutorial008.py hl[16:17,24:25] *}
-
-🎏 🌌, 👆 💪 ✔️ 🔗 ⏮️ `yield` & `return` 🌀.
-
-& 👆 💪 ✔️ 👁 🔗 👈 🚚 📚 🎏 🔗 ⏮️ `yield`, ♒️.
-
-👆 💪 ✔️ 🙆 🌀 🔗 👈 👆 💚.
-
-**FastAPI** 🔜 ⚒ 💭 🌐 🏃 ☑ ✔.
-
-/// note | 📡 ℹ
-
-👉 👷 👏 🐍 🔑 👨💼.
-
-**FastAPI** ⚙️ 👫 🔘 🏆 👉.
-
-///
-
-## 🔗 ⏮️ `yield` & `HTTPException`
-
-👆 👀 👈 👆 💪 ⚙️ 🔗 ⏮️ `yield` & ✔️ `try` 🍫 👈 ✊ ⚠.
-
-⚫️ 5️⃣📆 😋 🤚 `HTTPException` ⚖️ 🎏 🚪 📟, ⏮️ `yield`. ✋️ **⚫️ 🏆 🚫 👷**.
-
-🚪 📟 🔗 ⏮️ `yield` 🛠️ *⏮️* 📨 📨, [⚠ 🐕🦺](../handling-errors.md#_4){.internal-link target=_blank} 🔜 ✔️ ⏪ 🏃. 📤 🕳 😽 ⚠ 🚮 👆 🔗 🚪 📟 (⏮️ `yield`).
-
-, 🚥 👆 🤚 `HTTPException` ⏮️ `yield`, 🔢 (⚖️ 🙆 🛃) ⚠ 🐕🦺 👈 ✊ `HTTPException`Ⓜ & 📨 🇺🇸🔍 4️⃣0️⃣0️⃣ 📨 🏆 🚫 📤 ✊ 👈 ⚠ 🚫🔜.
-
-👉 ⚫️❔ ✔ 🕳 ⚒ 🔗 (✅ 💽 🎉), 🖼, ⚙️ 🖥 📋.
-
-🖥 📋 🏃 *⏮️* 📨 ✔️ 📨. 📤 🙅♂ 🌌 🤚 `HTTPException` ↩️ 📤 🚫 🌌 🔀 📨 👈 *⏪ 📨*.
-
-✋️ 🚥 🖥 📋 ✍ 💽 ❌, 🌘 👆 💪 💾 ⚖️ 😬 🔐 🎉 🔗 ⏮️ `yield`, & 🎲 🕹 ❌ ⚖️ 📄 ⚫️ 🛰 🕵 ⚙️.
-
-🚥 👆 ✔️ 📟 👈 👆 💭 💪 🤚 ⚠, 🏆 😐/"🙃" 👜 & 🚮 `try` 🍫 👈 📄 📟.
-
-🚥 👆 ✔️ 🛃 ⚠ 👈 👆 🔜 💖 🍵 *⏭* 🛬 📨 & 🎲 ❎ 📨, 🎲 🙋♀ `HTTPException`, ✍ [🛃 ⚠ 🐕🦺](../handling-errors.md#_4){.internal-link target=_blank}.
-
-/// tip
-
-👆 💪 🤚 ⚠ 🔌 `HTTPException` *⏭* `yield`. ✋️ 🚫 ⏮️.
-
-///
-
-🔁 🛠️ 🌅 ⚖️ 🌘 💖 👉 📊. 🕰 💧 ⚪️➡️ 🔝 🔝. & 🔠 🏓 1️⃣ 🍕 🔗 ⚖️ 🛠️ 📟.
-
-```mermaid
-sequenceDiagram
-
-participant client as Client
-participant handler as Exception handler
-participant dep as Dep with yield
-participant operation as Path Operation
-participant tasks as Background tasks
-
- Note over client,tasks: Can raise exception for dependency, handled after response is sent
- Note over client,operation: Can raise HTTPException and can change the response
- client ->> dep: Start request
- Note over dep: Run code up to yield
- opt raise
- dep -->> handler: Raise HTTPException
- handler -->> client: HTTP error response
- dep -->> dep: Raise other exception
- end
- dep ->> operation: Run dependency, e.g. DB session
- opt raise
- operation -->> dep: Raise HTTPException
- dep -->> handler: Auto forward exception
- handler -->> client: HTTP error response
- operation -->> dep: Raise other exception
- dep -->> handler: Auto forward exception
- end
- operation ->> client: Return response to client
- Note over client,operation: Response is already sent, can't change it anymore
- opt Tasks
- operation -->> tasks: Send background tasks
- end
- opt Raise other exception
- tasks -->> dep: Raise other exception
- end
- Note over dep: After yield
- opt Handle other exception
- dep -->> dep: Handle exception, can't change response. E.g. close DB session.
- end
-```
-
-/// info
-
-🕴 **1️⃣ 📨** 🔜 📨 👩💻. ⚫️ 💪 1️⃣ ❌ 📨 ⚖️ ⚫️ 🔜 📨 ⚪️➡️ *➡ 🛠️*.
-
-⏮️ 1️⃣ 📚 📨 📨, 🙅♂ 🎏 📨 💪 📨.
-
-///
-
-/// tip
-
-👉 📊 🎦 `HTTPException`, ✋️ 👆 💪 🤚 🙆 🎏 ⚠ ❔ 👆 ✍ [🛃 ⚠ 🐕🦺](../handling-errors.md#_4){.internal-link target=_blank}.
-
-🚥 👆 🤚 🙆 ⚠, ⚫️ 🔜 🚶♀️ 🔗 ⏮️ 🌾, 🔌 `HTTPException`, & ⤴️ **🔄** ⚠ 🐕🦺. 🚥 📤 🙅♂ ⚠ 🐕🦺 👈 ⚠, ⚫️ 🔜 ⤴️ 🍵 🔢 🔗 `ServerErrorMiddleware`, 🛬 5️⃣0️⃣0️⃣ 🇺🇸🔍 👔 📟, ➡️ 👩💻 💭 👈 📤 ❌ 💽.
-
-///
-
-## 🔑 👨💼
-
-### ⚫️❔ "🔑 👨💼"
-
-"🔑 👨💼" 🙆 👈 🐍 🎚 👈 👆 💪 ⚙️ `with` 📄.
-
-🖼, 👆 💪 ⚙️ `with` ✍ 📁:
-
-```Python
-with open("./somefile.txt") as f:
- contents = f.read()
- print(contents)
-```
-
-🔘, `open("./somefile.txt")` ✍ 🎚 👈 🤙 "🔑 👨💼".
-
-🕐❔ `with` 🍫 🏁, ⚫️ ⚒ 💭 🔐 📁, 🚥 📤 ⚠.
-
-🕐❔ 👆 ✍ 🔗 ⏮️ `yield`, **FastAPI** 🔜 🔘 🗜 ⚫️ 🔑 👨💼, & 🌀 ⚫️ ⏮️ 🎏 🔗 🧰.
-
-### ⚙️ 🔑 👨💼 🔗 ⏮️ `yield`
-
-/// warning
-
-👉, 🌅 ⚖️ 🌘, "🏧" 💭.
-
-🚥 👆 ▶️ ⏮️ **FastAPI** 👆 💪 💚 🚶 ⚫️ 🔜.
-
-///
-
-🐍, 👆 💪 ✍ 🔑 👨💼 🏗 🎓 ⏮️ 2️⃣ 👩🔬: `__enter__()` & `__exit__()`.
-
-👆 💪 ⚙️ 👫 🔘 **FastAPI** 🔗 ⏮️ `yield` ⚙️
-`with` ⚖️ `async with` 📄 🔘 🔗 🔢:
-
-{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *}
-
-/// tip
-
-➕1️⃣ 🌌 ✍ 🔑 👨💼 ⏮️:
-
-* `@contextlib.contextmanager` ⚖️
-* `@contextlib.asynccontextmanager`
-
-⚙️ 👫 🎀 🔢 ⏮️ 👁 `yield`.
-
-👈 ⚫️❔ **FastAPI** ⚙️ 🔘 🔗 ⏮️ `yield`.
-
-✋️ 👆 🚫 ✔️ ⚙️ 👨🎨 FastAPI 🔗 (& 👆 🚫🔜 🚫).
-
-FastAPI 🔜 ⚫️ 👆 🔘.
-
-///
diff --git a/docs/em/docs/tutorial/dependencies/global-dependencies.md b/docs/em/docs/tutorial/dependencies/global-dependencies.md
deleted file mode 100644
index 5a22e5f1c8..0000000000
--- a/docs/em/docs/tutorial/dependencies/global-dependencies.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# 🌐 🔗
-
-🆎 🈸 👆 💪 💚 🚮 🔗 🎂 🈸.
-
-🎏 🌌 👆 💪 [🚮 `dependencies` *➡ 🛠️ 👨🎨*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, 👆 💪 🚮 👫 `FastAPI` 🈸.
-
-👈 💼, 👫 🔜 ✔ 🌐 *➡ 🛠️* 🈸:
-
-{* ../../docs_src/dependencies/tutorial012.py hl[15] *}
-
-& 🌐 💭 📄 🔃 [❎ `dependencies` *➡ 🛠️ 👨🎨*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} ✔, ✋️ 👉 💼, 🌐 *➡ 🛠️* 📱.
-
-## 🔗 👪 *➡ 🛠️*
-
-⏪, 🕐❔ 👂 🔃 ❔ 📊 🦏 🈸 ([🦏 🈸 - 💗 📁](../../tutorial/bigger-applications.md){.internal-link target=_blank}), 🎲 ⏮️ 💗 📁, 👆 🔜 💡 ❔ 📣 👁 `dependencies` 🔢 👪 *➡ 🛠️*.
diff --git a/docs/em/docs/tutorial/dependencies/index.md b/docs/em/docs/tutorial/dependencies/index.md
deleted file mode 100644
index ce87d9ee48..0000000000
--- a/docs/em/docs/tutorial/dependencies/index.md
+++ /dev/null
@@ -1,212 +0,0 @@
-# 🔗
-
-**FastAPI** ✔️ 📶 🏋️ ✋️ 🏋️ **🔗 💉** ⚙️.
-
-⚫️ 🏗 📶 🙅 ⚙️, & ⚒ ⚫️ 📶 ⏩ 🙆 👩💻 🛠️ 🎏 🦲 ⏮️ **FastAPI**.
-
-## ⚫️❔ "🔗 💉"
-
-**"🔗 💉"** ⛓, 📋, 👈 📤 🌌 👆 📟 (👉 💼, 👆 *➡ 🛠️ 🔢*) 📣 👜 👈 ⚫️ 🚚 👷 & ⚙️: "🔗".
-
-& ⤴️, 👈 ⚙️ (👉 💼 **FastAPI**) 🔜 ✊ 💅 🔨 ⚫️❔ 💪 🚚 👆 📟 ⏮️ 📚 💪 🔗 ("💉" 🔗).
-
-👉 📶 ⚠ 🕐❔ 👆 💪:
-
-* ✔️ 💰 ⚛ (🎏 📟 ⚛ 🔄 & 🔄).
-* 💰 💽 🔗.
-* 🛠️ 💂♂, 🤝, 🔑 📄, ♒️.
-* & 📚 🎏 👜...
-
-🌐 👫, ⏪ 📉 📟 🔁.
-
-## 🥇 🔁
-
-➡️ 👀 📶 🙅 🖼. ⚫️ 🔜 🙅 👈 ⚫️ 🚫 📶 ⚠, 🔜.
-
-✋️ 👉 🌌 👥 💪 🎯 🔛 ❔ **🔗 💉** ⚙️ 👷.
-
-### ✍ 🔗, ⚖️ "☑"
-
-➡️ 🥇 🎯 🔛 🔗.
-
-⚫️ 🔢 👈 💪 ✊ 🌐 🎏 🔢 👈 *➡ 🛠️ 🔢* 💪 ✊:
-
-{* ../../docs_src/dependencies/tutorial001.py hl[8:11] *}
-
-👈 ⚫️.
-
-**2️⃣ ⏸**.
-
-& ⚫️ ✔️ 🎏 💠 & 📊 👈 🌐 👆 *➡ 🛠️ 🔢* ✔️.
-
-👆 💪 💭 ⚫️ *➡ 🛠️ 🔢* 🍵 "👨🎨" (🍵 `@app.get("/some-path")`).
-
-& ⚫️ 💪 📨 🕳 👆 💚.
-
-👉 💼, 👉 🔗 ⌛:
-
-* 📦 🔢 🔢 `q` 👈 `str`.
-* 📦 🔢 🔢 `skip` 👈 `int`, & 🔢 `0`.
-* 📦 🔢 🔢 `limit` 👈 `int`, & 🔢 `100`.
-
-& ⤴️ ⚫️ 📨 `dict` ⚗ 📚 💲.
-
-### 🗄 `Depends`
-
-{* ../../docs_src/dependencies/tutorial001.py hl[3] *}
-
-### 📣 🔗, "⚓️"
-
-🎏 🌌 👆 ⚙️ `Body`, `Query`, ♒️. ⏮️ 👆 *➡ 🛠️ 🔢* 🔢, ⚙️ `Depends` ⏮️ 🆕 🔢:
-
-{* ../../docs_src/dependencies/tutorial001.py hl[15,20] *}
-
-👐 👆 ⚙️ `Depends` 🔢 👆 🔢 🎏 🌌 👆 ⚙️ `Body`, `Query`, ♒️, `Depends` 👷 👄 🎏.
-
-👆 🕴 🤝 `Depends` 👁 🔢.
-
-👉 🔢 🔜 🕳 💖 🔢.
-
-& 👈 🔢 ✊ 🔢 🎏 🌌 👈 *➡ 🛠️ 🔢* .
-
-/// tip
-
-👆 🔜 👀 ⚫️❔ 🎏 "👜", ↖️ ⚪️➡️ 🔢, 💪 ⚙️ 🔗 ⏭ 📃.
-
-///
-
-🕐❔ 🆕 📨 🛬, **FastAPI** 🔜 ✊ 💅:
-
-* 🤙 👆 🔗 ("☑") 🔢 ⏮️ ☑ 🔢.
-* 🤚 🏁 ⚪️➡️ 👆 🔢.
-* 🛠️ 👈 🏁 🔢 👆 *➡ 🛠️ 🔢*.
-
-```mermaid
-graph TB
-
-common_parameters(["common_parameters"])
-read_items["/items/"]
-read_users["/users/"]
-
-common_parameters --> read_items
-common_parameters --> read_users
-```
-
-👉 🌌 👆 ✍ 🔗 📟 🕐 & **FastAPI** ✊ 💅 🤙 ⚫️ 👆 *➡ 🛠️*.
-
-/// check
-
-👀 👈 👆 🚫 ✔️ ✍ 🎁 🎓 & 🚶♀️ ⚫️ 👱 **FastAPI** "®" ⚫️ ⚖️ 🕳 🎏.
-
-👆 🚶♀️ ⚫️ `Depends` & **FastAPI** 💭 ❔ 🎂.
-
-///
-
-## `async` ⚖️ 🚫 `async`
-
-🔗 🔜 🤙 **FastAPI** (🎏 👆 *➡ 🛠️ 🔢*), 🎏 🚫 ✔ ⏪ 🔬 👆 🔢.
-
-👆 💪 ⚙️ `async def` ⚖️ 😐 `def`.
-
-& 👆 💪 📣 🔗 ⏮️ `async def` 🔘 😐 `def` *➡ 🛠️ 🔢*, ⚖️ `def` 🔗 🔘 `async def` *➡ 🛠️ 🔢*, ♒️.
-
-⚫️ 🚫 🤔. **FastAPI** 🔜 💭 ⚫️❔.
-
-/// note
-
-🚥 👆 🚫 💭, ✅ [🔁: *"🏃 ❓" *](../../async.md){.internal-link target=_blank} 📄 🔃 `async` & `await` 🩺.
-
-///
-
-## 🛠️ ⏮️ 🗄
-
-🌐 📨 📄, 🔬 & 📄 👆 🔗 (& 🎧-🔗) 🔜 🛠️ 🎏 🗄 🔗.
-
-, 🎓 🩺 🔜 ✔️ 🌐 ℹ ⚪️➡️ 👫 🔗 💁♂️:
-
-
-
-## 🙅 ⚙️
-
-🚥 👆 👀 ⚫️, *➡ 🛠️ 🔢* 📣 ⚙️ 🕐❔ *➡* & *🛠️* 🏏, & ⤴️ **FastAPI** ✊ 💅 🤙 🔢 ⏮️ ☑ 🔢, ❎ 📊 ⚪️➡️ 📨.
-
-🤙, 🌐 (⚖️ 🏆) 🕸 🛠️ 👷 👉 🎏 🌌.
-
-👆 🙅 🤙 👈 🔢 🔗. 👫 🤙 👆 🛠️ (👉 💼, **FastAPI**).
-
-⏮️ 🔗 💉 ⚙️, 👆 💪 💬 **FastAPI** 👈 👆 *➡ 🛠️ 🔢* "🪀" 🔛 🕳 🙆 👈 🔜 🛠️ ⏭ 👆 *➡ 🛠️ 🔢*, & **FastAPI** 🔜 ✊ 💅 🛠️ ⚫️ & "💉" 🏁.
-
-🎏 ⚠ ⚖ 👉 🎏 💭 "🔗 💉":
-
-* ℹ
-* 🐕🦺
-* 🐕🦺
-* 💉
-* 🦲
-
-## **FastAPI** 🔌-🔌
-
-🛠️ & "🔌-"Ⓜ 💪 🏗 ⚙️ **🔗 💉** ⚙️. ✋️ 👐, 📤 🤙 **🙅♂ 💪 ✍ "🔌-🔌"**, ⚙️ 🔗 ⚫️ 💪 📣 ♾ 🔢 🛠️ & 🔗 👈 ▶️️ 💪 👆 *➡ 🛠️ 🔢*.
-
-& 🔗 💪 ✍ 📶 🙅 & 🏋️ 🌌 👈 ✔ 👆 🗄 🐍 📦 👆 💪, & 🛠️ 👫 ⏮️ 👆 🛠️ 🔢 👩❤👨 ⏸ 📟, *🌖*.
-
-👆 🔜 👀 🖼 👉 ⏭ 📃, 🔃 🔗 & ☁ 💽, 💂♂, ♒️.
-
-## **FastAPI** 🔗
-
-🦁 🔗 💉 ⚙️ ⚒ **FastAPI** 🔗 ⏮️:
-
-* 🌐 🔗 💽
-* ☁ 💽
-* 🔢 📦
-* 🔢 🔗
-* 🤝 & ✔ ⚙️
-* 🛠️ ⚙️ ⚖ ⚙️
-* 📨 💽 💉 ⚙️
-* ♒️.
-
-## 🙅 & 🏋️
-
-👐 🔗 🔗 💉 ⚙️ 📶 🙅 🔬 & ⚙️, ⚫️ 📶 🏋️.
-
-👆 💪 🔬 🔗 👈 🔄 💪 🔬 🔗 👫.
-
-🔚, 🔗 🌲 🔗 🏗, & **🔗 💉** ⚙️ ✊ 💅 🔬 🌐 👉 🔗 👆 (& 👫 🎧-🔗) & 🚚 (💉) 🏁 🔠 🔁.
-
-🖼, ➡️ 💬 👆 ✔️ 4️⃣ 🛠️ 🔗 (*➡ 🛠️*):
-
-* `/items/public/`
-* `/items/private/`
-* `/users/{user_id}/activate`
-* `/items/pro/`
-
-⤴️ 👆 💪 🚮 🎏 ✔ 📄 🔠 👫 ⏮️ 🔗 & 🎧-🔗:
-
-```mermaid
-graph TB
-
-current_user(["current_user"])
-active_user(["active_user"])
-admin_user(["admin_user"])
-paying_user(["paying_user"])
-
-public["/items/public/"]
-private["/items/private/"]
-activate_user["/users/{user_id}/activate"]
-pro_items["/items/pro/"]
-
-current_user --> active_user
-active_user --> admin_user
-active_user --> paying_user
-
-current_user --> public
-active_user --> private
-admin_user --> activate_user
-paying_user --> pro_items
-```
-
-## 🛠️ ⏮️ **🗄**
-
-🌐 👫 🔗, ⏪ 📣 👫 📄, 🚮 🔢, 🔬, ♒️. 👆 *➡ 🛠️*.
-
-**FastAPI** 🔜 ✊ 💅 🚮 ⚫️ 🌐 🗄 🔗, 👈 ⚫️ 🎦 🎓 🧾 ⚙️.
diff --git a/docs/em/docs/tutorial/dependencies/sub-dependencies.md b/docs/em/docs/tutorial/dependencies/sub-dependencies.md
deleted file mode 100644
index 6d622e952f..0000000000
--- a/docs/em/docs/tutorial/dependencies/sub-dependencies.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# 🎧-🔗
-
-👆 💪 ✍ 🔗 👈 ✔️ **🎧-🔗**.
-
-👫 💪 **⏬** 👆 💪 👫.
-
-**FastAPI** 🔜 ✊ 💅 🔬 👫.
-
-## 🥇 🔗 "☑"
-
-👆 💪 ✍ 🥇 🔗 ("☑") 💖:
-
-{* ../../docs_src/dependencies/tutorial005.py hl[8:9] *}
-
-⚫️ 📣 📦 🔢 🔢 `q` `str`, & ⤴️ ⚫️ 📨 ⚫️.
-
-👉 🙅 (🚫 📶 ⚠), ✋️ 🔜 ℹ 👥 🎯 🔛 ❔ 🎧-🔗 👷.
-
-## 🥈 🔗, "☑" & "⚓️"
-
-⤴️ 👆 💪 ✍ ➕1️⃣ 🔗 🔢 ("☑") 👈 🎏 🕰 📣 🔗 🚮 👍 (⚫️ "⚓️" 💁♂️):
-
-{* ../../docs_src/dependencies/tutorial005.py hl[13] *}
-
-➡️ 🎯 🔛 🔢 📣:
-
-* ✋️ 👉 🔢 🔗 ("☑") ⚫️, ⚫️ 📣 ➕1️⃣ 🔗 (⚫️ "🪀" 🔛 🕳 🙆).
- * ⚫️ 🪀 🔛 `query_extractor`, & 🛠️ 💲 📨 ⚫️ 🔢 `q`.
-* ⚫️ 📣 📦 `last_query` 🍪, `str`.
- * 🚥 👩💻 🚫 🚚 🙆 🔢 `q`, 👥 ⚙️ 🏁 🔢 ⚙️, ❔ 👥 🖊 🍪 ⏭.
-
-## ⚙️ 🔗
-
-⤴️ 👥 💪 ⚙️ 🔗 ⏮️:
-
-{* ../../docs_src/dependencies/tutorial005.py hl[22] *}
-
-/// info
-
-👀 👈 👥 🕴 📣 1️⃣ 🔗 *➡ 🛠️ 🔢*, `query_or_cookie_extractor`.
-
-✋️ **FastAPI** 🔜 💭 👈 ⚫️ ✔️ ❎ `query_extractor` 🥇, 🚶♀️ 🏁 👈 `query_or_cookie_extractor` ⏪ 🤙 ⚫️.
-
-///
-
-```mermaid
-graph TB
-
-query_extractor(["query_extractor"])
-query_or_cookie_extractor(["query_or_cookie_extractor"])
-
-read_query["/items/"]
-
-query_extractor --> query_or_cookie_extractor --> read_query
-```
-
-## ⚙️ 🎏 🔗 💗 🕰
-
-🚥 1️⃣ 👆 🔗 📣 💗 🕰 🎏 *➡ 🛠️*, 🖼, 💗 🔗 ✔️ ⚠ 🎧-🔗, **FastAPI** 🔜 💭 🤙 👈 🎧-🔗 🕴 🕐 📍 📨.
-
-& ⚫️ 🔜 🖊 📨 💲 "💾" & 🚶♀️ ⚫️ 🌐 "⚓️" 👈 💪 ⚫️ 👈 🎯 📨, ↩️ 🤙 🔗 💗 🕰 🎏 📨.
-
-🏧 😐 🌐❔ 👆 💭 👆 💪 🔗 🤙 🔠 🔁 (🎲 💗 🕰) 🎏 📨 ↩️ ⚙️ "💾" 💲, 👆 💪 ⚒ 🔢 `use_cache=False` 🕐❔ ⚙️ `Depends`:
-
-```Python hl_lines="1"
-async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
- return {"fresh_value": fresh_value}
-```
-
-## 🌃
-
-↖️ ⚪️➡️ 🌐 🎀 🔤 ⚙️ 📥, **🔗 💉** ⚙️ 🙅.
-
-🔢 👈 👀 🎏 *➡ 🛠️ 🔢*.
-
-✋️, ⚫️ 📶 🏋️, & ✔ 👆 📣 🎲 🙇 🐦 🔗 "📊" (🌲).
-
-/// tip
-
-🌐 👉 💪 🚫 😑 ⚠ ⏮️ 👫 🙅 🖼.
-
-✋️ 👆 🔜 👀 ❔ ⚠ ⚫️ 📃 🔃 **💂♂**.
-
- & 👆 🔜 👀 💸 📟 ⚫️ 🔜 🖊 👆.
-
-///
diff --git a/docs/em/docs/tutorial/encoder.md b/docs/em/docs/tutorial/encoder.md
deleted file mode 100644
index ad05f701e2..0000000000
--- a/docs/em/docs/tutorial/encoder.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# 🎻 🔗 🔢
-
-📤 💼 🌐❔ 👆 5️⃣📆 💪 🗜 💽 🆎 (💖 Pydantic 🏷) 🕳 🔗 ⏮️ 🎻 (💖 `dict`, `list`, ♒️).
-
-🖼, 🚥 👆 💪 🏪 ⚫️ 💽.
-
-👈, **FastAPI** 🚚 `jsonable_encoder()` 🔢.
-
-## ⚙️ `jsonable_encoder`
-
-➡️ 🌈 👈 👆 ✔️ 💽 `fake_db` 👈 🕴 📨 🎻 🔗 💽.
-
-🖼, ⚫️ 🚫 📨 `datetime` 🎚, 👈 🚫 🔗 ⏮️ 🎻.
-
-, `datetime` 🎚 🔜 ✔️ 🗜 `str` ⚗ 💽 💾 📁.
-
-🎏 🌌, 👉 💽 🚫🔜 📨 Pydantic 🏷 (🎚 ⏮️ 🔢), 🕴 `dict`.
-
-👆 💪 ⚙️ `jsonable_encoder` 👈.
-
-⚫️ 📨 🎚, 💖 Pydantic 🏷, & 📨 🎻 🔗 ⏬:
-
-{* ../../docs_src/encoder/tutorial001.py hl[5,22] *}
-
-👉 🖼, ⚫️ 🔜 🗜 Pydantic 🏷 `dict`, & `datetime` `str`.
-
-🏁 🤙 ⚫️ 🕳 👈 💪 🗜 ⏮️ 🐍 🐩 `json.dumps()`.
-
-⚫️ 🚫 📨 ⭕ `str` ⚗ 💽 🎻 📁 (🎻). ⚫️ 📨 🐍 🐩 💽 📊 (✅ `dict`) ⏮️ 💲 & 🎧-💲 👈 🌐 🔗 ⏮️ 🎻.
-
-/// note
-
-`jsonable_encoder` 🤙 ⚙️ **FastAPI** 🔘 🗜 💽. ✋️ ⚫️ ⚠ 📚 🎏 😐.
-
-///
diff --git a/docs/em/docs/tutorial/extra-data-types.md b/docs/em/docs/tutorial/extra-data-types.md
deleted file mode 100644
index f15a74b4a3..0000000000
--- a/docs/em/docs/tutorial/extra-data-types.md
+++ /dev/null
@@ -1,62 +0,0 @@
-# ➕ 💽 🆎
-
-🆙 🔜, 👆 ✔️ ⚙️ ⚠ 📊 🆎, 💖:
-
-* `int`
-* `float`
-* `str`
-* `bool`
-
-✋️ 👆 💪 ⚙️ 🌅 🏗 📊 🆎.
-
-& 👆 🔜 ✔️ 🎏 ⚒ 👀 🆙 🔜:
-
-* 👑 👨🎨 🐕🦺.
-* 💽 🛠️ ⚪️➡️ 📨 📨.
-* 💽 🛠️ 📨 💽.
-* 💽 🔬.
-* 🏧 ✍ & 🧾.
-
-## 🎏 💽 🆎
-
-📥 🌖 📊 🆎 👆 💪 ⚙️:
-
-* `UUID`:
- * 🐩 "⭐ 😍 🆔", ⚠ 🆔 📚 💽 & ⚙️.
- * 📨 & 📨 🔜 🎨 `str`.
-* `datetime.datetime`:
- * 🐍 `datetime.datetime`.
- * 📨 & 📨 🔜 🎨 `str` 💾 8️⃣6️⃣0️⃣1️⃣ 📁, 💖: `2008-09-15T15:53:00+05:00`.
-* `datetime.date`:
- * 🐍 `datetime.date`.
- * 📨 & 📨 🔜 🎨 `str` 💾 8️⃣6️⃣0️⃣1️⃣ 📁, 💖: `2008-09-15`.
-* `datetime.time`:
- * 🐍 `datetime.time`.
- * 📨 & 📨 🔜 🎨 `str` 💾 8️⃣6️⃣0️⃣1️⃣ 📁, 💖: `14:23:55.003`.
-* `datetime.timedelta`:
- * 🐍 `datetime.timedelta`.
- * 📨 & 📨 🔜 🎨 `float` 🌐 🥈.
- * Pydantic ✔ 🎦 ⚫️ "💾 8️⃣6️⃣0️⃣1️⃣ 🕰 ➕ 🔢", 👀 🩺 🌅 ℹ.
-* `frozenset`:
- * 📨 & 📨, 😥 🎏 `set`:
- * 📨, 📇 🔜 ✍, ❎ ❎ & 🏭 ⚫️ `set`.
- * 📨, `set` 🔜 🗜 `list`.
- * 🏗 🔗 🔜 ✔ 👈 `set` 💲 😍 (⚙️ 🎻 🔗 `uniqueItems`).
-* `bytes`:
- * 🐩 🐍 `bytes`.
- * 📨 & 📨 🔜 😥 `str`.
- * 🏗 🔗 🔜 ✔ 👈 ⚫️ `str` ⏮️ `binary` "📁".
-* `Decimal`:
- * 🐩 🐍 `Decimal`.
- * 📨 & 📨, 🍵 🎏 `float`.
-* 👆 💪 ✅ 🌐 ☑ Pydantic 📊 🆎 📥: Pydantic 📊 🆎.
-
-## 🖼
-
-📥 🖼 *➡ 🛠️* ⏮️ 🔢 ⚙️ 🔛 🆎.
-
-{* ../../docs_src/extra_data_types/tutorial001.py hl[1,3,12:16] *}
-
-🗒 👈 🔢 🔘 🔢 ✔️ 👫 🐠 💽 🆎, & 👆 💪, 🖼, 🎭 😐 📅 🎭, 💖:
-
-{* ../../docs_src/extra_data_types/tutorial001.py hl[18:19] *}
diff --git a/docs/em/docs/tutorial/extra-models.md b/docs/em/docs/tutorial/extra-models.md
deleted file mode 100644
index 19ab5b7986..0000000000
--- a/docs/em/docs/tutorial/extra-models.md
+++ /dev/null
@@ -1,211 +0,0 @@
-# ➕ 🏷
-
-▶️ ⏮️ ⏮️ 🖼, ⚫️ 🔜 ⚠ ✔️ 🌅 🌘 1️⃣ 🔗 🏷.
-
-👉 ✴️ 💼 👩💻 🏷, ↩️:
-
-* **🔢 🏷** 💪 💪 ✔️ 🔐.
-* **🔢 🏷** 🔜 🚫 ✔️ 🔐.
-* **💽 🏷** 🔜 🎲 💪 ✔️ #️⃣ 🔐.
-
-/// danger
-
-🙅 🏪 👩💻 🔢 🔐. 🕧 🏪 "🔐 #️⃣" 👈 👆 💪 ⤴️ ✔.
-
-🚥 👆 🚫 💭, 👆 🔜 💡 ⚫️❔ "🔐#️⃣" [💂♂ 📃](security/simple-oauth2.md#_4){.internal-link target=_blank}.
-
-///
-
-## 💗 🏷
-
-📥 🏢 💭 ❔ 🏷 💪 👀 💖 ⏮️ 👫 🔐 🏑 & 🥉 🌐❔ 👫 ⚙️:
-
-{* ../../docs_src/extra_models/tutorial001.py hl[9,11,16,22,24,29:30,33:35,40:41] *}
-
-### 🔃 `**user_in.dict()`
-
-#### Pydantic `.dict()`
-
-`user_in` Pydantic 🏷 🎓 `UserIn`.
-
-Pydantic 🏷 ✔️ `.dict()` 👩🔬 👈 📨 `dict` ⏮️ 🏷 💽.
-
-, 🚥 👥 ✍ Pydantic 🎚 `user_in` 💖:
-
-```Python
-user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
-```
-
-& ⤴️ 👥 🤙:
-
-```Python
-user_dict = user_in.dict()
-```
-
-👥 🔜 ✔️ `dict` ⏮️ 💽 🔢 `user_dict` (⚫️ `dict` ↩️ Pydantic 🏷 🎚).
-
-& 🚥 👥 🤙:
-
-```Python
-print(user_dict)
-```
-
-👥 🔜 🤚 🐍 `dict` ⏮️:
-
-```Python
-{
- 'username': 'john',
- 'password': 'secret',
- 'email': 'john.doe@example.com',
- 'full_name': None,
-}
-```
-
-#### 🎁 `dict`
-
-🚥 👥 ✊ `dict` 💖 `user_dict` & 🚶♀️ ⚫️ 🔢 (⚖️ 🎓) ⏮️ `**user_dict`, 🐍 🔜 "🎁" ⚫️. ⚫️ 🔜 🚶♀️ 🔑 & 💲 `user_dict` 🔗 🔑-💲 ❌.
-
-, ▶️ ⏮️ `user_dict` ⚪️➡️ 🔛, ✍:
-
-```Python
-UserInDB(**user_dict)
-```
-
-🔜 🏁 🕳 🌓:
-
-```Python
-UserInDB(
- username="john",
- password="secret",
- email="john.doe@example.com",
- full_name=None,
-)
-```
-
-⚖️ 🌅 ⚫️❔, ⚙️ `user_dict` 🔗, ⏮️ ⚫️❔ 🎚 ⚫️ 💪 ✔️ 🔮:
-
-```Python
-UserInDB(
- username = user_dict["username"],
- password = user_dict["password"],
- email = user_dict["email"],
- full_name = user_dict["full_name"],
-)
-```
-
-#### Pydantic 🏷 ⚪️➡️ 🎚 ➕1️⃣
-
-🖼 🔛 👥 🤚 `user_dict` ⚪️➡️ `user_in.dict()`, 👉 📟:
-
-```Python
-user_dict = user_in.dict()
-UserInDB(**user_dict)
-```
-
-🔜 🌓:
-
-```Python
-UserInDB(**user_in.dict())
-```
-
-...↩️ `user_in.dict()` `dict`, & ⤴️ 👥 ⚒ 🐍 "🎁" ⚫️ 🚶♀️ ⚫️ `UserInDB` 🔠 ⏮️ `**`.
-
-, 👥 🤚 Pydantic 🏷 ⚪️➡️ 💽 ➕1️⃣ Pydantic 🏷.
-
-#### 🎁 `dict` & ➕ 🇨🇻
-
-& ⤴️ ❎ ➕ 🇨🇻 ❌ `hashed_password=hashed_password`, 💖:
-
-```Python
-UserInDB(**user_in.dict(), hashed_password=hashed_password)
-```
-
-...🔚 🆙 💆♂ 💖:
-
-```Python
-UserInDB(
- username = user_dict["username"],
- password = user_dict["password"],
- email = user_dict["email"],
- full_name = user_dict["full_name"],
- hashed_password = hashed_password,
-)
-```
-
-/// warning
-
-🔗 🌖 🔢 🤖 💪 💧 💽, ✋️ 👫 ↗️ 🚫 🚚 🙆 🎰 💂♂.
-
-///
-
-## 📉 ❎
-
-📉 📟 ❎ 1️⃣ 🐚 💭 **FastAPI**.
-
-📟 ❎ 📈 🤞 🐛, 💂♂ ❔, 📟 🔁 ❔ (🕐❔ 👆 ℹ 1️⃣ 🥉 ✋️ 🚫 🎏), ♒️.
-
-& 👉 🏷 🌐 🤝 📚 💽 & ❎ 🔢 📛 & 🆎.
-
-👥 💪 👻.
-
-👥 💪 📣 `UserBase` 🏷 👈 🍦 🧢 👆 🎏 🏷. & ⤴️ 👥 💪 ⚒ 🏿 👈 🏷 👈 😖 🚮 🔢 (🆎 📄, 🔬, ♒️).
-
-🌐 💽 🛠️, 🔬, 🧾, ♒️. 🔜 👷 🛎.
-
-👈 🌌, 👥 💪 📣 🔺 🖖 🏷 (⏮️ 🔢 `password`, ⏮️ `hashed_password` & 🍵 🔐):
-
-{* ../../docs_src/extra_models/tutorial002.py hl[9,15:16,19:20,23:24] *}
-
-## `Union` ⚖️ `anyOf`
-
-👆 💪 📣 📨 `Union` 2️⃣ 🆎, 👈 ⛓, 👈 📨 🔜 🙆 2️⃣.
-
-⚫️ 🔜 🔬 🗄 ⏮️ `anyOf`.
-
-👈, ⚙️ 🐩 🐍 🆎 🔑 `typing.Union`:
-
-/// note
-
-🕐❔ ⚖ `Union`, 🔌 🏆 🎯 🆎 🥇, ⏩ 🌘 🎯 🆎. 🖼 🔛, 🌖 🎯 `PlaneItem` 👟 ⏭ `CarItem` `Union[PlaneItem, CarItem]`.
-
-///
-
-{* ../../docs_src/extra_models/tutorial003.py hl[1,14:15,18:20,33] *}
-
-### `Union` 🐍 3️⃣.1️⃣0️⃣
-
-👉 🖼 👥 🚶♀️ `Union[PlaneItem, CarItem]` 💲 ❌ `response_model`.
-
-↩️ 👥 🚶♀️ ⚫️ **💲 ❌** ↩️ 🚮 ⚫️ **🆎 ✍**, 👥 ✔️ ⚙️ `Union` 🐍 3️⃣.1️⃣0️⃣.
-
-🚥 ⚫️ 🆎 ✍ 👥 💪 ✔️ ⚙️ ⏸ ⏸,:
-
-```Python
-some_variable: PlaneItem | CarItem
-```
-
-✋️ 🚥 👥 🚮 👈 `response_model=PlaneItem | CarItem` 👥 🔜 🤚 ❌, ↩️ 🐍 🔜 🔄 🎭 **❌ 🛠️** 🖖 `PlaneItem` & `CarItem` ↩️ 🔬 👈 🆎 ✍.
-
-## 📇 🏷
-
-🎏 🌌, 👆 💪 📣 📨 📇 🎚.
-
-👈, ⚙️ 🐩 🐍 `typing.List` (⚖️ `list` 🐍 3️⃣.9️⃣ & 🔛):
-
-{* ../../docs_src/extra_models/tutorial004.py hl[1,20] *}
-
-## 📨 ⏮️ ❌ `dict`
-
-👆 💪 📣 📨 ⚙️ ✅ ❌ `dict`, 📣 🆎 🔑 & 💲, 🍵 ⚙️ Pydantic 🏷.
-
-👉 ⚠ 🚥 👆 🚫 💭 ☑ 🏑/🔢 📛 (👈 🔜 💪 Pydantic 🏷) ⏪.
-
-👉 💼, 👆 💪 ⚙️ `typing.Dict` (⚖️ `dict` 🐍 3️⃣.9️⃣ & 🔛):
-
-{* ../../docs_src/extra_models/tutorial005.py hl[1,8] *}
-
-## 🌃
-
-⚙️ 💗 Pydantic 🏷 & 😖 ➡ 🔠 💼.
-
-👆 🚫 💪 ✔️ 👁 💽 🏷 📍 👨💼 🚥 👈 👨💼 🔜 💪 ✔️ 🎏 "🇵🇸". 💼 ⏮️ 👩💻 "👨💼" ⏮️ 🇵🇸 ✅ `password`, `password_hash` & 🙅♂ 🔐.
diff --git a/docs/em/docs/tutorial/first-steps.md b/docs/em/docs/tutorial/first-steps.md
deleted file mode 100644
index f9bb3fb756..0000000000
--- a/docs/em/docs/tutorial/first-steps.md
+++ /dev/null
@@ -1,335 +0,0 @@
-# 🥇 🔁
-
-🙅 FastAPI 📁 💪 👀 💖 👉:
-
-{* ../../docs_src/first_steps/tutorial001.py *}
-
-📁 👈 📁 `main.py`.
-
-🏃 🖖 💽:
-
-get 🛠️
-
-/// info | `@decorator` ℹ
-
-👈 `@something` ❕ 🐍 🤙 "👨🎨".
-
-👆 🚮 ⚫️ 🔛 🔝 🔢. 💖 📶 📔 👒 (👤 💭 👈 🌐❔ ⚖ 👟 ⚪️➡️).
-
- "👨🎨" ✊ 🔢 🔛 & 🔨 🕳 ⏮️ ⚫️.
-
-👆 💼, 👉 👨🎨 💬 **FastAPI** 👈 🔢 🔛 🔗 **➡** `/` ⏮️ **🛠️** `get`.
-
-⚫️ "**➡ 🛠️ 👨🎨**".
-
-///
-
-👆 💪 ⚙️ 🎏 🛠️:
-
-* `@app.post()`
-* `@app.put()`
-* `@app.delete()`
-
-& 🌅 😍 🕐:
-
-* `@app.options()`
-* `@app.head()`
-* `@app.patch()`
-* `@app.trace()`
-
-/// tip
-
-👆 🆓 ⚙️ 🔠 🛠️ (🇺🇸🔍 👩🔬) 👆 🎋.
-
-**FastAPI** 🚫 🛠️ 🙆 🎯 🔑.
-
-ℹ 📥 🎁 📄, 🚫 📄.
-
-🖼, 🕐❔ ⚙️ 🕹 👆 🛎 🎭 🌐 🎯 ⚙️ 🕴 `POST` 🛠️.
-
-///
-
-### 🔁 4️⃣: 🔬 **➡ 🛠️ 🔢**
-
-👉 👆 "**➡ 🛠️ 🔢**":
-
-* **➡**: `/`.
-* **🛠️**: `get`.
-* **🔢**: 🔢 🔛 "👨🎨" (🔛 `@app.get("/")`).
-
-{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
-
-👉 🐍 🔢.
-
-⚫️ 🔜 🤙 **FastAPI** 🕐❔ ⚫️ 📨 📨 📛 "`/`" ⚙️ `GET` 🛠️.
-
-👉 💼, ⚫️ `async` 🔢.
-
----
-
-👆 💪 🔬 ⚫️ 😐 🔢 ↩️ `async def`:
-
-{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
-
-/// note
-
-🚥 👆 🚫 💭 🔺, ✅ [🔁: *"🏃 ❓"*](../async.md#_2){.internal-link target=_blank}.
-
-///
-
-### 🔁 5️⃣: 📨 🎚
-
-{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
-
-👆 💪 📨 `dict`, `list`, ⭐ 💲 `str`, `int`, ♒️.
-
-👆 💪 📨 Pydantic 🏷 (👆 🔜 👀 🌅 🔃 👈 ⏪).
-
-📤 📚 🎏 🎚 & 🏷 👈 🔜 🔁 🗜 🎻 (🔌 🐜, ♒️). 🔄 ⚙️ 👆 💕 🕐, ⚫️ 🏆 🎲 👈 👫 ⏪ 🐕🦺.
-
-## 🌃
-
-* 🗄 `FastAPI`.
-* ✍ `app` 👐.
-* ✍ **➡ 🛠️ 👨🎨** (💖 `@app.get("/")`).
-* ✍ **➡ 🛠️ 🔢** (💖 `def root(): ...` 🔛).
-* 🏃 🛠️ 💽 (💖 `uvicorn main:app --reload`).
diff --git a/docs/em/docs/tutorial/handling-errors.md b/docs/em/docs/tutorial/handling-errors.md
deleted file mode 100644
index 6d72775976..0000000000
--- a/docs/em/docs/tutorial/handling-errors.md
+++ /dev/null
@@ -1,257 +0,0 @@
-# 🚚 ❌
-
-📤 📚 ⚠ 🌐❔ 👆 💪 🚨 ❌ 👩💻 👈 ⚙️ 👆 🛠️.
-
-👉 👩💻 💪 🖥 ⏮️ 🕸, 📟 ⚪️➡️ 👱 🙆, ☁ 📳, ♒️.
-
-👆 💪 💪 💬 👩💻 👈:
-
-* 👩💻 🚫 ✔️ 🥃 😌 👈 🛠️.
-* 👩💻 🚫 ✔️ 🔐 👈 ℹ.
-* 🏬 👩💻 🔄 🔐 🚫 🔀.
-* ♒️.
-
-👫 💼, 👆 🔜 🛎 📨 **🇺🇸🔍 👔 📟** ↔ **4️⃣0️⃣0️⃣** (⚪️➡️ 4️⃣0️⃣0️⃣ 4️⃣9️⃣9️⃣).
-
-👉 🎏 2️⃣0️⃣0️⃣ 🇺🇸🔍 👔 📟 (⚪️➡️ 2️⃣0️⃣0️⃣ 2️⃣9️⃣9️⃣). 👈 "2️⃣0️⃣0️⃣" 👔 📟 ⛓ 👈 😫 📤 "🏆" 📨.
-
-👔 📟 4️⃣0️⃣0️⃣ ↔ ⛓ 👈 📤 ❌ ⚪️➡️ 👩💻.
-
-💭 🌐 👈 **"4️⃣0️⃣4️⃣ 🚫 🔎"** ❌ (& 🤣) ❓
-
-## ⚙️ `HTTPException`
-
-📨 🇺🇸🔍 📨 ⏮️ ❌ 👩💻 👆 ⚙️ `HTTPException`.
-
-### 🗄 `HTTPException`
-
-{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
-
-### 🤚 `HTTPException` 👆 📟
-
-`HTTPException` 😐 🐍 ⚠ ⏮️ 🌖 📊 🔗 🔗.
-
-↩️ ⚫️ 🐍 ⚠, 👆 🚫 `return` ⚫️, 👆 `raise` ⚫️.
-
-👉 ⛓ 👈 🚥 👆 🔘 🚙 🔢 👈 👆 🤙 🔘 👆 *➡ 🛠️ 🔢*, & 👆 🤚 `HTTPException` ⚪️➡️ 🔘 👈 🚙 🔢, ⚫️ 🏆 🚫 🏃 🎂 📟 *➡ 🛠️ 🔢*, ⚫️ 🔜 ❎ 👈 📨 ▶️️ ↖️ & 📨 🇺🇸🔍 ❌ ⚪️➡️ `HTTPException` 👩💻.
-
-💰 🙋♀ ⚠ 🤭 `return`😅 💲 🔜 🌖 ⭐ 📄 🔃 🔗 & 💂♂.
-
-👉 🖼, 🕐❔ 👩💻 📨 🏬 🆔 👈 🚫 🔀, 🤚 ⚠ ⏮️ 👔 📟 `404`:
-
-{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
-
-### 📉 📨
-
-🚥 👩💻 📨 `http://example.com/items/foo` ( `item_id` `"foo"`), 👈 👩💻 🔜 📨 🇺🇸🔍 👔 📟 2️⃣0️⃣0️⃣, & 🎻 📨:
-
-```JSON
-{
- "item": "The Foo Wrestlers"
-}
-```
-
-✋️ 🚥 👩💻 📨 `http://example.com/items/bar` (🚫-🚫 `item_id` `"bar"`), 👈 👩💻 🔜 📨 🇺🇸🔍 👔 📟 4️⃣0️⃣4️⃣ ("🚫 🔎" ❌), & 🎻 📨:
-
-```JSON
-{
- "detail": "Item not found"
-}
-```
-
-/// tip
-
-🕐❔ 🙋♀ `HTTPException`, 👆 💪 🚶♀️ 🙆 💲 👈 💪 🗜 🎻 🔢 `detail`, 🚫 🕴 `str`.
-
-👆 💪 🚶♀️ `dict`, `list`, ♒️.
-
-👫 🍵 🔁 **FastAPI** & 🗜 🎻.
-
-///
-
-## 🚮 🛃 🎚
-
-📤 ⚠ 🌐❔ ⚫️ ⚠ 💪 🚮 🛃 🎚 🇺🇸🔍 ❌. 🖼, 🆎 💂♂.
-
-👆 🎲 🏆 🚫 💪 ⚙️ ⚫️ 🔗 👆 📟.
-
-✋️ 💼 👆 💪 ⚫️ 🏧 😐, 👆 💪 🚮 🛃 🎚:
-
-{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
-
-## ❎ 🛃 ⚠ 🐕🦺
-
-👆 💪 🚮 🛃 ⚠ 🐕🦺 ⏮️ 🎏 ⚠ 🚙 ⚪️➡️ 💃.
-
-➡️ 💬 👆 ✔️ 🛃 ⚠ `UnicornException` 👈 👆 (⚖️ 🗃 👆 ⚙️) 💪 `raise`.
-
-& 👆 💚 🍵 👉 ⚠ 🌐 ⏮️ FastAPI.
-
-👆 💪 🚮 🛃 ⚠ 🐕🦺 ⏮️ `@app.exception_handler()`:
-
-{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *}
-
-📥, 🚥 👆 📨 `/unicorns/yolo`, *➡ 🛠️* 🔜 `raise` `UnicornException`.
-
-✋️ ⚫️ 🔜 🍵 `unicorn_exception_handler`.
-
-, 👆 🔜 📨 🧹 ❌, ⏮️ 🇺🇸🔍 👔 📟 `418` & 🎻 🎚:
-
-```JSON
-{"message": "Oops! yolo did something. There goes a rainbow..."}
-```
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.requests import Request` & `from starlette.responses import JSONResponse`.
-
-**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. 🎏 ⏮️ `Request`.
-
-///
-
-## 🔐 🔢 ⚠ 🐕🦺
-
-**FastAPI** ✔️ 🔢 ⚠ 🐕🦺.
-
-👫 🐕🦺 🈚 🛬 🔢 🎻 📨 🕐❔ 👆 `raise` `HTTPException` & 🕐❔ 📨 ✔️ ❌ 💽.
-
-👆 💪 🔐 👫 ⚠ 🐕🦺 ⏮️ 👆 👍.
-
-### 🔐 📨 🔬 ⚠
-
-🕐❔ 📨 🔌 ❌ 📊, **FastAPI** 🔘 🤚 `RequestValidationError`.
-
-& ⚫️ 🔌 🔢 ⚠ 🐕🦺 ⚫️.
-
-🔐 ⚫️, 🗄 `RequestValidationError` & ⚙️ ⚫️ ⏮️ `@app.exception_handler(RequestValidationError)` 🎀 ⚠ 🐕🦺.
-
-⚠ 🐕🦺 🔜 📨 `Request` & ⚠.
-
-{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *}
-
-🔜, 🚥 👆 🚶 `/items/foo`, ↩️ 💆♂ 🔢 🎻 ❌ ⏮️:
-
-```JSON
-{
- "detail": [
- {
- "loc": [
- "path",
- "item_id"
- ],
- "msg": "value is not a valid integer",
- "type": "type_error.integer"
- }
- ]
-}
-```
-
-👆 🔜 🤚 ✍ ⏬, ⏮️:
-
-```
-1 validation error
-path -> item_id
- value is not a valid integer (type=type_error.integer)
-```
-
-#### `RequestValidationError` 🆚 `ValidationError`
-
-/// warning
-
-👫 📡 ℹ 👈 👆 💪 🚶 🚥 ⚫️ 🚫 ⚠ 👆 🔜.
-
-///
-
-`RequestValidationError` 🎧-🎓 Pydantic `ValidationError`.
-
-**FastAPI** ⚙️ ⚫️ 👈, 🚥 👆 ⚙️ Pydantic 🏷 `response_model`, & 👆 💽 ✔️ ❌, 👆 🔜 👀 ❌ 👆 🕹.
-
-✋️ 👩💻/👩💻 🔜 🚫 👀 ⚫️. ↩️, 👩💻 🔜 📨 "🔗 💽 ❌" ⏮️ 🇺🇸🔍 👔 📟 `500`.
-
-⚫️ 🔜 👉 🌌 ↩️ 🚥 👆 ✔️ Pydantic `ValidationError` 👆 *📨* ⚖️ 🙆 👆 📟 (🚫 👩💻 *📨*), ⚫️ 🤙 🐛 👆 📟.
-
-& ⏪ 👆 🔧 ⚫️, 👆 👩💻/👩💻 🚫🔜 🚫 ✔️ 🔐 🔗 ℹ 🔃 ❌, 👈 💪 🎦 💂♂ ⚠.
-
-### 🔐 `HTTPException` ❌ 🐕🦺
-
-🎏 🌌, 👆 💪 🔐 `HTTPException` 🐕🦺.
-
-🖼, 👆 💪 💚 📨 ✅ ✍ 📨 ↩️ 🎻 👫 ❌:
-
-{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.responses import PlainTextResponse`.
-
-**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃.
-
-///
-
-### ⚙️ `RequestValidationError` 💪
-
-`RequestValidationError` 🔌 `body` ⚫️ 📨 ⏮️ ❌ 💽.
-
-👆 💪 ⚙️ ⚫️ ⏪ 🛠️ 👆 📱 🕹 💪 & ℹ ⚫️, 📨 ⚫️ 👩💻, ♒️.
-
-{* ../../docs_src/handling_errors/tutorial005.py hl[14] *}
-
-🔜 🔄 📨 ❌ 🏬 💖:
-
-```JSON
-{
- "title": "towel",
- "size": "XL"
-}
-```
-
-👆 🔜 📨 📨 💬 👆 👈 💽 ❌ ⚗ 📨 💪:
-
-```JSON hl_lines="12-15"
-{
- "detail": [
- {
- "loc": [
- "body",
- "size"
- ],
- "msg": "value is not a valid integer",
- "type": "type_error.integer"
- }
- ],
- "body": {
- "title": "towel",
- "size": "XL"
- }
-}
-```
-
-#### FastAPI `HTTPException` 🆚 💃 `HTTPException`
-
-**FastAPI** ✔️ 🚮 👍 `HTTPException`.
-
-& **FastAPI**'Ⓜ `HTTPException` ❌ 🎓 😖 ⚪️➡️ 💃 `HTTPException` ❌ 🎓.
-
-🕴 🔺, 👈 **FastAPI**'Ⓜ `HTTPException` ✔ 👆 🚮 🎚 🔌 📨.
-
-👉 💪/⚙️ 🔘 ✳ 2️⃣.0️⃣ & 💂♂ 🚙.
-
-, 👆 💪 🚧 🙋♀ **FastAPI**'Ⓜ `HTTPException` 🛎 👆 📟.
-
-✋️ 🕐❔ 👆 ® ⚠ 🐕🦺, 👆 🔜 ® ⚫️ 💃 `HTTPException`.
-
-👉 🌌, 🚥 🙆 🍕 💃 🔗 📟, ⚖️ 💃 ↔ ⚖️ 🔌 -, 🤚 💃 `HTTPException`, 👆 🐕🦺 🔜 💪 ✊ & 🍵 ⚫️.
-
-👉 🖼, 💪 ✔️ 👯♂️ `HTTPException`Ⓜ 🎏 📟, 💃 ⚠ 📁 `StarletteHTTPException`:
-
-```Python
-from starlette.exceptions import HTTPException as StarletteHTTPException
-```
-
-### 🏤-⚙️ **FastAPI**'Ⓜ ⚠ 🐕🦺
-
-🚥 👆 💚 ⚙️ ⚠ ⤴️ ⏮️ 🎏 🔢 ⚠ 🐕🦺 ⚪️➡️ **FastAPI**, 👆 💪 🗄 & 🏤-⚙️ 🔢 ⚠ 🐕🦺 ⚪️➡️ `fastapi.exception_handlers`:
-
-{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *}
-
-👉 🖼 👆 `print`😅 ❌ ⏮️ 📶 🎨 📧, ✋️ 👆 🤚 💭. 👆 💪 ⚙️ ⚠ & ⤴️ 🏤-⚙️ 🔢 ⚠ 🐕🦺.
diff --git a/docs/em/docs/tutorial/header-params.md b/docs/em/docs/tutorial/header-params.md
deleted file mode 100644
index fa5e3a22b4..0000000000
--- a/docs/em/docs/tutorial/header-params.md
+++ /dev/null
@@ -1,91 +0,0 @@
-# 🎚 🔢
-
-👆 💪 🔬 🎚 🔢 🎏 🌌 👆 🔬 `Query`, `Path` & `Cookie` 🔢.
-
-## 🗄 `Header`
-
-🥇 🗄 `Header`:
-
-{* ../../docs_src/header_params/tutorial001.py hl[3] *}
-
-## 📣 `Header` 🔢
-
-⤴️ 📣 🎚 🔢 ⚙️ 🎏 📊 ⏮️ `Path`, `Query` & `Cookie`.
-
-🥇 💲 🔢 💲, 👆 💪 🚶♀️ 🌐 ➕ 🔬 ⚖️ ✍ 🔢:
-
-{* ../../docs_src/header_params/tutorial001.py hl[9] *}
-
-/// note | 📡 ℹ
-
-`Header` "👭" 🎓 `Path`, `Query` & `Cookie`. ⚫️ 😖 ⚪️➡️ 🎏 ⚠ `Param` 🎓.
-
-✋️ 💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, `Header`, & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓.
-
-///
-
-/// info
-
-📣 🎚, 👆 💪 ⚙️ `Header`, ↩️ ⏪ 🔢 🔜 🔬 🔢 🔢.
-
-///
-
-## 🏧 🛠️
-
-`Header` ✔️ 🐥 ➕ 🛠️ 🔛 🔝 ⚫️❔ `Path`, `Query` & `Cookie` 🚚.
-
-🌅 🐩 🎚 🎏 "🔠" 🦹, 💭 "➖ 🔣" (`-`).
-
-✋️ 🔢 💖 `user-agent` ❌ 🐍.
-
-, 🔢, `Header` 🔜 🗜 🔢 📛 🦹 ⚪️➡️ 🎦 (`_`) 🔠 (`-`) ⚗ & 📄 🎚.
-
-, 🇺🇸🔍 🎚 💼-😛,, 👆 💪 📣 👫 ⏮️ 🐩 🐍 👗 (💭 "🔡").
-
-, 👆 💪 ⚙️ `user_agent` 👆 🛎 🔜 🐍 📟, ↩️ 💆♂ 🎯 🥇 🔤 `User_Agent` ⚖️ 🕳 🎏.
-
-🚥 🤔 👆 💪 ❎ 🏧 🛠️ 🎦 🔠, ⚒ 🔢 `convert_underscores` `Header` `False`:
-
-{* ../../docs_src/header_params/tutorial002.py hl[10] *}
-
-/// warning
-
-⏭ ⚒ `convert_underscores` `False`, 🐻 🤯 👈 🇺🇸🔍 🗳 & 💽 / ⚙️ 🎚 ⏮️ 🎦.
-
-///
-
-## ❎ 🎚
-
-⚫️ 💪 📨 ❎ 🎚. 👈 ⛓, 🎏 🎚 ⏮️ 💗 💲.
-
-👆 💪 🔬 👈 💼 ⚙️ 📇 🆎 📄.
-
-👆 🔜 📨 🌐 💲 ⚪️➡️ ❎ 🎚 🐍 `list`.
-
-🖼, 📣 🎚 `X-Token` 👈 💪 😑 🌅 🌘 🕐, 👆 💪 ✍:
-
-{* ../../docs_src/header_params/tutorial003.py hl[9] *}
-
-🚥 👆 🔗 ⏮️ 👈 *➡ 🛠️* 📨 2️⃣ 🇺🇸🔍 🎚 💖:
-
-```
-X-Token: foo
-X-Token: bar
-```
-
-📨 🔜 💖:
-
-```JSON
-{
- "X-Token values": [
- "bar",
- "foo"
- ]
-}
-```
-
-## 🌃
-
-📣 🎚 ⏮️ `Header`, ⚙️ 🎏 ⚠ ⚓ `Query`, `Path` & `Cookie`.
-
-& 🚫 😟 🔃 🎦 👆 🔢, **FastAPI** 🔜 ✊ 💅 🏭 👫.
diff --git a/docs/em/docs/tutorial/index.md b/docs/em/docs/tutorial/index.md
deleted file mode 100644
index 5f7532341e..0000000000
--- a/docs/em/docs/tutorial/index.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# 🔰 - 👩💻 🦮
-
-👉 🔰 🎦 👆 ❔ ⚙️ **FastAPI** ⏮️ 🌅 🚮 ⚒, 🔁 🔁.
-
-🔠 📄 📉 🏗 🔛 ⏮️ 🕐, ✋️ ⚫️ 🏗 🎏 ❔, 👈 👆 💪 🚶 🔗 🙆 🎯 1️⃣ ❎ 👆 🎯 🛠️ 💪.
-
-⚫️ 🏗 👷 🔮 🔗.
-
-👆 💪 👟 🔙 & 👀 ⚫️❔ ⚫️❔ 👆 💪.
-
-## 🏃 📟
-
-🌐 📟 🍫 💪 📁 & ⚙️ 🔗 (👫 🤙 💯 🐍 📁).
-
-🏃 🙆 🖼, 📁 📟 📁 `main.py`, & ▶️ `uvicorn` ⏮️:
-
-contact 🏑| 🔢 | 🆎 | 📛 |
|---|---|---|
name | str | ⚖ 📛 📧 👨💼/🏢. |
url | str | 📛 ☝ 📧 ℹ. 🔜 📁 📛. |
email | str | 📧 📢 📧 👨💼/🏢. 🔜 📁 📧 📢. |
license_info 🏑| 🔢 | 🆎 | 📛 |
|---|---|---|
name | str | 🚚 (🚥 license_info ⚒). 🛂 📛 ⚙️ 🛠️. |
url | str | 📛 🛂 ⚙️ 🛠️. 🔜 📁 📛. |
-
-## 🗃 🔖
-
-👆 💪 🚮 🌖 🗃 🎏 🔖 ⚙️ 👪 👆 ➡ 🛠️ ⏮️ 🔢 `openapi_tags`.
-
-⚫️ ✊ 📇 ⚗ 1️⃣ 📖 🔠 🔖.
-
-🔠 📖 💪 🔌:
-
-* `name` (**✔**): `str` ⏮️ 🎏 📛 👆 ⚙️ `tags` 🔢 👆 *➡ 🛠️* & `APIRouter`Ⓜ.
-* `description`: `str` ⏮️ 📏 📛 🔖. ⚫️ 💪 ✔️ ✍ & 🔜 🎦 🩺 🎚.
-* `externalDocs`: `dict` 🔬 🔢 🧾 ⏮️:
- * `description`: `str` ⏮️ 📏 📛 🔢 🩺.
- * `url` (**✔**): `str` ⏮️ 📛 🔢 🧾.
-
-### ✍ 🗃 🔖
-
-➡️ 🔄 👈 🖼 ⏮️ 🔖 `users` & `items`.
-
-✍ 🗃 👆 🔖 & 🚶♀️ ⚫️ `openapi_tags` 🔢:
-
-{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
-
-👀 👈 👆 💪 ⚙️ ✍ 🔘 📛, 🖼 "💳" 🔜 🎦 🦁 (**💳**) & "🎀" 🔜 🎦 ❕ (_🎀_).
-
-/// tip
-
-👆 🚫 ✔️ 🚮 🗃 🌐 🔖 👈 👆 ⚙️.
-
-///
-
-### ⚙️ 👆 🔖
-
-⚙️ `tags` 🔢 ⏮️ 👆 *➡ 🛠️* (& `APIRouter`Ⓜ) 🛠️ 👫 🎏 🔖:
-
-{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
-
-/// info
-
-✍ 🌅 🔃 🔖 [➡ 🛠️ 📳](path-operation-configuration.md#_3){.internal-link target=_blank}.
-
-///
-
-### ✅ 🩺
-
-🔜, 🚥 👆 ✅ 🩺, 👫 🔜 🎦 🌐 🌖 🗃:
-
-
-
-### ✔ 🔖
-
-✔ 🔠 🔖 🗃 📖 🔬 ✔ 🎦 🩺 🎚.
-
-🖼, ✋️ `users` 🔜 🚶 ⏮️ `items` 🔤 ✔, ⚫️ 🎦 ⏭ 👫, ↩️ 👥 🚮 👫 🗃 🥇 📖 📇.
-
-## 🗄 📛
-
-🔢, 🗄 🔗 🍦 `/openapi.json`.
-
-✋️ 👆 💪 🔗 ⚫️ ⏮️ 🔢 `openapi_url`.
-
-🖼, ⚒ ⚫️ 🍦 `/api/v1/openapi.json`:
-
-{* ../../docs_src/metadata/tutorial002.py hl[3] *}
-
-🚥 👆 💚 ❎ 🗄 🔗 🍕 👆 💪 ⚒ `openapi_url=None`, 👈 🔜 ❎ 🧾 👩💻 🔢 👈 ⚙️ ⚫️.
-
-## 🩺 📛
-
-👆 💪 🔗 2️⃣ 🧾 👩💻 🔢 🔌:
-
-* **🦁 🎚**: 🍦 `/docs`.
- * 👆 💪 ⚒ 🚮 📛 ⏮️ 🔢 `docs_url`.
- * 👆 💪 ❎ ⚫️ ⚒ `docs_url=None`.
-* **📄**: 🍦 `/redoc`.
- * 👆 💪 ⚒ 🚮 📛 ⏮️ 🔢 `redoc_url`.
- * 👆 💪 ❎ ⚫️ ⚒ `redoc_url=None`.
-
-🖼, ⚒ 🦁 🎚 🍦 `/documentation` & ❎ 📄:
-
-{* ../../docs_src/metadata/tutorial003.py hl[3] *}
diff --git a/docs/em/docs/tutorial/middleware.md b/docs/em/docs/tutorial/middleware.md
deleted file mode 100644
index c77b105544..0000000000
--- a/docs/em/docs/tutorial/middleware.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# 🛠️
-
-👆 💪 🚮 🛠️ **FastAPI** 🈸.
-
-"🛠️" 🔢 👈 👷 ⏮️ 🔠 **📨** ⏭ ⚫️ 🛠️ 🙆 🎯 *➡ 🛠️*. & ⏮️ 🔠 **📨** ⏭ 🛬 ⚫️.
-
-* ⚫️ ✊ 🔠 **📨** 👈 👟 👆 🈸.
-* ⚫️ 💪 ⤴️ 🕳 👈 **📨** ⚖️ 🏃 🙆 💪 📟.
-* ⤴️ ⚫️ 🚶♀️ **📨** 🛠️ 🎂 🈸 ( *➡ 🛠️*).
-* ⚫️ ⤴️ ✊ **📨** 🏗 🈸 ( *➡ 🛠️*).
-* ⚫️ 💪 🕳 👈 **📨** ⚖️ 🏃 🙆 💪 📟.
-* ⤴️ ⚫️ 📨 **📨**.
-
-/// note | 📡 ℹ
-
-🚥 👆 ✔️ 🔗 ⏮️ `yield`, 🚪 📟 🔜 🏃 *⏮️* 🛠️.
-
-🚥 📤 🙆 🖥 📋 (📄 ⏪), 👫 🔜 🏃 *⏮️* 🌐 🛠️.
-
-///
-
-## ✍ 🛠️
-
-✍ 🛠️ 👆 ⚙️ 👨🎨 `@app.middleware("http")` 🔛 🔝 🔢.
-
-🛠️ 🔢 📨:
-
-* `request`.
-* 🔢 `call_next` 👈 🔜 📨 `request` 🔢.
- * 👉 🔢 🔜 🚶♀️ `request` 🔗 *➡ 🛠️*.
- * ⤴️ ⚫️ 📨 `response` 🏗 🔗 *➡ 🛠️*.
-* 👆 💪 ⤴️ 🔀 🌅 `response` ⏭ 🛬 ⚫️.
-
-{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
-
-/// tip
-
-✔️ 🤯 👈 🛃 © 🎚 💪 🚮 ⚙️ '✖-' 🔡.
-
-✋️ 🚥 👆 ✔️ 🛃 🎚 👈 👆 💚 👩💻 🖥 💪 👀, 👆 💪 🚮 👫 👆 ⚜ 📳 ([⚜ (✖️-🇨🇳 ℹ 🤝)](cors.md){.internal-link target=_blank}) ⚙️ 🔢 `expose_headers` 📄 💃 ⚜ 🩺.
-
-///
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.requests import Request`.
-
-**FastAPI** 🚚 ⚫️ 🏪 👆, 👩💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃.
-
-///
-
-### ⏭ & ⏮️ `response`
-
-👆 💪 🚮 📟 🏃 ⏮️ `request`, ⏭ 🙆 *➡ 🛠️* 📨 ⚫️.
-
-& ⏮️ `response` 🏗, ⏭ 🛬 ⚫️.
-
-🖼, 👆 💪 🚮 🛃 🎚 `X-Process-Time` ⚗ 🕰 🥈 👈 ⚫️ ✊ 🛠️ 📨 & 🏗 📨:
-
-{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
-
-## 🎏 🛠️
-
-👆 💪 ⏪ ✍ 🌖 🔃 🎏 🛠️ [🏧 👩💻 🦮: 🏧 🛠️](../advanced/middleware.md){.internal-link target=_blank}.
-
-👆 🔜 ✍ 🔃 ❔ 🍵 ⚜ ⏮️ 🛠️ ⏭ 📄.
diff --git a/docs/em/docs/tutorial/path-operation-configuration.md b/docs/em/docs/tutorial/path-operation-configuration.md
deleted file mode 100644
index c6030c0890..0000000000
--- a/docs/em/docs/tutorial/path-operation-configuration.md
+++ /dev/null
@@ -1,107 +0,0 @@
-# ➡ 🛠️ 📳
-
-📤 📚 🔢 👈 👆 💪 🚶♀️ 👆 *➡ 🛠️ 👨🎨* 🔗 ⚫️.
-
-/// warning
-
-👀 👈 👫 🔢 🚶♀️ 🔗 *➡ 🛠️ 👨🎨*, 🚫 👆 *➡ 🛠️ 🔢*.
-
-///
-
-## 📨 👔 📟
-
-👆 💪 🔬 (🇺🇸🔍) `status_code` ⚙️ 📨 👆 *➡ 🛠️*.
-
-👆 💪 🚶♀️ 🔗 `int` 📟, 💖 `404`.
-
-✋️ 🚥 👆 🚫 💭 ⚫️❔ 🔠 🔢 📟, 👆 💪 ⚙️ ⌨ 📉 `status`:
-
-{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *}
-
-👈 👔 📟 🔜 ⚙️ 📨 & 🔜 🚮 🗄 🔗.
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette import status`.
-
-**FastAPI** 🚚 🎏 `starlette.status` `fastapi.status` 🏪 👆, 👩💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃.
-
-///
-
-## 🔖
-
-👆 💪 🚮 🔖 👆 *➡ 🛠️*, 🚶♀️ 🔢 `tags` ⏮️ `list` `str` (🛎 1️⃣ `str`):
-
-{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *}
-
-👫 🔜 🚮 🗄 🔗 & ⚙️ 🏧 🧾 🔢:
-
-
-
-### 🔖 ⏮️ 🔢
-
-🚥 👆 ✔️ 🦏 🈸, 👆 5️⃣📆 🔚 🆙 📈 **📚 🔖**, & 👆 🔜 💚 ⚒ 💭 👆 🕧 ⚙️ **🎏 🔖** 🔗 *➡ 🛠️*.
-
-👫 💼, ⚫️ 💪 ⚒ 🔑 🏪 🔖 `Enum`.
-
-**FastAPI** 🐕🦺 👈 🎏 🌌 ⏮️ ✅ 🎻:
-
-{* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *}
-
-## 📄 & 📛
-
-👆 💪 🚮 `summary` & `description`:
-
-{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *}
-
-## 📛 ⚪️➡️ #️⃣
-
-📛 😑 📏 & 📔 💗 ⏸, 👆 💪 📣 *➡ 🛠️* 📛 🔢 #️⃣ & **FastAPI** 🔜 ✍ ⚫️ ⚪️➡️ 📤.
-
-👆 💪 ✍ ✍ #️⃣ , ⚫️ 🔜 🔬 & 🖥 ☑ (✊ 🔘 🏧 #️⃣ 📐).
-
-{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *}
-
-⚫️ 🔜 ⚙️ 🎓 🩺:
-
-
-
-## 📨 📛
-
-👆 💪 ✔ 📨 📛 ⏮️ 🔢 `response_description`:
-
-{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *}
-
-/// info
-
-👀 👈 `response_description` 🔗 🎯 📨, `description` 🔗 *➡ 🛠️* 🏢.
-
-///
-
-/// check
-
-🗄 ✔ 👈 🔠 *➡ 🛠️* 🚚 📨 📛.
-
-, 🚥 👆 🚫 🚚 1️⃣, **FastAPI** 🔜 🔁 🏗 1️⃣ "🏆 📨".
-
-///
-
-
-
-## 😢 *➡ 🛠️*
-
-🚥 👆 💪 ™ *➡ 🛠️* 😢, ✋️ 🍵 ❎ ⚫️, 🚶♀️ 🔢 `deprecated`:
-
-{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *}
-
-⚫️ 🔜 🎯 ™ 😢 🎓 🩺:
-
-
-
-✅ ❔ 😢 & 🚫-😢 *➡ 🛠️* 👀 💖:
-
-
-
-## 🌃
-
-👆 💪 🔗 & 🚮 🗃 👆 *➡ 🛠️* 💪 🚶♀️ 🔢 *➡ 🛠️ 👨🎨*.
diff --git a/docs/em/docs/tutorial/path-params-numeric-validations.md b/docs/em/docs/tutorial/path-params-numeric-validations.md
deleted file mode 100644
index b45e0557b6..0000000000
--- a/docs/em/docs/tutorial/path-params-numeric-validations.md
+++ /dev/null
@@ -1,117 +0,0 @@
-# ➡ 🔢 & 🔢 🔬
-
-🎏 🌌 👈 👆 💪 📣 🌅 🔬 & 🗃 🔢 🔢 ⏮️ `Query`, 👆 💪 📣 🎏 🆎 🔬 & 🗃 ➡ 🔢 ⏮️ `Path`.
-
-## 🗄 ➡
-
-🥇, 🗄 `Path` ⚪️➡️ `fastapi`:
-
-{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[3] *}
-
-## 📣 🗃
-
-👆 💪 📣 🌐 🎏 🔢 `Query`.
-
-🖼, 📣 `title` 🗃 💲 ➡ 🔢 `item_id` 👆 💪 🆎:
-
-{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[10] *}
-
-/// note
-
-➡ 🔢 🕧 ✔ ⚫️ ✔️ 🍕 ➡.
-
-, 👆 🔜 📣 ⚫️ ⏮️ `...` ™ ⚫️ ✔.
-
-👐, 🚥 👆 📣 ⚫️ ⏮️ `None` ⚖️ ⚒ 🔢 💲, ⚫️ 🔜 🚫 📉 🕳, ⚫️ 🔜 🕧 🚚.
-
-///
-
-## ✔ 🔢 👆 💪
-
-➡️ 💬 👈 👆 💚 📣 🔢 🔢 `q` ✔ `str`.
-
-& 👆 🚫 💪 📣 🕳 🙆 👈 🔢, 👆 🚫 🤙 💪 ⚙️ `Query`.
-
-✋️ 👆 💪 ⚙️ `Path` `item_id` ➡ 🔢.
-
-🐍 🔜 😭 🚥 👆 🚮 💲 ⏮️ "🔢" ⏭ 💲 👈 🚫 ✔️ "🔢".
-
-✋️ 👆 💪 🏤-✔ 👫, & ✔️ 💲 🍵 🔢 (🔢 🔢 `q`) 🥇.
-
-⚫️ 🚫 🤔 **FastAPI**. ⚫️ 🔜 🔍 🔢 👫 📛, 🆎 & 🔢 📄 (`Query`, `Path`, ♒️), ⚫️ 🚫 💅 🔃 ✔.
-
-, 👆 💪 📣 👆 🔢:
-
-{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
-
-## ✔ 🔢 👆 💪, 🎱
-
-🚥 👆 💚 📣 `q` 🔢 🔢 🍵 `Query` 🚫 🙆 🔢 💲, & ➡ 🔢 `item_id` ⚙️ `Path`, & ✔️ 👫 🎏 ✔, 🐍 ✔️ 🐥 🎁 ❕ 👈.
-
-🚶♀️ `*`, 🥇 🔢 🔢.
-
-🐍 🏆 🚫 🕳 ⏮️ 👈 `*`, ✋️ ⚫️ 🔜 💭 👈 🌐 📄 🔢 🔜 🤙 🇨🇻 ❌ (🔑-💲 👫), 💭 kwargs. 🚥 👫 🚫 ✔️ 🔢 💲.
-
-{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
-
-## 🔢 🔬: 👑 🌘 ⚖️ 🌓
-
-⏮️ `Query` & `Path` (& 🎏 👆 🔜 👀 ⏪) 👆 💪 📣 🔢 ⚛.
-
-📥, ⏮️ `ge=1`, `item_id` 🔜 💪 🔢 🔢 "`g`🅾 🌘 ⚖️ `e`🅾" `1`.
-
-{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *}
-
-## 🔢 🔬: 🌘 🌘 & 🌘 🌘 ⚖️ 🌓
-
-🎏 ✔:
-
-* `gt`: `g`🅾 `t`👲
-* `le`: `l`👭 🌘 ⚖️ `e`🅾
-
-{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *}
-
-## 🔢 🔬: 🎈, 🌘 🌘 & 🌘 🌘
-
-🔢 🔬 👷 `float` 💲.
-
-📥 🌐❔ ⚫️ ▶️️ ⚠ 💪 📣 gt & 🚫 ge. ⏮️ ⚫️ 👆 💪 🚚, 🖼, 👈 💲 🔜 👑 🌘 `0`, 🚥 ⚫️ 🌘 🌘 `1`.
-
-, `0.5` 🔜 ☑ 💲. ✋️ `0.0` ⚖️ `0` 🔜 🚫.
-
-& 🎏 lt.
-
-{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *}
-
-## 🌃
-
-⏮️ `Query`, `Path` (& 🎏 👆 🚫 👀) 👆 💪 📣 🗃 & 🎻 🔬 🎏 🌌 ⏮️ [🔢 🔢 & 🎻 🔬](query-params-str-validations.md){.internal-link target=_blank}.
-
-& 👆 💪 📣 🔢 🔬:
-
-* `gt`: `g`🅾 `t`👲
-* `ge`: `g`🅾 🌘 ⚖️ `e`🅾
-* `lt`: `l`👭 `t`👲
-* `le`: `l`👭 🌘 ⚖️ `e`🅾
-
-/// info
-
-`Query`, `Path`, & 🎏 🎓 👆 🔜 👀 ⏪ 🏿 ⚠ `Param` 🎓.
-
-🌐 👫 💰 🎏 🔢 🌖 🔬 & 🗃 👆 ✔️ 👀.
-
-///
-
-/// note | 📡 ℹ
-
-🕐❔ 👆 🗄 `Query`, `Path` & 🎏 ⚪️➡️ `fastapi`, 👫 🤙 🔢.
-
-👈 🕐❔ 🤙, 📨 👐 🎓 🎏 📛.
-
-, 👆 🗄 `Query`, ❔ 🔢. & 🕐❔ 👆 🤙 ⚫️, ⚫️ 📨 👐 🎓 🌟 `Query`.
-
-👫 🔢 📤 (↩️ ⚙️ 🎓 🔗) 👈 👆 👨🎨 🚫 ™ ❌ 🔃 👫 🆎.
-
-👈 🌌 👆 💪 ⚙️ 👆 😐 👨🎨 & 🛠️ 🧰 🍵 ✔️ 🚮 🛃 📳 🤷♂ 📚 ❌.
-
-///
diff --git a/docs/em/docs/tutorial/path-params.md b/docs/em/docs/tutorial/path-params.md
deleted file mode 100644
index a914dc9050..0000000000
--- a/docs/em/docs/tutorial/path-params.md
+++ /dev/null
@@ -1,256 +0,0 @@
-# ➡ 🔢
-
-👆 💪 📣 ➡ "🔢" ⚖️ "🔢" ⏮️ 🎏 ❕ ⚙️ 🐍 📁 🎻:
-
-{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
-
-💲 ➡ 🔢 `item_id` 🔜 🚶♀️ 👆 🔢 ❌ `item_id`.
-
-, 🚥 👆 🏃 👉 🖼 & 🚶 http://127.0.0.1:8000/items/foo, 👆 🔜 👀 📨:
-
-```JSON
-{"item_id":"foo"}
-```
-
-## ➡ 🔢 ⏮️ 🆎
-
-👆 💪 📣 🆎 ➡ 🔢 🔢, ⚙️ 🐩 🐍 🆎 ✍:
-
-{* ../../docs_src/path_params/tutorial002.py hl[7] *}
-
-👉 💼, `item_id` 📣 `int`.
-
-/// check
-
-👉 🔜 🤝 👆 👨🎨 🐕🦺 🔘 👆 🔢, ⏮️ ❌ ✅, 🛠️, ♒️.
-
-///
-
-## 💽 🛠️
-
-🚥 👆 🏃 👉 🖼 & 📂 👆 🖥 http://127.0.0.1:8000/items/3, 👆 🔜 👀 📨:
-
-```JSON
-{"item_id":3}
-```
-
-/// check
-
-👀 👈 💲 👆 🔢 📨 (& 📨) `3`, 🐍 `int`, 🚫 🎻 `"3"`.
-
-, ⏮️ 👈 🆎 📄, **FastAPI** 🤝 👆 🏧 📨 "✍".
-
-///
-
-## 💽 🔬
-
-✋️ 🚥 👆 🚶 🖥 http://127.0.0.1:8000/items/foo, 👆 🔜 👀 👌 🇺🇸🔍 ❌:
-
-```JSON
-{
- "detail": [
- {
- "loc": [
- "path",
- "item_id"
- ],
- "msg": "value is not a valid integer",
- "type": "type_error.integer"
- }
- ]
-}
-```
-
-↩️ ➡ 🔢 `item_id` ✔️ 💲 `"foo"`, ❔ 🚫 `int`.
-
-🎏 ❌ 🔜 😑 🚥 👆 🚚 `float` ↩️ `int`,: http://127.0.0.1:8000/items/4.2
-
-/// check
-
-, ⏮️ 🎏 🐍 🆎 📄, **FastAPI** 🤝 👆 💽 🔬.
-
-👀 👈 ❌ 🎯 🇵🇸 ⚫️❔ ☝ 🌐❔ 🔬 🚫 🚶♀️.
-
-👉 🙃 👍 ⏪ 🛠️ & 🛠️ 📟 👈 🔗 ⏮️ 👆 🛠️.
-
-///
-
-## 🧾
-
-& 🕐❔ 👆 📂 👆 🖥 http://127.0.0.1:8000/docs, 👆 🔜 👀 🏧, 🎓, 🛠️ 🧾 💖:
-
-
-
-/// check
-
-🔄, ⏮️ 👈 🎏 🐍 🆎 📄, **FastAPI** 🤝 👆 🏧, 🎓 🧾 (🛠️ 🦁 🎚).
-
-👀 👈 ➡ 🔢 📣 🔢.
-
-///
-
-## 🐩-⚓️ 💰, 🎛 🧾
-
-& ↩️ 🏗 🔗 ⚪️➡️ 🗄 🐩, 📤 📚 🔗 🧰.
-
-↩️ 👉, **FastAPI** ⚫️ 🚚 🎛 🛠️ 🧾 (⚙️ 📄), ❔ 👆 💪 🔐 http://127.0.0.1:8000/redoc:
-
-
-
-🎏 🌌, 📤 📚 🔗 🧰. ✅ 📟 ⚡ 🧰 📚 🇪🇸.
-
-## Pydantic
-
-🌐 💽 🔬 🎭 🔽 🚘 Pydantic, 👆 🤚 🌐 💰 ⚪️➡️ ⚫️. & 👆 💭 👆 👍 ✋.
-
-👆 💪 ⚙️ 🎏 🆎 📄 ⏮️ `str`, `float`, `bool` & 📚 🎏 🏗 📊 🆎.
-
-📚 👫 🔬 ⏭ 📃 🔰.
-
-## ✔ 🤔
-
-🕐❔ 🏗 *➡ 🛠️*, 👆 💪 🔎 ⚠ 🌐❔ 👆 ✔️ 🔧 ➡.
-
-💖 `/users/me`, ➡️ 💬 👈 ⚫️ 🤚 📊 🔃 ⏮️ 👩💻.
-
-& ⤴️ 👆 💪 ✔️ ➡ `/users/{user_id}` 🤚 💽 🔃 🎯 👩💻 👩💻 🆔.
-
-↩️ *➡ 🛠️* 🔬 ✔, 👆 💪 ⚒ 💭 👈 ➡ `/users/me` 📣 ⏭ 1️⃣ `/users/{user_id}`:
-
-{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
-
-⏪, ➡ `/users/{user_id}` 🔜 🏏 `/users/me`, "💭" 👈 ⚫️ 📨 🔢 `user_id` ⏮️ 💲 `"me"`.
-
-➡, 👆 🚫🔜 ↔ ➡ 🛠️:
-
-{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *}
-
-🥇 🕐 🔜 🕧 ⚙️ ↩️ ➡ 🏏 🥇.
-
-## 🔁 💲
-
-🚥 👆 ✔️ *➡ 🛠️* 👈 📨 *➡ 🔢*, ✋️ 👆 💚 💪 ☑ *➡ 🔢* 💲 🔁, 👆 💪 ⚙️ 🐩 🐍 `Enum`.
-
-### ✍ `Enum` 🎓
-
-🗄 `Enum` & ✍ 🎧-🎓 👈 😖 ⚪️➡️ `str` & ⚪️➡️ `Enum`.
-
-😖 ⚪️➡️ `str` 🛠️ 🩺 🔜 💪 💭 👈 💲 🔜 🆎 `string` & 🔜 💪 ✍ ☑.
-
-⤴️ ✍ 🎓 🔢 ⏮️ 🔧 💲, ❔ 🔜 💪 ☑ 💲:
-
-{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
-
-/// info
-
-🔢 (⚖️ 🔢) 💪 🐍 ↩️ ⏬ 3️⃣.4️⃣.
-
-///
-
-/// tip
-
-🚥 👆 💭, "📊", "🎓", & "🍏" 📛 🎰 🏫 🏷.
-
-///
-
-### 📣 *➡ 🔢*
-
-⤴️ ✍ *➡ 🔢* ⏮️ 🆎 ✍ ⚙️ 🔢 🎓 👆 ✍ (`ModelName`):
-
-{* ../../docs_src/path_params/tutorial005.py hl[16] *}
-
-### ✅ 🩺
-
-↩️ 💪 💲 *➡ 🔢* 🔢, 🎓 🩺 💪 🎦 👫 🎆:
-
-
-
-### 👷 ⏮️ 🐍 *🔢*
-
-💲 *➡ 🔢* 🔜 *🔢 👨🎓*.
-
-#### 🔬 *🔢 👨🎓*
-
-👆 💪 🔬 ⚫️ ⏮️ *🔢 👨🎓* 👆 ✍ 🔢 `ModelName`:
-
-{* ../../docs_src/path_params/tutorial005.py hl[17] *}
-
-#### 🤚 *🔢 💲*
-
-👆 💪 🤚 ☑ 💲 ( `str` 👉 💼) ⚙️ `model_name.value`, ⚖️ 🏢, `your_enum_member.value`:
-
-{* ../../docs_src/path_params/tutorial005.py hl[20] *}
-
-/// tip
-
-👆 💪 🔐 💲 `"lenet"` ⏮️ `ModelName.lenet.value`.
-
-///
-
-#### 📨 *🔢 👨🎓*
-
-👆 💪 📨 *🔢 👨🎓* ⚪️➡️ 👆 *➡ 🛠️*, 🐦 🎻 💪 (✅ `dict`).
-
-👫 🔜 🗜 👫 🔗 💲 (🎻 👉 💼) ⏭ 🛬 👫 👩💻:
-
-{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
-
-👆 👩💻 👆 🔜 🤚 🎻 📨 💖:
-
-```JSON
-{
- "model_name": "alexnet",
- "message": "Deep Learning FTW!"
-}
-```
-
-## ➡ 🔢 ⚗ ➡
-
-➡️ 💬 👆 ✔️ *➡ 🛠️* ⏮️ ➡ `/files/{file_path}`.
-
-✋️ 👆 💪 `file_path` ⚫️ 🔌 *➡*, 💖 `home/johndoe/myfile.txt`.
-
-, 📛 👈 📁 🔜 🕳 💖: `/files/home/johndoe/myfile.txt`.
-
-### 🗄 🐕🦺
-
-🗄 🚫 🐕🦺 🌌 📣 *➡ 🔢* 🔌 *➡* 🔘, 👈 💪 ↘️ 😐 👈 ⚠ 💯 & 🔬.
-
-👐, 👆 💪 ⚫️ **FastAPI**, ⚙️ 1️⃣ 🔗 🧰 ⚪️➡️ 💃.
-
-& 🩺 🔜 👷, 👐 🚫 ❎ 🙆 🧾 💬 👈 🔢 🔜 🔌 ➡.
-
-### ➡ 🔌
-
-⚙️ 🎛 🔗 ⚪️➡️ 💃 👆 💪 📣 *➡ 🔢* ⚗ *➡* ⚙️ 📛 💖:
-
-```
-/files/{file_path:path}
-```
-
-👉 💼, 📛 🔢 `file_path`, & 🏁 🍕, `:path`, 💬 ⚫️ 👈 🔢 🔜 🏏 🙆 *➡*.
-
-, 👆 💪 ⚙️ ⚫️ ⏮️:
-
-{* ../../docs_src/path_params/tutorial004.py hl[6] *}
-
-/// tip
-
-👆 💪 💪 🔢 🔌 `/home/johndoe/myfile.txt`, ⏮️ 🏁 🔪 (`/`).
-
-👈 💼, 📛 🔜: `/files//home/johndoe/myfile.txt`, ⏮️ 2️⃣✖️ 🔪 (`//`) 🖖 `files` & `home`.
-
-///
-
-## 🌃
-
-⏮️ **FastAPI**, ⚙️ 📏, 🏋️ & 🐩 🐍 🆎 📄, 👆 🤚:
-
-* 👨🎨 🐕🦺: ❌ ✅, ✍, ♒️.
-* 💽 "✍"
-* 💽 🔬
-* 🛠️ ✍ & 🏧 🧾
-
-& 👆 🕴 ✔️ 📣 👫 🕐.
-
-👈 🎲 👑 ⭐ 📈 **FastAPI** 🔬 🎛 🛠️ (↖️ ⚪️➡️ 🍣 🎭).
diff --git a/docs/em/docs/tutorial/query-params-str-validations.md b/docs/em/docs/tutorial/query-params-str-validations.md
deleted file mode 100644
index fd077bf8f7..0000000000
--- a/docs/em/docs/tutorial/query-params-str-validations.md
+++ /dev/null
@@ -1,320 +0,0 @@
-# 🔢 🔢 & 🎻 🔬
-
-**FastAPI** ✔ 👆 📣 🌖 ℹ & 🔬 👆 🔢.
-
-➡️ ✊ 👉 🈸 🖼:
-
-{* ../../docs_src/query_params_str_validations/tutorial001.py hl[9] *}
-
-🔢 🔢 `q` 🆎 `Union[str, None]` (⚖️ `str | None` 🐍 3️⃣.1️⃣0️⃣), 👈 ⛓ 👈 ⚫️ 🆎 `str` ✋️ 💪 `None`, & 👐, 🔢 💲 `None`, FastAPI 🔜 💭 ⚫️ 🚫 ✔.
-
-/// note
-
-FastAPI 🔜 💭 👈 💲 `q` 🚫 ✔ ↩️ 🔢 💲 `= None`.
-
- `Union` `Union[str, None]` 🔜 ✔ 👆 👨🎨 🤝 👆 👍 🐕🦺 & 🔍 ❌.
-
-///
-
-## 🌖 🔬
-
-👥 🔜 🛠️ 👈 ✋️ `q` 📦, 🕐❔ ⚫️ 🚚, **🚮 📐 🚫 📉 5️⃣0️⃣ 🦹**.
-
-### 🗄 `Query`
-
-🏆 👈, 🥇 🗄 `Query` ⚪️➡️ `fastapi`:
-
-{* ../../docs_src/query_params_str_validations/tutorial002.py hl[3] *}
-
-## ⚙️ `Query` 🔢 💲
-
-& 🔜 ⚙️ ⚫️ 🔢 💲 👆 🔢, ⚒ 🔢 `max_length` 5️⃣0️⃣:
-
-{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *}
-
-👥 ✔️ ❎ 🔢 💲 `None` 🔢 ⏮️ `Query()`, 👥 💪 🔜 ⚒ 🔢 💲 ⏮️ 🔢 `Query(default=None)`, ⚫️ 🍦 🎏 🎯 ⚖ 👈 🔢 💲.
-
-:
-
-```Python
-q: Union[str, None] = Query(default=None)
-```
-
-...⚒ 🔢 📦, 🎏:
-
-```Python
-q: Union[str, None] = None
-```
-
-& 🐍 3️⃣.1️⃣0️⃣ & 🔛:
-
-```Python
-q: str | None = Query(default=None)
-```
-
-...⚒ 🔢 📦, 🎏:
-
-```Python
-q: str | None = None
-```
-
-✋️ ⚫️ 📣 ⚫️ 🎯 💆♂ 🔢 🔢.
-
-/// info
-
-✔️ 🤯 👈 🌅 ⚠ 🍕 ⚒ 🔢 📦 🍕:
-
-```Python
-= None
-```
-
-⚖️:
-
-```Python
-= Query(default=None)
-```
-
-⚫️ 🔜 ⚙️ 👈 `None` 🔢 💲, & 👈 🌌 ⚒ 🔢 **🚫 ✔**.
-
- `Union[str, None]` 🍕 ✔ 👆 👨🎨 🚚 👻 🐕🦺, ✋️ ⚫️ 🚫 ⚫️❔ 💬 FastAPI 👈 👉 🔢 🚫 ✔.
-
-///
-
-⤴️, 👥 💪 🚶♀️ 🌅 🔢 `Query`. 👉 💼, `max_length` 🔢 👈 ✔ 🎻:
-
-```Python
-q: Union[str, None] = Query(default=None, max_length=50)
-```
-
-👉 🔜 ✔ 📊, 🎦 🆑 ❌ 🕐❔ 📊 🚫 ☑, & 📄 🔢 🗄 🔗 *➡ 🛠️*.
-
-## 🚮 🌅 🔬
-
-👆 💪 🚮 🔢 `min_length`:
-
-{* ../../docs_src/query_params_str_validations/tutorial003.py hl[10] *}
-
-## 🚮 🥔 🧬
-
-👆 💪 🔬 🥔 🧬 👈 🔢 🔜 🏏:
-
-{* ../../docs_src/query_params_str_validations/tutorial004.py hl[11] *}
-
-👉 🎯 🥔 🧬 ✅ 👈 📨 🔢 💲:
-
-* `^`: ▶️ ⏮️ 📄 🦹, 🚫 ✔️ 🦹 ⏭.
-* `fixedquery`: ✔️ ☑ 💲 `fixedquery`.
-* `$`: 🔚 📤, 🚫 ✔️ 🙆 🌖 🦹 ⏮️ `fixedquery`.
-
-🚥 👆 💭 💸 ⏮️ 🌐 👉 **"🥔 🧬"** 💭, 🚫 😟. 👫 🏋️ ❔ 📚 👫👫. 👆 💪 📚 💩 🍵 💆♂ 🥔 🧬.
-
-✋️ 🕐❔ 👆 💪 👫 & 🚶 & 💡 👫, 💭 👈 👆 💪 ⏪ ⚙️ 👫 🔗 **FastAPI**.
-
-## 🔢 💲
-
-🎏 🌌 👈 👆 💪 🚶♀️ `None` 💲 `default` 🔢, 👆 💪 🚶♀️ 🎏 💲.
-
-➡️ 💬 👈 👆 💚 📣 `q` 🔢 🔢 ✔️ `min_length` `3`, & ✔️ 🔢 💲 `"fixedquery"`:
-
-{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *}
-
-/// note
-
-✔️ 🔢 💲 ⚒ 🔢 📦.
-
-///
-
-## ⚒ ⚫️ ✔
-
-🕐❔ 👥 🚫 💪 📣 🌅 🔬 ⚖️ 🗃, 👥 💪 ⚒ `q` 🔢 🔢 ✔ 🚫 📣 🔢 💲, 💖:
-
-```Python
-q: str
-```
-
-↩️:
-
-```Python
-q: Union[str, None] = None
-```
-
-✋️ 👥 🔜 📣 ⚫️ ⏮️ `Query`, 🖼 💖:
-
-```Python
-q: Union[str, None] = Query(default=None, min_length=3)
-```
-
-, 🕐❔ 👆 💪 📣 💲 ✔ ⏪ ⚙️ `Query`, 👆 💪 🎯 🚫 📣 🔢 💲:
-
-{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *}
-
-### ✔ ⏮️ `None`
-
-👆 💪 📣 👈 🔢 💪 🚫 `None`, ✋️ 👈 ⚫️ ✔. 👉 🔜 ⚡ 👩💻 📨 💲, 🚥 💲 `None`.
-
-👈, 👆 💪 📣 👈 `None` ☑ 🆎 ✋️ ⚙️ `default=...`:
-
-{* ../../docs_src/query_params_str_validations/tutorial006c.py hl[9] *}
-
-/// tip
-
-Pydantic, ❔ ⚫️❔ 🏋️ 🌐 💽 🔬 & 🛠️ FastAPI, ✔️ 🎁 🎭 🕐❔ 👆 ⚙️ `Optional` ⚖️ `Union[Something, None]` 🍵 🔢 💲, 👆 💪 ✍ 🌅 🔃 ⚫️ Pydantic 🩺 🔃 ✔ 📦 🏑.
-
-///
-
-## 🔢 🔢 📇 / 💗 💲
-
-🕐❔ 👆 🔬 🔢 🔢 🎯 ⏮️ `Query` 👆 💪 📣 ⚫️ 📨 📇 💲, ⚖️ 🙆♀ 🎏 🌌, 📨 💗 💲.
-
-🖼, 📣 🔢 🔢 `q` 👈 💪 😑 💗 🕰 📛, 👆 💪 ✍:
-
-{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *}
-
-⤴️, ⏮️ 📛 💖:
-
-```
-http://localhost:8000/items/?q=foo&q=bar
-```
-
-👆 🔜 📨 💗 `q` *🔢 🔢'* 💲 (`foo` & `bar`) 🐍 `list` 🔘 👆 *➡ 🛠️ 🔢*, *🔢 🔢* `q`.
-
-, 📨 👈 📛 🔜:
-
-```JSON
-{
- "q": [
- "foo",
- "bar"
- ]
-}
-```
-
-/// tip
-
-📣 🔢 🔢 ⏮️ 🆎 `list`, 💖 🖼 🔛, 👆 💪 🎯 ⚙️ `Query`, ⏪ ⚫️ 🔜 🔬 📨 💪.
-
-///
-
-🎓 🛠️ 🩺 🔜 ℹ ➡️, ✔ 💗 💲:
-
-
-
-### 🔢 🔢 📇 / 💗 💲 ⏮️ 🔢
-
-& 👆 💪 🔬 🔢 `list` 💲 🚥 👌 🚚:
-
-{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *}
-
-🚥 👆 🚶:
-
-```
-http://localhost:8000/items/
-```
-
-🔢 `q` 🔜: `["foo", "bar"]` & 👆 📨 🔜:
-
-```JSON
-{
- "q": [
- "foo",
- "bar"
- ]
-}
-```
-
-#### ⚙️ `list`
-
-👆 💪 ⚙️ `list` 🔗 ↩️ `List[str]` (⚖️ `list[str]` 🐍 3️⃣.9️⃣ ➕):
-
-{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *}
-
-/// note
-
-✔️ 🤯 👈 👉 💼, FastAPI 🏆 🚫 ✅ 🎚 📇.
-
-🖼, `List[int]` 🔜 ✅ (& 📄) 👈 🎚 📇 🔢. ✋️ `list` 😞 🚫🔜.
-
-///
-
-## 📣 🌅 🗃
-
-👆 💪 🚮 🌅 ℹ 🔃 🔢.
-
-👈 ℹ 🔜 🔌 🏗 🗄 & ⚙️ 🧾 👩💻 🔢 & 🔢 🧰.
-
-/// note
-
-✔️ 🤯 👈 🎏 🧰 5️⃣📆 ✔️ 🎏 🎚 🗄 🐕🦺.
-
-👫 💪 🚫 🎦 🌐 ➕ ℹ 📣, 👐 🌅 💼, ❌ ⚒ ⏪ 📄 🛠️.
-
-///
-
-👆 💪 🚮 `title`:
-
-{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *}
-
-& `description`:
-
-{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *}
-
-## 📛 🔢
-
-🌈 👈 👆 💚 🔢 `item-query`.
-
-💖:
-
-```
-http://127.0.0.1:8000/items/?item-query=foobaritems
-```
-
-✋️ `item-query` 🚫 ☑ 🐍 🔢 📛.
-
-🔐 🔜 `item_query`.
-
-✋️ 👆 💪 ⚫️ ⚫️❔ `item-query`...
-
-⤴️ 👆 💪 📣 `alias`, & 👈 📛 ⚫️❔ 🔜 ⚙️ 🔎 🔢 💲:
-
-{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *}
-
-## 😛 🔢
-
-🔜 ➡️ 💬 👆 🚫 💖 👉 🔢 🚫🔜.
-
-👆 ✔️ 👈 ⚫️ 📤 ⏪ ↩️ 📤 👩💻 ⚙️ ⚫️, ✋️ 👆 💚 🩺 🎯 🎦 ⚫️ 😢.
-
-⤴️ 🚶♀️ 🔢 `deprecated=True` `Query`:
-
-{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *}
-
-🩺 🔜 🎦 ⚫️ 💖 👉:
-
-
-
-## 🚫 ⚪️➡️ 🗄
-
-🚫 🔢 🔢 ⚪️➡️ 🏗 🗄 🔗 (& ➡️, ⚪️➡️ 🏧 🧾 ⚙️), ⚒ 🔢 `include_in_schema` `Query` `False`:
-
-{* ../../docs_src/query_params_str_validations/tutorial014.py hl[10] *}
-
-## 🌃
-
-👆 💪 📣 🌖 🔬 & 🗃 👆 🔢.
-
-💊 🔬 & 🗃:
-
-* `alias`
-* `title`
-* `description`
-* `deprecated`
-
-🔬 🎯 🎻:
-
-* `min_length`
-* `max_length`
-* `regex`
-
-👫 🖼 👆 👀 ❔ 📣 🔬 `str` 💲.
-
-👀 ⏭ 📃 👀 ❔ 📣 🔬 🎏 🆎, 💖 🔢.
diff --git a/docs/em/docs/tutorial/query-params.md b/docs/em/docs/tutorial/query-params.md
deleted file mode 100644
index 5c8d868a9a..0000000000
--- a/docs/em/docs/tutorial/query-params.md
+++ /dev/null
@@ -1,187 +0,0 @@
-# 🔢 🔢
-
-🕐❔ 👆 📣 🎏 🔢 🔢 👈 🚫 🍕 ➡ 🔢, 👫 🔁 🔬 "🔢" 🔢.
-
-{* ../../docs_src/query_params/tutorial001.py hl[9] *}
-
-🔢 ⚒ 🔑-💲 👫 👈 🚶 ⏮️ `?` 📛, 🎏 `&` 🦹.
-
-🖼, 📛:
-
-```
-http://127.0.0.1:8000/items/?skip=0&limit=10
-```
-
-...🔢 🔢:
-
-* `skip`: ⏮️ 💲 `0`
-* `limit`: ⏮️ 💲 `10`
-
-👫 🍕 📛, 👫 "🛎" 🎻.
-
-✋️ 🕐❔ 👆 📣 👫 ⏮️ 🐍 🆎 (🖼 🔛, `int`), 👫 🗜 👈 🆎 & ✔ 🛡 ⚫️.
-
-🌐 🎏 🛠️ 👈 ⚖ ➡ 🔢 ✔ 🔢 🔢:
-
-* 👨🎨 🐕🦺 (🎲)
-* 💽 "✍"
-* 💽 🔬
-* 🏧 🧾
-
-## 🔢
-
-🔢 🔢 🚫 🔧 🍕 ➡, 👫 💪 📦 & 💪 ✔️ 🔢 💲.
-
-🖼 🔛 👫 ✔️ 🔢 💲 `skip=0` & `limit=10`.
-
-, 🔜 📛:
-
-```
-http://127.0.0.1:8000/items/
-```
-
-🔜 🎏 🔜:
-
-```
-http://127.0.0.1:8000/items/?skip=0&limit=10
-```
-
-✋️ 🚥 👆 🚶, 🖼:
-
-```
-http://127.0.0.1:8000/items/?skip=20
-```
-
-🔢 💲 👆 🔢 🔜:
-
-* `skip=20`: ↩️ 👆 ⚒ ⚫️ 📛
-* `limit=10`: ↩️ 👈 🔢 💲
-
-## 📦 🔢
-
-🎏 🌌, 👆 💪 📣 📦 🔢 🔢, ⚒ 👫 🔢 `None`:
-
-{* ../../docs_src/query_params/tutorial002.py hl[9] *}
-
-👉 💼, 🔢 🔢 `q` 🔜 📦, & 🔜 `None` 🔢.
-
-/// check
-
-👀 👈 **FastAPI** 🙃 🥃 👀 👈 ➡ 🔢 `item_id` ➡ 🔢 & `q` 🚫,, ⚫️ 🔢 🔢.
-
-///
-
-## 🔢 🔢 🆎 🛠️
-
-👆 💪 📣 `bool` 🆎, & 👫 🔜 🗜:
-
-{* ../../docs_src/query_params/tutorial003.py hl[9] *}
-
-👉 💼, 🚥 👆 🚶:
-
-```
-http://127.0.0.1:8000/items/foo?short=1
-```
-
-⚖️
-
-```
-http://127.0.0.1:8000/items/foo?short=True
-```
-
-⚖️
-
-```
-http://127.0.0.1:8000/items/foo?short=true
-```
-
-⚖️
-
-```
-http://127.0.0.1:8000/items/foo?short=on
-```
-
-⚖️
-
-```
-http://127.0.0.1:8000/items/foo?short=yes
-```
-
-⚖️ 🙆 🎏 💼 📈 (🔠, 🥇 🔤 🔠, ♒️), 👆 🔢 🔜 👀 🔢 `short` ⏮️ `bool` 💲 `True`. ⏪ `False`.
-
-
-## 💗 ➡ & 🔢 🔢
-
-👆 💪 📣 💗 ➡ 🔢 & 🔢 🔢 🎏 🕰, **FastAPI** 💭 ❔ ❔.
-
-& 👆 🚫 ✔️ 📣 👫 🙆 🎯 ✔.
-
-👫 🔜 🔬 📛:
-
-{* ../../docs_src/query_params/tutorial004.py hl[8,10] *}
-
-## ✔ 🔢 🔢
-
-🕐❔ 👆 📣 🔢 💲 🚫-➡ 🔢 (🔜, 👥 ✔️ 🕴 👀 🔢 🔢), ⤴️ ⚫️ 🚫 ✔.
-
-🚥 👆 🚫 💚 🚮 🎯 💲 ✋️ ⚒ ⚫️ 📦, ⚒ 🔢 `None`.
-
-✋️ 🕐❔ 👆 💚 ⚒ 🔢 🔢 ✔, 👆 💪 🚫 📣 🙆 🔢 💲:
-
-{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
-
-📥 🔢 🔢 `needy` ✔ 🔢 🔢 🆎 `str`.
-
-🚥 👆 📂 👆 🖥 📛 💖:
-
-```
-http://127.0.0.1:8000/items/foo-item
-```
-
-...🍵 ❎ ✔ 🔢 `needy`, 👆 🔜 👀 ❌ 💖:
-
-```JSON
-{
- "detail": [
- {
- "loc": [
- "query",
- "needy"
- ],
- "msg": "field required",
- "type": "value_error.missing"
- }
- ]
-}
-```
-
-`needy` 🚚 🔢, 👆 🔜 💪 ⚒ ⚫️ 📛:
-
-```
-http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
-```
-
-...👉 🔜 👷:
-
-```JSON
-{
- "item_id": "foo-item",
- "needy": "sooooneedy"
-}
-```
-
-& ↗️, 👆 💪 🔬 🔢 ✔, ✔️ 🔢 💲, & 🍕 📦:
-
-{* ../../docs_src/query_params/tutorial006.py hl[10] *}
-
-👉 💼, 📤 3️⃣ 🔢 🔢:
-
-* `needy`, ✔ `str`.
-* `skip`, `int` ⏮️ 🔢 💲 `0`.
-* `limit`, 📦 `int`.
-
-/// tip
-
-👆 💪 ⚙️ `Enum`Ⓜ 🎏 🌌 ⏮️ [➡ 🔢](path-params.md#_7){.internal-link target=_blank}.
-
-///
diff --git a/docs/em/docs/tutorial/request-files.md b/docs/em/docs/tutorial/request-files.md
deleted file mode 100644
index c3bdeafd48..0000000000
--- a/docs/em/docs/tutorial/request-files.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# 📨 📁
-
-👆 💪 🔬 📁 📂 👩💻 ⚙️ `File`.
-
-/// info
-
-📨 📂 📁, 🥇 ❎ `python-multipart`.
-
-🤶 Ⓜ. `pip install python-multipart`.
-
-👉 ↩️ 📂 📁 📨 "📨 💽".
-
-///
-
-## 🗄 `File`
-
-🗄 `File` & `UploadFile` ⚪️➡️ `fastapi`:
-
-{* ../../docs_src/request_files/tutorial001.py hl[1] *}
-
-## 🔬 `File` 🔢
-
-✍ 📁 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Form`:
-
-{* ../../docs_src/request_files/tutorial001.py hl[7] *}
-
-/// info
-
-`File` 🎓 👈 😖 🔗 ⚪️➡️ `Form`.
-
-✋️ 💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, `File` & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓.
-
-///
-
-/// tip
-
-📣 📁 💪, 👆 💪 ⚙️ `File`, ↩️ ⏪ 🔢 🔜 🔬 🔢 🔢 ⚖️ 💪 (🎻) 🔢.
-
-///
-
-📁 🔜 📂 "📨 💽".
-
-🚥 👆 📣 🆎 👆 *➡ 🛠️ 🔢* 🔢 `bytes`, **FastAPI** 🔜 ✍ 📁 👆 & 👆 🔜 📨 🎚 `bytes`.
-
-✔️ 🤯 👈 👉 ⛓ 👈 🎂 🎚 🔜 🏪 💾. 👉 🔜 👷 👍 🤪 📁.
-
-✋️ 📤 📚 💼 ❔ 👆 💪 💰 ⚪️➡️ ⚙️ `UploadFile`.
-
-## 📁 🔢 ⏮️ `UploadFile`
-
-🔬 📁 🔢 ⏮️ 🆎 `UploadFile`:
-
-{* ../../docs_src/request_files/tutorial001.py hl[12] *}
-
-⚙️ `UploadFile` ✔️ 📚 📈 🤭 `bytes`:
-
-* 👆 🚫 ✔️ ⚙️ `File()` 🔢 💲 🔢.
-* ⚫️ ⚙️ "🧵" 📁:
- * 📁 🏪 💾 🆙 🔆 📐 📉, & ⏮️ 🚶♀️ 👉 📉 ⚫️ 🔜 🏪 💾.
-* 👉 ⛓ 👈 ⚫️ 🔜 👷 👍 ⭕ 📁 💖 🖼, 📹, ⭕ 💱, ♒️. 🍵 😩 🌐 💾.
-* 👆 💪 🤚 🗃 ⚪️➡️ 📂 📁.
-* ⚫️ ✔️ 📁-💖 `async` 🔢.
-* ⚫️ 🎦 ☑ 🐍 `SpooledTemporaryFile` 🎚 👈 👆 💪 🚶♀️ 🔗 🎏 🗃 👈 ⌛ 📁-💖 🎚.
-
-### `UploadFile`
-
-`UploadFile` ✔️ 📄 🔢:
-
-* `filename`: `str` ⏮️ ⏮️ 📁 📛 👈 📂 (✅ `myimage.jpg`).
-* `content_type`: `str` ⏮️ 🎚 🆎 (📁 🆎 / 📻 🆎) (✅ `image/jpeg`).
-* `file`: `SpooledTemporaryFile` ( 📁-💖 🎚). 👉 ☑ 🐍 📁 👈 👆 💪 🚶♀️ 🔗 🎏 🔢 ⚖️ 🗃 👈 ⌛ "📁-💖" 🎚.
-
-`UploadFile` ✔️ 📄 `async` 👩🔬. 👫 🌐 🤙 🔗 📁 👩🔬 🔘 (⚙️ 🔗 `SpooledTemporaryFile`).
-
-* `write(data)`: ✍ `data` (`str` ⚖️ `bytes`) 📁.
-* `read(size)`: ✍ `size` (`int`) 🔢/🦹 📁.
-* `seek(offset)`: 🚶 🔢 🧘 `offset` (`int`) 📁.
- * 🤶 Ⓜ., `await myfile.seek(0)` 🔜 🚶 ▶️ 📁.
- * 👉 ✴️ ⚠ 🚥 👆 🏃 `await myfile.read()` 🕐 & ⤴️ 💪 ✍ 🎚 🔄.
-* `close()`: 🔐 📁.
-
-🌐 👫 👩🔬 `async` 👩🔬, 👆 💪 "⌛" 👫.
-
-🖼, 🔘 `async` *➡ 🛠️ 🔢* 👆 💪 🤚 🎚 ⏮️:
-
-```Python
-contents = await myfile.read()
-```
-
-🚥 👆 🔘 😐 `def` *➡ 🛠️ 🔢*, 👆 💪 🔐 `UploadFile.file` 🔗, 🖼:
-
-```Python
-contents = myfile.file.read()
-```
-
-/// note | `async` 📡 ℹ
-
-🕐❔ 👆 ⚙️ `async` 👩🔬, **FastAPI** 🏃 📁 👩🔬 🧵 & ⌛ 👫.
-
-///
-
-/// note | 💃 📡 ℹ
-
-**FastAPI**'Ⓜ `UploadFile` 😖 🔗 ⚪️➡️ **💃**'Ⓜ `UploadFile`, ✋️ 🚮 💪 🍕 ⚒ ⚫️ 🔗 ⏮️ **Pydantic** & 🎏 🍕 FastAPI.
-
-///
-
-## ⚫️❔ "📨 💽"
-
-🌌 🕸 📨 (``) 📨 💽 💽 🛎 ⚙️ "🎁" 🔢 👈 📊, ⚫️ 🎏 ⚪️➡️ 🎻.
-
-**FastAPI** 🔜 ⚒ 💭 ✍ 👈 📊 ⚪️➡️ ▶️️ 🥉 ↩️ 🎻.
-
-/// note | 📡 ℹ
-
-📊 ⚪️➡️ 📨 🛎 🗜 ⚙️ "📻 🆎" `application/x-www-form-urlencoded` 🕐❔ ⚫️ 🚫 🔌 📁.
-
-✋️ 🕐❔ 📨 🔌 📁, ⚫️ 🗜 `multipart/form-data`. 🚥 👆 ⚙️ `File`, **FastAPI** 🔜 💭 ⚫️ ✔️ 🤚 📁 ⚪️➡️ ☑ 🍕 💪.
-
-🚥 👆 💚 ✍ 🌖 🔃 👉 🔢 & 📨 🏑, 👳 🏇 🕸 🩺 POST.
-
-///
-
-/// warning
-
-👆 💪 📣 💗 `File` & `Form` 🔢 *➡ 🛠️*, ✋️ 👆 💪 🚫 📣 `Body` 🏑 👈 👆 ⌛ 📨 🎻, 📨 🔜 ✔️ 💪 🗜 ⚙️ `multipart/form-data` ↩️ `application/json`.
-
-👉 🚫 🚫 **FastAPI**, ⚫️ 🍕 🇺🇸🔍 🛠️.
-
-///
-
-## 📦 📁 📂
-
-👆 💪 ⚒ 📁 📦 ⚙️ 🐩 🆎 ✍ & ⚒ 🔢 💲 `None`:
-
-{* ../../docs_src/request_files/tutorial001_02.py hl[9,17] *}
-
-## `UploadFile` ⏮️ 🌖 🗃
-
-👆 💪 ⚙️ `File()` ⏮️ `UploadFile`, 🖼, ⚒ 🌖 🗃:
-
-{* ../../docs_src/request_files/tutorial001_03.py hl[13] *}
-
-## 💗 📁 📂
-
-⚫️ 💪 📂 📚 📁 🎏 🕰.
-
-👫 🔜 👨💼 🎏 "📨 🏑" 📨 ⚙️ "📨 💽".
-
-⚙️ 👈, 📣 📇 `bytes` ⚖️ `UploadFile`:
-
-{* ../../docs_src/request_files/tutorial002.py hl[10,15] *}
-
-👆 🔜 📨, 📣, `list` `bytes` ⚖️ `UploadFile`Ⓜ.
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.responses import HTMLResponse`.
-
-**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃.
-
-///
-
-### 💗 📁 📂 ⏮️ 🌖 🗃
-
-& 🎏 🌌 ⏭, 👆 💪 ⚙️ `File()` ⚒ 🌖 🔢, `UploadFile`:
-
-{* ../../docs_src/request_files/tutorial003.py hl[18] *}
-
-## 🌃
-
-⚙️ `File`, `bytes`, & `UploadFile` 📣 📁 📂 📨, 📨 📨 💽.
diff --git a/docs/em/docs/tutorial/request-forms-and-files.md b/docs/em/docs/tutorial/request-forms-and-files.md
deleted file mode 100644
index 680b1a96a5..0000000000
--- a/docs/em/docs/tutorial/request-forms-and-files.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# 📨 📨 & 📁
-
-👆 💪 🔬 📁 & 📨 🏑 🎏 🕰 ⚙️ `File` & `Form`.
-
-/// info
-
-📨 📂 📁 & /⚖️ 📨 📊, 🥇 ❎ `python-multipart`.
-
-🤶 Ⓜ. `pip install python-multipart`.
-
-///
-
-## 🗄 `File` & `Form`
-
-{* ../../docs_src/request_forms_and_files/tutorial001.py hl[1] *}
-
-## 🔬 `File` & `Form` 🔢
-
-✍ 📁 & 📨 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Query`:
-
-{* ../../docs_src/request_forms_and_files/tutorial001.py hl[8] *}
-
-📁 & 📨 🏑 🔜 📂 📨 📊 & 👆 🔜 📨 📁 & 📨 🏑.
-
-& 👆 💪 📣 📁 `bytes` & `UploadFile`.
-
-/// warning
-
-👆 💪 📣 💗 `File` & `Form` 🔢 *➡ 🛠️*, ✋️ 👆 💪 🚫 📣 `Body` 🏑 👈 👆 ⌛ 📨 🎻, 📨 🔜 ✔️ 💪 🗜 ⚙️ `multipart/form-data` ↩️ `application/json`.
-
-👉 🚫 🚫 **FastAPI**, ⚫️ 🍕 🇺🇸🔍 🛠️.
-
-///
-
-## 🌃
-
-⚙️ `File` & `Form` 👯♂️ 🕐❔ 👆 💪 📨 💽 & 📁 🎏 📨.
diff --git a/docs/em/docs/tutorial/request-forms.md b/docs/em/docs/tutorial/request-forms.md
deleted file mode 100644
index 1cc1ea5dcb..0000000000
--- a/docs/em/docs/tutorial/request-forms.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# 📨 💽
-
-🕐❔ 👆 💪 📨 📨 🏑 ↩️ 🎻, 👆 💪 ⚙️ `Form`.
-
-/// info
-
-⚙️ 📨, 🥇 ❎ `python-multipart`.
-
-🤶 Ⓜ. `pip install python-multipart`.
-
-///
-
-## 🗄 `Form`
-
-🗄 `Form` ⚪️➡️ `fastapi`:
-
-{* ../../docs_src/request_forms/tutorial001.py hl[1] *}
-
-## 🔬 `Form` 🔢
-
-✍ 📨 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Query`:
-
-{* ../../docs_src/request_forms/tutorial001.py hl[7] *}
-
-🖼, 1️⃣ 🌌 Oauth2️⃣ 🔧 💪 ⚙️ (🤙 "🔐 💧") ⚫️ ✔ 📨 `username` & `password` 📨 🏑.
-
-🔌 🚚 🏑 ⚫️❔ 📛 `username` & `password`, & 📨 📨 🏑, 🚫 🎻.
-
-⏮️ `Form` 👆 💪 📣 🎏 📳 ⏮️ `Body` (& `Query`, `Path`, `Cookie`), 🔌 🔬, 🖼, 📛 (✅ `user-name` ↩️ `username`), ♒️.
-
-/// info
-
-`Form` 🎓 👈 😖 🔗 ⚪️➡️ `Body`.
-
-///
-
-/// tip
-
-📣 📨 💪, 👆 💪 ⚙️ `Form` 🎯, ↩️ 🍵 ⚫️ 🔢 🔜 🔬 🔢 🔢 ⚖️ 💪 (🎻) 🔢.
-
-///
-
-## 🔃 "📨 🏑"
-
-🌌 🕸 📨 (``) 📨 💽 💽 🛎 ⚙️ "🎁" 🔢 👈 📊, ⚫️ 🎏 ⚪️➡️ 🎻.
-
-**FastAPI** 🔜 ⚒ 💭 ✍ 👈 📊 ⚪️➡️ ▶️️ 🥉 ↩️ 🎻.
-
-/// note | 📡 ℹ
-
-📊 ⚪️➡️ 📨 🛎 🗜 ⚙️ "📻 🆎" `application/x-www-form-urlencoded`.
-
-✋️ 🕐❔ 📨 🔌 📁, ⚫️ 🗜 `multipart/form-data`. 👆 🔜 ✍ 🔃 🚚 📁 ⏭ 📃.
-
-🚥 👆 💚 ✍ 🌖 🔃 👉 🔢 & 📨 🏑, 👳 🏇 🕸 🩺 POST.
-
-///
-
-/// warning
-
-👆 💪 📣 💗 `Form` 🔢 *➡ 🛠️*, ✋️ 👆 💪 🚫 📣 `Body` 🏑 👈 👆 ⌛ 📨 🎻, 📨 🔜 ✔️ 💪 🗜 ⚙️ `application/x-www-form-urlencoded` ↩️ `application/json`.
-
-👉 🚫 🚫 **FastAPI**, ⚫️ 🍕 🇺🇸🔍 🛠️.
-
-///
-
-## 🌃
-
-⚙️ `Form` 📣 📨 💽 🔢 🔢.
diff --git a/docs/em/docs/tutorial/response-model.md b/docs/em/docs/tutorial/response-model.md
deleted file mode 100644
index 477376458d..0000000000
--- a/docs/em/docs/tutorial/response-model.md
+++ /dev/null
@@ -1,340 +0,0 @@
-# 📨 🏷 - 📨 🆎
-
-👆 💪 📣 🆎 ⚙️ 📨 ✍ *➡ 🛠️ 🔢* **📨 🆎**.
-
-👆 💪 ⚙️ **🆎 ✍** 🎏 🌌 👆 🔜 🔢 💽 🔢 **🔢**, 👆 💪 ⚙️ Pydantic 🏷, 📇, 📖, 📊 💲 💖 🔢, 🎻, ♒️.
-
-{* ../../docs_src/response_model/tutorial001_01.py hl[18,23] *}
-
-FastAPI 🔜 ⚙️ 👉 📨 🆎:
-
-* **✔** 📨 💽.
- * 🚥 💽 ❌ (✅ 👆 ❌ 🏑), ⚫️ ⛓ 👈 *👆* 📱 📟 💔, 🚫 🛬 ⚫️❔ ⚫️ 🔜, & ⚫️ 🔜 📨 💽 ❌ ↩️ 🛬 ❌ 💽. 👉 🌌 👆 & 👆 👩💻 💪 🎯 👈 👫 🔜 📨 💽 & 💽 💠 📈.
-* 🚮 **🎻 🔗** 📨, 🗄 *➡ 🛠️*.
- * 👉 🔜 ⚙️ **🏧 🩺**.
- * ⚫️ 🔜 ⚙️ 🏧 👩💻 📟 ⚡ 🧰.
-
-✋️ 🏆 🥈:
-
-* ⚫️ 🔜 **📉 & ⛽** 🔢 📊 ⚫️❔ 🔬 📨 🆎.
- * 👉 ✴️ ⚠ **💂♂**, 👥 🔜 👀 🌅 👈 🔛.
-
-## `response_model` 🔢
-
-📤 💼 🌐❔ 👆 💪 ⚖️ 💚 📨 💽 👈 🚫 ⚫️❔ ⚫️❔ 🆎 📣.
-
-🖼, 👆 💪 💚 **📨 📖** ⚖️ 💽 🎚, ✋️ **📣 ⚫️ Pydantic 🏷**. 👉 🌌 Pydantic 🏷 🔜 🌐 💽 🧾, 🔬, ♒️. 🎚 👈 👆 📨 (✅ 📖 ⚖️ 💽 🎚).
-
-🚥 👆 🚮 📨 🆎 ✍, 🧰 & 👨🎨 🔜 😭 ⏮️ (☑) ❌ 💬 👆 👈 👆 🔢 🛬 🆎 (✅#️⃣) 👈 🎏 ⚪️➡️ ⚫️❔ 👆 📣 (✅ Pydantic 🏷).
-
-📚 💼, 👆 💪 ⚙️ *➡ 🛠️ 👨🎨* 🔢 `response_model` ↩️ 📨 🆎.
-
-👆 💪 ⚙️ `response_model` 🔢 🙆 *➡ 🛠️*:
-
-* `@app.get()`
-* `@app.post()`
-* `@app.put()`
-* `@app.delete()`
-* ♒️.
-
-{* ../../docs_src/response_model/tutorial001.py hl[17,22,24:27] *}
-
-/// note
-
-👀 👈 `response_model` 🔢 "👨🎨" 👩🔬 (`get`, `post`, ♒️). 🚫 👆 *➡ 🛠️ 🔢*, 💖 🌐 🔢 & 💪.
-
-///
-
-`response_model` 📨 🎏 🆎 👆 🔜 📣 Pydantic 🏷 🏑,, ⚫️ 💪 Pydantic 🏷, ✋️ ⚫️ 💪, ✅ `list` Pydantic 🏷, 💖 `List[Item]`.
-
-FastAPI 🔜 ⚙️ 👉 `response_model` 🌐 💽 🧾, 🔬, ♒️. & **🗜 & ⛽ 🔢 📊** 🚮 🆎 📄.
-
-/// tip
-
-🚥 👆 ✔️ ⚠ 🆎 ✅ 👆 👨🎨, ✍, ♒️, 👆 💪 📣 🔢 📨 🆎 `Any`.
-
-👈 🌌 👆 💬 👨🎨 👈 👆 😫 🛬 🕳. ✋️ FastAPI 🔜 💽 🧾, 🔬, 🖥, ♒️. ⏮️ `response_model`.
-
-///
-
-### `response_model` 📫
-
-🚥 👆 📣 👯♂️ 📨 🆎 & `response_model`, `response_model` 🔜 ✊ 📫 & ⚙️ FastAPI.
-
-👉 🌌 👆 💪 🚮 ☑ 🆎 ✍ 👆 🔢 🕐❔ 👆 🛬 🆎 🎏 🌘 📨 🏷, ⚙️ 👨🎨 & 🧰 💖 ✍. & 👆 💪 ✔️ FastAPI 💽 🔬, 🧾, ♒️. ⚙️ `response_model`.
-
-👆 💪 ⚙️ `response_model=None` ❎ 🏗 📨 🏷 👈 *➡ 🛠️*, 👆 5️⃣📆 💪 ⚫️ 🚥 👆 ❎ 🆎 ✍ 👜 👈 🚫 ☑ Pydantic 🏑, 👆 🔜 👀 🖼 👈 1️⃣ 📄 🔛.
-
-## 📨 🎏 🔢 💽
-
-📥 👥 📣 `UserIn` 🏷, ⚫️ 🔜 🔌 🔢 🔐:
-
-{* ../../docs_src/response_model/tutorial002.py hl[9,11] *}
-
-/// info
-
-⚙️ `EmailStr`, 🥇 ❎ `email-validator`.
-
-🤶 Ⓜ. `pip install email-validator`
-⚖️ `pip install pydantic[email]`.
-
-///
-
-& 👥 ⚙️ 👉 🏷 📣 👆 🔢 & 🎏 🏷 📣 👆 🔢:
-
-{* ../../docs_src/response_model/tutorial002.py hl[18] *}
-
-🔜, 🕐❔ 🖥 🏗 👩💻 ⏮️ 🔐, 🛠️ 🔜 📨 🎏 🔐 📨.
-
-👉 💼, ⚫️ 💪 🚫 ⚠, ↩️ ⚫️ 🎏 👩💻 📨 🔐.
-
-✋️ 🚥 👥 ⚙️ 🎏 🏷 ➕1️⃣ *➡ 🛠️*, 👥 💪 📨 👆 👩💻 🔐 🔠 👩💻.
-
-/// danger
-
-🙅 🏪 ✅ 🔐 👩💻 ⚖️ 📨 ⚫️ 📨 💖 👉, 🚥 👆 💭 🌐 ⚠ & 👆 💭 ⚫️❔ 👆 🔨.
-
-///
-
-## 🚮 🔢 🏷
-
-👥 💪 ↩️ ✍ 🔢 🏷 ⏮️ 🔢 🔐 & 🔢 🏷 🍵 ⚫️:
-
-{* ../../docs_src/response_model/tutorial003.py hl[9,11,16] *}
-
-📥, ✋️ 👆 *➡ 🛠️ 🔢* 🛬 🎏 🔢 👩💻 👈 🔌 🔐:
-
-{* ../../docs_src/response_model/tutorial003.py hl[24] *}
-
-...👥 📣 `response_model` 👆 🏷 `UserOut`, 👈 🚫 🔌 🔐:
-
-{* ../../docs_src/response_model/tutorial003.py hl[22] *}
-
-, **FastAPI** 🔜 ✊ 💅 🖥 👅 🌐 💽 👈 🚫 📣 🔢 🏷 (⚙️ Pydantic).
-
-### `response_model` ⚖️ 📨 🆎
-
-👉 💼, ↩️ 2️⃣ 🏷 🎏, 🚥 👥 ✍ 🔢 📨 🆎 `UserOut`, 👨🎨 & 🧰 🔜 😭 👈 👥 🛬 ❌ 🆎, 📚 🎏 🎓.
-
-👈 ⚫️❔ 👉 🖼 👥 ✔️ 📣 ⚫️ `response_model` 🔢.
-
-...✋️ 😣 👂 🔛 👀 ❔ ❎ 👈.
-
-## 📨 🆎 & 💽 🖥
-
-➡️ 😣 ⚪️➡️ ⏮️ 🖼. 👥 💚 **✍ 🔢 ⏮️ 1️⃣ 🆎** ✋️ 📨 🕳 👈 🔌 **🌅 💽**.
-
-👥 💚 FastAPI 🚧 **🖥** 📊 ⚙️ 📨 🏷.
-
-⏮️ 🖼, ↩️ 🎓 🎏, 👥 ✔️ ⚙️ `response_model` 🔢. ✋️ 👈 ⛓ 👈 👥 🚫 🤚 🐕🦺 ⚪️➡️ 👨🎨 & 🧰 ✅ 🔢 📨 🆎.
-
-✋️ 🌅 💼 🌐❔ 👥 💪 🕳 💖 👉, 👥 💚 🏷 **⛽/❎** 📊 👉 🖼.
-
-& 👈 💼, 👥 💪 ⚙️ 🎓 & 🧬 ✊ 📈 🔢 **🆎 ✍** 🤚 👍 🐕🦺 👨🎨 & 🧰, & 🤚 FastAPI **💽 🖥**.
-
-{* ../../docs_src/response_model/tutorial003_01.py hl[9:13,15:16,20] *}
-
-⏮️ 👉, 👥 🤚 🏭 🐕🦺, ⚪️➡️ 👨🎨 & ✍ 👉 📟 ☑ ⚖ 🆎, ✋️ 👥 🤚 💽 🖥 ⚪️➡️ FastAPI.
-
-❔ 🔨 👉 👷 ❓ ➡️ ✅ 👈 👅. 👶
-
-### 🆎 ✍ & 🏭
-
-🥇 ➡️ 👀 ❔ 👨🎨, ✍ & 🎏 🧰 🔜 👀 👉.
-
-`BaseUser` ✔️ 🧢 🏑. ⤴️ `UserIn` 😖 ⚪️➡️ `BaseUser` & 🚮 `password` 🏑,, ⚫️ 🔜 🔌 🌐 🏑 ⚪️➡️ 👯♂️ 🏷.
-
-👥 ✍ 🔢 📨 🆎 `BaseUser`, ✋️ 👥 🤙 🛬 `UserIn` 👐.
-
-👨🎨, ✍, & 🎏 🧰 🏆 🚫 😭 🔃 👉 ↩️, ⌨ ⚖, `UserIn` 🏿 `BaseUser`, ❔ ⛓ ⚫️ *☑* 🆎 🕐❔ ⚫️❔ ⌛ 🕳 👈 `BaseUser`.
-
-### FastAPI 💽 🖥
-
-🔜, FastAPI, ⚫️ 🔜 👀 📨 🆎 & ⚒ 💭 👈 ⚫️❔ 👆 📨 🔌 **🕴** 🏑 👈 📣 🆎.
-
-FastAPI 🔨 📚 👜 🔘 ⏮️ Pydantic ⚒ 💭 👈 📚 🎏 🚫 🎓 🧬 🚫 ⚙️ 📨 💽 🖥, ⏪ 👆 💪 🔚 🆙 🛬 🌅 🌅 💽 🌘 ⚫️❔ 👆 📈.
-
-👉 🌌, 👆 💪 🤚 🏆 👯♂️ 🌏: 🆎 ✍ ⏮️ **🏭 🐕🦺** & **💽 🖥**.
-
-## 👀 ⚫️ 🩺
-
-🕐❔ 👆 👀 🏧 🩺, 👆 💪 ✅ 👈 🔢 🏷 & 🔢 🏷 🔜 👯♂️ ✔️ 👫 👍 🎻 🔗:
-
-
-
-& 👯♂️ 🏷 🔜 ⚙️ 🎓 🛠️ 🧾:
-
-
-
-## 🎏 📨 🆎 ✍
-
-📤 5️⃣📆 💼 🌐❔ 👆 📨 🕳 👈 🚫 ☑ Pydantic 🏑 & 👆 ✍ ⚫️ 🔢, 🕴 🤚 🐕🦺 🚚 🏭 (👨🎨, ✍, ♒️).
-
-### 📨 📨 🔗
-
-🏆 ⚠ 💼 🔜 [🛬 📨 🔗 🔬 ⏪ 🏧 🩺](../advanced/response-directly.md){.internal-link target=_blank}.
-
-{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
-
-👉 🙅 💼 🍵 🔁 FastAPI ↩️ 📨 🆎 ✍ 🎓 (⚖️ 🏿) `Response`.
-
-& 🧰 🔜 😄 ↩️ 👯♂️ `RedirectResponse` & `JSONResponse` 🏿 `Response`, 🆎 ✍ ☑.
-
-### ✍ 📨 🏿
-
-👆 💪 ⚙️ 🏿 `Response` 🆎 ✍:
-
-{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
-
-👉 🔜 👷 ↩️ `RedirectResponse` 🏿 `Response`, & FastAPI 🔜 🔁 🍵 👉 🙅 💼.
-
-### ❌ 📨 🆎 ✍
-
-✋️ 🕐❔ 👆 📨 🎏 ❌ 🎚 👈 🚫 ☑ Pydantic 🆎 (✅ 💽 🎚) & 👆 ✍ ⚫️ 💖 👈 🔢, FastAPI 🔜 🔄 ✍ Pydantic 📨 🏷 ⚪️➡️ 👈 🆎 ✍, & 🔜 ❌.
-
-🎏 🔜 🔨 🚥 👆 ✔️ 🕳 💖 🇪🇺 🖖 🎏 🆎 🌐❔ 1️⃣ ⚖️ 🌅 👫 🚫 ☑ Pydantic 🆎, 🖼 👉 🔜 ❌ 👶:
-
-{* ../../docs_src/response_model/tutorial003_04.py hl[10] *}
-
-...👉 ❌ ↩️ 🆎 ✍ 🚫 Pydantic 🆎 & 🚫 👁 `Response` 🎓 ⚖️ 🏿, ⚫️ 🇪🇺 (🙆 2️⃣) 🖖 `Response` & `dict`.
-
-### ❎ 📨 🏷
-
-▶️ ⚪️➡️ 🖼 🔛, 👆 5️⃣📆 🚫 💚 ✔️ 🔢 💽 🔬, 🧾, 🖥, ♒️. 👈 🎭 FastAPI.
-
-✋️ 👆 💪 💚 🚧 📨 🆎 ✍ 🔢 🤚 🐕🦺 ⚪️➡️ 🧰 💖 👨🎨 & 🆎 ☑ (✅ ✍).
-
-👉 💼, 👆 💪 ❎ 📨 🏷 ⚡ ⚒ `response_model=None`:
-
-{* ../../docs_src/response_model/tutorial003_05.py hl[9] *}
-
-👉 🔜 ⚒ FastAPI 🚶 📨 🏷 ⚡ & 👈 🌌 👆 💪 ✔️ 🙆 📨 🆎 ✍ 👆 💪 🍵 ⚫️ 🤕 👆 FastAPI 🈸. 👶
-
-## 📨 🏷 🔢 🔢
-
-👆 📨 🏷 💪 ✔️ 🔢 💲, 💖:
-
-{* ../../docs_src/response_model/tutorial004.py hl[11,13:14] *}
-
-* `description: Union[str, None] = None` (⚖️ `str | None = None` 🐍 3️⃣.1️⃣0️⃣) ✔️ 🔢 `None`.
-* `tax: float = 10.5` ✔️ 🔢 `10.5`.
-* `tags: List[str] = []` 🔢 🛁 📇: `[]`.
-
-✋️ 👆 💪 💚 🚫 👫 ⚪️➡️ 🏁 🚥 👫 🚫 🤙 🏪.
-
-🖼, 🚥 👆 ✔️ 🏷 ⏮️ 📚 📦 🔢 ☁ 💽, ✋️ 👆 🚫 💚 📨 📶 📏 🎻 📨 🌕 🔢 💲.
-
-### ⚙️ `response_model_exclude_unset` 🔢
-
-👆 💪 ⚒ *➡ 🛠️ 👨🎨* 🔢 `response_model_exclude_unset=True`:
-
-{* ../../docs_src/response_model/tutorial004.py hl[24] *}
-
-& 👈 🔢 💲 🏆 🚫 🔌 📨, 🕴 💲 🤙 ⚒.
-
-, 🚥 👆 📨 📨 👈 *➡ 🛠️* 🏬 ⏮️ 🆔 `foo`, 📨 (🚫 ✅ 🔢 💲) 🔜:
-
-```JSON
-{
- "name": "Foo",
- "price": 50.2
-}
-```
-
-/// info
-
-FastAPI ⚙️ Pydantic 🏷 `.dict()` ⏮️ 🚮 `exclude_unset` 🔢 🏆 👉.
-
-///
-
-/// info
-
-👆 💪 ⚙️:
-
-* `response_model_exclude_defaults=True`
-* `response_model_exclude_none=True`
-
-🔬 Pydantic 🩺 `exclude_defaults` & `exclude_none`.
-
-///
-
-#### 📊 ⏮️ 💲 🏑 ⏮️ 🔢
-
-✋️ 🚥 👆 📊 ✔️ 💲 🏷 🏑 ⏮️ 🔢 💲, 💖 🏬 ⏮️ 🆔 `bar`:
-
-```Python hl_lines="3 5"
-{
- "name": "Bar",
- "description": "The bartenders",
- "price": 62,
- "tax": 20.2
-}
-```
-
-👫 🔜 🔌 📨.
-
-#### 📊 ⏮️ 🎏 💲 🔢
-
-🚥 📊 ✔️ 🎏 💲 🔢 🕐, 💖 🏬 ⏮️ 🆔 `baz`:
-
-```Python hl_lines="3 5-6"
-{
- "name": "Baz",
- "description": None,
- "price": 50.2,
- "tax": 10.5,
- "tags": []
-}
-```
-
-FastAPI 🙃 🥃 (🤙, Pydantic 🙃 🥃) 🤔 👈, ✋️ `description`, `tax`, & `tags` ✔️ 🎏 💲 🔢, 👫 ⚒ 🎯 (↩️ ✊ ⚪️➡️ 🔢).
-
-, 👫 🔜 🔌 🎻 📨.
-
-/// tip
-
-👀 👈 🔢 💲 💪 🕳, 🚫 🕴 `None`.
-
-👫 💪 📇 (`[]`), `float` `10.5`, ♒️.
-
-///
-
-### `response_model_include` & `response_model_exclude`
-
-👆 💪 ⚙️ *➡ 🛠️ 👨🎨* 🔢 `response_model_include` & `response_model_exclude`.
-
-👫 ✊ `set` `str` ⏮️ 📛 🔢 🔌 (❎ 🎂) ⚖️ 🚫 (✅ 🎂).
-
-👉 💪 ⚙️ ⏩ ⌨ 🚥 👆 ✔️ 🕴 1️⃣ Pydantic 🏷 & 💚 ❎ 💽 ⚪️➡️ 🔢.
-
-/// tip
-
-✋️ ⚫️ 👍 ⚙️ 💭 🔛, ⚙️ 💗 🎓, ↩️ 👫 🔢.
-
-👉 ↩️ 🎻 🔗 🏗 👆 📱 🗄 (& 🩺) 🔜 1️⃣ 🏁 🏷, 🚥 👆 ⚙️ `response_model_include` ⚖️ `response_model_exclude` 🚫 🔢.
-
-👉 ✔ `response_model_by_alias` 👈 👷 ➡.
-
-///
-
-{* ../../docs_src/response_model/tutorial005.py hl[31,37] *}
-
-/// tip
-
-❕ `{"name", "description"}` ✍ `set` ⏮️ 📚 2️⃣ 💲.
-
-⚫️ 🌓 `set(["name", "description"])`.
-
-///
-
-#### ⚙️ `list`Ⓜ ↩️ `set`Ⓜ
-
-🚥 👆 💭 ⚙️ `set` & ⚙️ `list` ⚖️ `tuple` ↩️, FastAPI 🔜 🗜 ⚫️ `set` & ⚫️ 🔜 👷 ☑:
-
-{* ../../docs_src/response_model/tutorial006.py hl[31,37] *}
-
-## 🌃
-
-⚙️ *➡ 🛠️ 👨🎨* 🔢 `response_model` 🔬 📨 🏷 & ✴️ 🚚 📢 💽 ⛽ 👅.
-
-⚙️ `response_model_exclude_unset` 📨 🕴 💲 🎯 ⚒.
diff --git a/docs/em/docs/tutorial/response-status-code.md b/docs/em/docs/tutorial/response-status-code.md
deleted file mode 100644
index 413ceb9169..0000000000
--- a/docs/em/docs/tutorial/response-status-code.md
+++ /dev/null
@@ -1,101 +0,0 @@
-# 📨 👔 📟
-
-🎏 🌌 👆 💪 ✔ 📨 🏷, 👆 💪 📣 🇺🇸🔍 👔 📟 ⚙️ 📨 ⏮️ 🔢 `status_code` 🙆 *➡ 🛠️*:
-
-* `@app.get()`
-* `@app.post()`
-* `@app.put()`
-* `@app.delete()`
-* ♒️.
-
-{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
-
-/// note
-
-👀 👈 `status_code` 🔢 "👨🎨" 👩🔬 (`get`, `post`, ♒️). 🚫 👆 *➡ 🛠️ 🔢*, 💖 🌐 🔢 & 💪.
-
-///
-
-`status_code` 🔢 📨 🔢 ⏮️ 🇺🇸🔍 👔 📟.
-
-/// info
-
-`status_code` 💪 👐 📨 `IntEnum`, ✅ 🐍 `http.HTTPStatus`.
-
-///
-
-⚫️ 🔜:
-
-* 📨 👈 👔 📟 📨.
-* 📄 ⚫️ ✅ 🗄 🔗 ( & , 👩💻 🔢):
-
-
-
-/// note
-
-📨 📟 (👀 ⏭ 📄) 🎦 👈 📨 🔨 🚫 ✔️ 💪.
-
-FastAPI 💭 👉, & 🔜 🏭 🗄 🩺 👈 🇵🇸 📤 🙅♂ 📨 💪.
-
-///
-
-## 🔃 🇺🇸🔍 👔 📟
-
-/// note
-
-🚥 👆 ⏪ 💭 ⚫️❔ 🇺🇸🔍 👔 📟, 🚶 ⏭ 📄.
-
-///
-
-🇺🇸🔍, 👆 📨 🔢 👔 📟 3️⃣ 9️⃣ 🍕 📨.
-
-👫 👔 📟 ✔️ 📛 🔗 🤔 👫, ✋️ ⚠ 🍕 🔢.
-
-📏:
-
-* `100` & 🔛 "ℹ". 👆 🛎 ⚙️ 👫 🔗. 📨 ⏮️ 👫 👔 📟 🚫🔜 ✔️ 💪.
-* **`200`** & 🔛 "🏆" 📨. 👫 🕐 👆 🔜 ⚙️ 🏆.
- * `200` 🔢 👔 📟, ❔ ⛓ 🌐 "👌".
- * ➕1️⃣ 🖼 🔜 `201`, "✍". ⚫️ 🛎 ⚙️ ⏮️ 🏗 🆕 ⏺ 💽.
- * 🎁 💼 `204`, "🙅♂ 🎚". 👉 📨 ⚙️ 🕐❔ 📤 🙅♂ 🎚 📨 👩💻, & 📨 🔜 🚫 ✔️ 💪.
-* **`300`** & 🔛 "❎". 📨 ⏮️ 👫 👔 📟 5️⃣📆 ⚖️ 5️⃣📆 🚫 ✔️ 💪, 🌖 `304`, "🚫 🔀", ❔ 🔜 🚫 ✔️ 1️⃣.
-* **`400`** & 🔛 "👩💻 ❌" 📨. 👫 🥈 🆎 👆 🔜 🎲 ⚙️ 🏆.
- * 🖼 `404`, "🚫 🔎" 📨.
- * 💊 ❌ ⚪️➡️ 👩💻, 👆 💪 ⚙️ `400`.
-* `500` & 🔛 💽 ❌. 👆 🌖 🙅 ⚙️ 👫 🔗. 🕐❔ 🕳 🚶 ❌ 🍕 👆 🈸 📟, ⚖️ 💽, ⚫️ 🔜 🔁 📨 1️⃣ 👫 👔 📟.
-
-/// tip
-
-💭 🌅 🔃 🔠 👔 📟 & ❔ 📟 ⚫️❔, ✅ 🏇 🧾 🔃 🇺🇸🔍 👔 📟.
-
-///
-
-## ⌨ 💭 📛
-
-➡️ 👀 ⏮️ 🖼 🔄:
-
-{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
-
-`201` 👔 📟 "✍".
-
-✋️ 👆 🚫 ✔️ ✍ ⚫️❔ 🔠 👉 📟 ⛓.
-
-👆 💪 ⚙️ 🏪 🔢 ⚪️➡️ `fastapi.status`.
-
-{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
-
-👫 🏪, 👫 🧑🤝🧑 🎏 🔢, ✋️ 👈 🌌 👆 💪 ⚙️ 👨🎨 📋 🔎 👫:
-
-
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette import status`.
-
-**FastAPI** 🚚 🎏 `starlette.status` `fastapi.status` 🏪 👆, 👩💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃.
-
-///
-
-## 🔀 🔢
-
-⏪, [🏧 👩💻 🦮](../advanced/response-change-status-code.md){.internal-link target=_blank}, 👆 🔜 👀 ❔ 📨 🎏 👔 📟 🌘 🔢 👆 📣 📥.
diff --git a/docs/em/docs/tutorial/schema-extra-example.md b/docs/em/docs/tutorial/schema-extra-example.md
deleted file mode 100644
index 1bd314c513..0000000000
--- a/docs/em/docs/tutorial/schema-extra-example.md
+++ /dev/null
@@ -1,110 +0,0 @@
-# 📣 📨 🖼 💽
-
-👆 💪 📣 🖼 💽 👆 📱 💪 📨.
-
-📥 📚 🌌 ⚫️.
-
-## Pydantic `schema_extra`
-
-👆 💪 📣 `example` Pydantic 🏷 ⚙️ `Config` & `schema_extra`, 🔬 Pydantic 🩺: 🔗 🛃:
-
-{* ../../docs_src/schema_extra_example/tutorial001.py hl[15:23] *}
-
-👈 ➕ ℹ 🔜 🚮-🔢 **🎻 🔗** 👈 🏷, & ⚫️ 🔜 ⚙️ 🛠️ 🩺.
-
-/// tip
-
-👆 💪 ⚙️ 🎏 ⚒ ↔ 🎻 🔗 & 🚮 👆 👍 🛃 ➕ ℹ.
-
-🖼 👆 💪 ⚙️ ⚫️ 🚮 🗃 🕸 👩💻 🔢, ♒️.
-
-///
-
-## `Field` 🌖 ❌
-
-🕐❔ ⚙️ `Field()` ⏮️ Pydantic 🏷, 👆 💪 📣 ➕ ℹ **🎻 🔗** 🚶♀️ 🙆 🎏 ❌ ❌ 🔢.
-
-👆 💪 ⚙️ 👉 🚮 `example` 🔠 🏑:
-
-{* ../../docs_src/schema_extra_example/tutorial002.py hl[4,10:13] *}
-
-/// warning
-
-🚧 🤯 👈 📚 ➕ ❌ 🚶♀️ 🏆 🚫 🚮 🙆 🔬, 🕴 ➕ ℹ, 🧾 🎯.
-
-///
-
-## `example` & `examples` 🗄
-
-🕐❔ ⚙️ 🙆:
-
-* `Path()`
-* `Query()`
-* `Header()`
-* `Cookie()`
-* `Body()`
-* `Form()`
-* `File()`
-
-👆 💪 📣 💽 `example` ⚖️ 👪 `examples` ⏮️ 🌖 ℹ 👈 🔜 🚮 **🗄**.
-
-### `Body` ⏮️ `example`
-
-📥 👥 🚶♀️ `example` 📊 ⌛ `Body()`:
-
-{* ../../docs_src/schema_extra_example/tutorial003.py hl[20:25] *}
-
-### 🖼 🩺 🎚
-
-⏮️ 🙆 👩🔬 🔛 ⚫️ 🔜 👀 💖 👉 `/docs`:
-
-
-
-### `Body` ⏮️ 💗 `examples`
-
-👐 👁 `example`, 👆 💪 🚶♀️ `examples` ⚙️ `dict` ⏮️ **💗 🖼**, 🔠 ⏮️ ➕ ℹ 👈 🔜 🚮 **🗄** 💁♂️.
-
-🔑 `dict` 🔬 🔠 🖼, & 🔠 💲 ➕1️⃣ `dict`.
-
-🔠 🎯 🖼 `dict` `examples` 💪 🔌:
-
-* `summary`: 📏 📛 🖼.
-* `description`: 📏 📛 👈 💪 🔌 ✍ ✍.
-* `value`: 👉 ☑ 🖼 🎦, ✅ `dict`.
-* `externalValue`: 🎛 `value`, 📛 ☝ 🖼. 👐 👉 5️⃣📆 🚫 🐕🦺 📚 🧰 `value`.
-
-{* ../../docs_src/schema_extra_example/tutorial004.py hl[21:47] *}
-
-### 🖼 🩺 🎚
-
-⏮️ `examples` 🚮 `Body()` `/docs` 🔜 👀 💖:
-
-
-
-## 📡 ℹ
-
-/// warning
-
-👉 📶 📡 ℹ 🔃 🐩 **🎻 🔗** & **🗄**.
-
-🚥 💭 🔛 ⏪ 👷 👆, 👈 💪 🥃, & 👆 🎲 🚫 💪 👉 ℹ, 💭 🆓 🚶 👫.
-
-///
-
-🕐❔ 👆 🚮 🖼 🔘 Pydantic 🏷, ⚙️ `schema_extra` ⚖️ `Field(example="something")` 👈 🖼 🚮 **🎻 🔗** 👈 Pydantic 🏷.
-
-& 👈 **🎻 🔗** Pydantic 🏷 🔌 **🗄** 👆 🛠️, & ⤴️ ⚫️ ⚙️ 🩺 🎚.
-
-**🎻 🔗** 🚫 🤙 ✔️ 🏑 `example` 🐩. ⏮️ ⏬ 🎻 🔗 🔬 🏑 `examples`, ✋️ 🗄 3️⃣.0️⃣.3️⃣ ⚓️ 🔛 🗝 ⏬ 🎻 🔗 👈 🚫 ✔️ `examples`.
-
-, 🗄 3️⃣.0️⃣.3️⃣ 🔬 🚮 👍 `example` 🔀 ⏬ **🎻 🔗** ⚫️ ⚙️, 🎏 🎯 (✋️ ⚫️ 👁 `example`, 🚫 `examples`), & 👈 ⚫️❔ ⚙️ 🛠️ 🩺 🎚 (⚙️ 🦁 🎚).
-
-, 👐 `example` 🚫 🍕 🎻 🔗, ⚫️ 🍕 🗄 🛃 ⏬ 🎻 🔗, & 👈 ⚫️❔ 🔜 ⚙️ 🩺 🎚.
-
-✋️ 🕐❔ 👆 ⚙️ `example` ⚖️ `examples` ⏮️ 🙆 🎏 🚙 (`Query()`, `Body()`, ♒️.) 📚 🖼 🚫 🚮 🎻 🔗 👈 🔬 👈 💽 (🚫 🗄 👍 ⏬ 🎻 🔗), 👫 🚮 🔗 *➡ 🛠️* 📄 🗄 (🏞 🍕 🗄 👈 ⚙️ 🎻 🔗).
-
-`Path()`, `Query()`, `Header()`, & `Cookie()`, `example` ⚖️ `examples` 🚮 🗄 🔑, `Parameter Object` (🔧).
-
-& `Body()`, `File()`, & `Form()`, `example` ⚖️ `examples` 📊 🚮 🗄 🔑, `Request Body Object`, 🏑 `content`, 🔛 `Media Type Object` (🔧).
-
-🔛 🎏 ✋, 📤 🆕 ⏬ 🗄: **3️⃣.1️⃣.0️⃣**, ⏳ 🚀. ⚫️ ⚓️ 🔛 ⏪ 🎻 🔗 & 🏆 🛠️ ⚪️➡️ 🗄 🛃 ⏬ 🎻 🔗 ❎, 💱 ⚒ ⚪️➡️ ⏮️ ⏬ 🎻 🔗, 🌐 👫 🤪 🔺 📉. 👐, 🦁 🎚 ⏳ 🚫 🐕🦺 🗄 3️⃣.1️⃣.0️⃣,, 🔜, ⚫️ 👍 😣 ⚙️ 💭 🔛.
diff --git a/docs/em/docs/tutorial/security/first-steps.md b/docs/em/docs/tutorial/security/first-steps.md
deleted file mode 100644
index 8fb459a651..0000000000
--- a/docs/em/docs/tutorial/security/first-steps.md
+++ /dev/null
@@ -1,197 +0,0 @@
-# 💂♂ - 🥇 🔁
-
-➡️ 🌈 👈 👆 ✔️ 👆 **👩💻** 🛠️ 🆔.
-
-& 👆 ✔️ **🕸** ➕1️⃣ 🆔 ⚖️ 🎏 ➡ 🎏 🆔 (⚖️ 📱 🈸).
-
-& 👆 💚 ✔️ 🌌 🕸 🔓 ⏮️ 👩💻, ⚙️ **🆔** & **🔐**.
-
-👥 💪 ⚙️ **Oauth2️⃣** 🏗 👈 ⏮️ **FastAPI**.
-
-✋️ ➡️ 🖊 👆 🕰 👂 🌕 📏 🔧 🔎 👈 🐥 🍖 ℹ 👆 💪.
-
-➡️ ⚙️ 🧰 🚚 **FastAPI** 🍵 💂♂.
-
-## ❔ ⚫️ 👀
-
-➡️ 🥇 ⚙️ 📟 & 👀 ❔ ⚫️ 👷, & ⤴️ 👥 🔜 👟 🔙 🤔 ⚫️❔ 😥.
-
-## ✍ `main.py`
-
-📁 🖼 📁 `main.py`:
-
-{* ../../docs_src/security/tutorial001.py *}
-
-## 🏃 ⚫️
-
-/// info
-
-🥇 ❎ `python-multipart`.
-
-🤶 Ⓜ. `pip install python-multipart`.
-
-👉 ↩️ **Oauth2️⃣** ⚙️ "📨 📊" 📨 `username` & `password`.
-
-///
-
-🏃 🖼 ⏮️:
-
-
-
-/// check | ✔ 🔼 ❗
-
-👆 ⏪ ✔️ ✨ 🆕 "✔" 🔼.
-
- & 👆 *➡ 🛠️* ✔️ 🐥 🔒 🔝-▶️️ ↩ 👈 👆 💪 🖊.
-
-///
-
-& 🚥 👆 🖊 ⚫️, 👆 ✔️ 🐥 ✔ 📨 🆎 `username` & `password` (& 🎏 📦 🏑):
-
-
-
-/// note
-
-⚫️ 🚫 🤔 ⚫️❔ 👆 🆎 📨, ⚫️ 🏆 🚫 👷. ✋️ 👥 🔜 🤚 📤.
-
-///
-
-👉 ↗️ 🚫 🕸 🏁 👩💻, ✋️ ⚫️ 👑 🏧 🧰 📄 🖥 🌐 👆 🛠️.
-
-⚫️ 💪 ⚙️ 🕸 🏉 (👈 💪 👆).
-
-⚫️ 💪 ⚙️ 🥉 🥳 🈸 & ⚙️.
-
-& ⚫️ 💪 ⚙️ 👆, ℹ, ✅ & 💯 🎏 🈸.
-
-## `password` 💧
-
-🔜 ➡️ 🚶 🔙 👄 & 🤔 ⚫️❔ 🌐 👈.
-
-`password` "💧" 1️⃣ 🌌 ("💧") 🔬 Oauth2️⃣, 🍵 💂♂ & 🤝.
-
-Oauth2️⃣ 🔧 👈 👩💻 ⚖️ 🛠️ 💪 🔬 💽 👈 🔓 👩💻.
-
-✋️ 👉 💼, 🎏 **FastAPI** 🈸 🔜 🍵 🛠️ & 🤝.
-
-, ➡️ 📄 ⚫️ ⚪️➡️ 👈 📉 ☝ 🎑:
-
-* 👩💻 🆎 `username` & `password` 🕸, & 🎯 `Enter`.
-* 🕸 (🏃♂ 👩💻 🖥) 📨 👈 `username` & `password` 🎯 📛 👆 🛠️ (📣 ⏮️ `tokenUrl="token"`).
-* 🛠️ ✅ 👈 `username` & `password`, & 📨 ⏮️ "🤝" (👥 🚫 🛠️ 🙆 👉).
- * "🤝" 🎻 ⏮️ 🎚 👈 👥 💪 ⚙️ ⏪ ✔ 👉 👩💻.
- * 🛎, 🤝 ⚒ 🕛 ⏮️ 🕰.
- * , 👩💻 🔜 ✔️ 🕹 🔄 ☝ ⏪.
- * & 🚥 🤝 📎, ⚠ 🌘. ⚫️ 🚫 💖 🧲 🔑 👈 🔜 👷 ♾ (🏆 💼).
-* 🕸 🏪 👈 🤝 🍕 👱.
-* 👩💻 🖊 🕸 🚶 ➕1️⃣ 📄 🕸 🕸 📱.
-* 🕸 💪 ☕ 🌅 💽 ⚪️➡️ 🛠️.
- * ✋️ ⚫️ 💪 🤝 👈 🎯 🔗.
- * , 🔓 ⏮️ 👆 🛠️, ⚫️ 📨 🎚 `Authorization` ⏮️ 💲 `Bearer ` ➕ 🤝.
- * 🚥 🤝 🔌 `foobar`, 🎚 `Authorization` 🎚 🔜: `Bearer foobar`.
-
-## **FastAPI**'Ⓜ `OAuth2PasswordBearer`
-
-**FastAPI** 🚚 📚 🧰, 🎏 🎚 ⚛, 🛠️ 👫 💂♂ ⚒.
-
-👉 🖼 👥 🔜 ⚙️ **Oauth2️⃣**, ⏮️ **🔐** 💧, ⚙️ **📨** 🤝. 👥 👈 ⚙️ `OAuth2PasswordBearer` 🎓.
-
-/// info
-
-"📨" 🤝 🚫 🕴 🎛.
-
-✋️ ⚫️ 🏆 1️⃣ 👆 ⚙️ 💼.
-
- & ⚫️ 💪 🏆 🏆 ⚙️ 💼, 🚥 👆 Oauth2️⃣ 🕴 & 💭 ⚫️❔ ⚫️❔ 📤 ➕1️⃣ 🎛 👈 ♣ 👻 👆 💪.
-
-👈 💼, **FastAPI** 🚚 👆 ⏮️ 🧰 🏗 ⚫️.
-
-///
-
-🕐❔ 👥 ✍ 👐 `OAuth2PasswordBearer` 🎓 👥 🚶♀️ `tokenUrl` 🔢. 👉 🔢 🔌 📛 👈 👩💻 (🕸 🏃 👩💻 🖥) 🔜 ⚙️ 📨 `username` & `password` ✔ 🤚 🤝.
-
-{* ../../docs_src/security/tutorial001.py hl[6] *}
-
-/// tip
-
-📥 `tokenUrl="token"` 🔗 ⚖ 📛 `token` 👈 👥 🚫 ✍. ⚫️ ⚖ 📛, ⚫️ 🌓 `./token`.
-
-↩️ 👥 ⚙️ ⚖ 📛, 🚥 👆 🛠️ 🔎 `https://example.com/`, ⤴️ ⚫️ 🔜 🔗 `https://example.com/token`. ✋️ 🚥 👆 🛠️ 🔎 `https://example.com/api/v1/`, ⤴️ ⚫️ 🔜 🔗 `https://example.com/api/v1/token`.
-
-⚙️ ⚖ 📛 ⚠ ⚒ 💭 👆 🈸 🚧 👷 🏧 ⚙️ 💼 💖 [⛅ 🗳](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
-
-///
-
-👉 🔢 🚫 ✍ 👈 🔗 / *➡ 🛠️*, ✋️ 📣 👈 📛 `/token` 🔜 1️⃣ 👈 👩💻 🔜 ⚙️ 🤚 🤝. 👈 ℹ ⚙️ 🗄, & ⤴️ 🎓 🛠️ 🧾 ⚙️.
-
-👥 🔜 🔜 ✍ ☑ ➡ 🛠️.
-
-/// info
-
-🚥 👆 📶 ⚠ "✍" 👆 💪 👎 👗 🔢 📛 `tokenUrl` ↩️ `token_url`.
-
-👈 ↩️ ⚫️ ⚙️ 🎏 📛 🗄 🔌. 👈 🚥 👆 💪 🔬 🌅 🔃 🙆 👫 💂♂ ⚖ 👆 💪 📁 & 📋 ⚫️ 🔎 🌖 ℹ 🔃 ⚫️.
-
-///
-
-`oauth2_scheme` 🔢 👐 `OAuth2PasswordBearer`, ✋️ ⚫️ "🇧🇲".
-
-⚫️ 💪 🤙:
-
-```Python
-oauth2_scheme(some, parameters)
-```
-
-, ⚫️ 💪 ⚙️ ⏮️ `Depends`.
-
-### ⚙️ ⚫️
-
-🔜 👆 💪 🚶♀️ 👈 `oauth2_scheme` 🔗 ⏮️ `Depends`.
-
-{* ../../docs_src/security/tutorial001.py hl[10] *}
-
-👉 🔗 🔜 🚚 `str` 👈 🛠️ 🔢 `token` *➡ 🛠️ 🔢*.
-
-**FastAPI** 🔜 💭 👈 ⚫️ 💪 ⚙️ 👉 🔗 🔬 "💂♂ ⚖" 🗄 🔗 (& 🏧 🛠️ 🩺).
-
-/// info | 📡 ℹ
-
-**FastAPI** 🔜 💭 👈 ⚫️ 💪 ⚙️ 🎓 `OAuth2PasswordBearer` (📣 🔗) 🔬 💂♂ ⚖ 🗄 ↩️ ⚫️ 😖 ⚪️➡️ `fastapi.security.oauth2.OAuth2`, ❔ 🔄 😖 ⚪️➡️ `fastapi.security.base.SecurityBase`.
-
-🌐 💂♂ 🚙 👈 🛠️ ⏮️ 🗄 (& 🏧 🛠️ 🩺) 😖 ⚪️➡️ `SecurityBase`, 👈 ❔ **FastAPI** 💪 💭 ❔ 🛠️ 👫 🗄.
-
-///
-
-## ⚫️❔ ⚫️ 🔨
-
-⚫️ 🔜 🚶 & 👀 📨 👈 `Authorization` 🎚, ✅ 🚥 💲 `Bearer ` ➕ 🤝, & 🔜 📨 🤝 `str`.
-
-🚥 ⚫️ 🚫 👀 `Authorization` 🎚, ⚖️ 💲 🚫 ✔️ `Bearer ` 🤝, ⚫️ 🔜 📨 ⏮️ 4️⃣0️⃣1️⃣ 👔 📟 ❌ (`UNAUTHORIZED`) 🔗.
-
-👆 🚫 ✔️ ✅ 🚥 🤝 🔀 📨 ❌. 👆 💪 💭 👈 🚥 👆 🔢 🛠️, ⚫️ 🔜 ✔️ `str` 👈 🤝.
-
-👆 💪 🔄 ⚫️ ⏪ 🎓 🩺:
-
-
-
-👥 🚫 ✔ 🔬 🤝, ✋️ 👈 ▶️ ⏪.
-
-## 🌃
-
-, 3️⃣ ⚖️ 4️⃣ ➕ ⏸, 👆 ⏪ ✔️ 🐒 📨 💂♂.
diff --git a/docs/em/docs/tutorial/security/get-current-user.md b/docs/em/docs/tutorial/security/get-current-user.md
deleted file mode 100644
index 2f4a26f352..0000000000
--- a/docs/em/docs/tutorial/security/get-current-user.md
+++ /dev/null
@@ -1,105 +0,0 @@
-# 🤚 ⏮️ 👩💻
-
-⏮️ 📃 💂♂ ⚙️ (❔ 🧢 🔛 🔗 💉 ⚙️) 🤝 *➡ 🛠️ 🔢* `token` `str`:
-
-{* ../../docs_src/security/tutorial001.py hl[10] *}
-
-✋️ 👈 🚫 👈 ⚠.
-
-➡️ ⚒ ⚫️ 🤝 👥 ⏮️ 👩💻.
-
-## ✍ 👩💻 🏷
-
-🥇, ➡️ ✍ Pydantic 👩💻 🏷.
-
-🎏 🌌 👥 ⚙️ Pydantic 📣 💪, 👥 💪 ⚙️ ⚫️ 🙆 🙆:
-
-{* ../../docs_src/security/tutorial002.py hl[5,12:16] *}
-
-## ✍ `get_current_user` 🔗
-
-➡️ ✍ 🔗 `get_current_user`.
-
-💭 👈 🔗 💪 ✔️ 🎧-🔗 ❓
-
-`get_current_user` 🔜 ✔️ 🔗 ⏮️ 🎏 `oauth2_scheme` 👥 ✍ ⏭.
-
-🎏 👥 🔨 ⏭ *➡ 🛠️* 🔗, 👆 🆕 🔗 `get_current_user` 🔜 📨 `token` `str` ⚪️➡️ 🎧-🔗 `oauth2_scheme`:
-
-{* ../../docs_src/security/tutorial002.py hl[25] *}
-
-## 🤚 👩💻
-
-`get_current_user` 🔜 ⚙️ (❌) 🚙 🔢 👥 ✍, 👈 ✊ 🤝 `str` & 📨 👆 Pydantic `User` 🏷:
-
-{* ../../docs_src/security/tutorial002.py hl[19:22,26:27] *}
-
-## 💉 ⏮️ 👩💻
-
-🔜 👥 💪 ⚙️ 🎏 `Depends` ⏮️ 👆 `get_current_user` *➡ 🛠️*:
-
-{* ../../docs_src/security/tutorial002.py hl[31] *}
-
-👀 👈 👥 📣 🆎 `current_user` Pydantic 🏷 `User`.
-
-👉 🔜 ℹ 🇺🇲 🔘 🔢 ⏮️ 🌐 🛠️ & 🆎 ✅.
-
-/// tip
-
-👆 5️⃣📆 💭 👈 📨 💪 📣 ⏮️ Pydantic 🏷.
-
-📥 **FastAPI** 🏆 🚫 🤚 😨 ↩️ 👆 ⚙️ `Depends`.
-
-///
-
-/// check
-
-🌌 👉 🔗 ⚙️ 🏗 ✔ 👥 ✔️ 🎏 🔗 (🎏 "☑") 👈 🌐 📨 `User` 🏷.
-
-👥 🚫 🚫 ✔️ 🕴 1️⃣ 🔗 👈 💪 📨 👈 🆎 💽.
-
-///
-
-## 🎏 🏷
-
-👆 💪 🔜 🤚 ⏮️ 👩💻 🔗 *➡ 🛠️ 🔢* & 🙅 ⏮️ 💂♂ 🛠️ **🔗 💉** 🎚, ⚙️ `Depends`.
-
-& 👆 💪 ⚙️ 🙆 🏷 ⚖️ 💽 💂♂ 📄 (👉 💼, Pydantic 🏷 `User`).
-
-✋️ 👆 🚫 🚫 ⚙️ 🎯 💽 🏷, 🎓 ⚖️ 🆎.
-
-👆 💚 ✔️ `id` & `email` & 🚫 ✔️ 🙆 `username` 👆 🏷 ❓ 💭. 👆 💪 ⚙️ 👉 🎏 🧰.
-
-👆 💚 ✔️ `str`❓ ⚖️ `dict`❓ ⚖️ 💽 🎓 🏷 👐 🔗 ❓ ⚫️ 🌐 👷 🎏 🌌.
-
-👆 🤙 🚫 ✔️ 👩💻 👈 🕹 👆 🈸 ✋️ 🤖, 🤖, ⚖️ 🎏 ⚙️, 👈 ✔️ 🔐 🤝 ❓ 🔄, ⚫️ 🌐 👷 🎏.
-
-⚙️ 🙆 😇 🏷, 🙆 😇 🎓, 🙆 😇 💽 👈 👆 💪 👆 🈸. **FastAPI** ✔️ 👆 📔 ⏮️ 🔗 💉 ⚙️.
-
-## 📟 📐
-
-👉 🖼 5️⃣📆 😑 🔁. ✔️ 🤯 👈 👥 🌀 💂♂, 📊 🏷, 🚙 🔢 & *➡ 🛠️* 🎏 📁.
-
-✋️ 📥 🔑 ☝.
-
-💂♂ & 🔗 💉 💩 ✍ 🕐.
-
-& 👆 💪 ⚒ ⚫️ 🏗 👆 💚. & , ✔️ ⚫️ ✍ 🕴 🕐, 👁 🥉. ⏮️ 🌐 💪.
-
-✋️ 👆 💪 ✔️ 💯 🔗 (*➡ 🛠️*) ⚙️ 🎏 💂♂ ⚙️.
-
-& 🌐 👫 (⚖️ 🙆 ↔ 👫 👈 👆 💚) 💪 ✊ 📈 🏤-⚙️ 👫 🔗 ⚖️ 🙆 🎏 🔗 👆 ✍.
-
-& 🌐 👉 💯 *➡ 🛠️* 💪 🤪 3️⃣ ⏸:
-
-{* ../../docs_src/security/tutorial002.py hl[30:32] *}
-
-## 🌃
-
-👆 💪 🔜 🤚 ⏮️ 👩💻 🔗 👆 *➡ 🛠️ 🔢*.
-
-👥 ⏪ 😬 📤.
-
-👥 💪 🚮 *➡ 🛠️* 👩💻/👩💻 🤙 📨 `username` & `password`.
-
-👈 👟 ⏭.
diff --git a/docs/em/docs/tutorial/security/index.md b/docs/em/docs/tutorial/security/index.md
deleted file mode 100644
index 1a47e55102..0000000000
--- a/docs/em/docs/tutorial/security/index.md
+++ /dev/null
@@ -1,106 +0,0 @@
-# 💂♂
-
-📤 📚 🌌 🍵 💂♂, 🤝 & ✔.
-
-& ⚫️ 🛎 🏗 & "⚠" ❔.
-
-📚 🛠️ & ⚙️ 🍵 💂♂ & 🤝 ✊ 🦏 💸 🎯 & 📟 (📚 💼 ⚫️ 💪 5️⃣0️⃣ 💯 ⚖️ 🌅 🌐 📟 ✍).
-
-**FastAPI** 🚚 📚 🧰 ℹ 👆 🙅 ⏮️ **💂♂** 💪, 📉, 🐩 🌌, 🍵 ✔️ 🔬 & 💡 🌐 💂♂ 🔧.
-
-✋️ 🥇, ➡️ ✅ 🤪 🔧.
-
-## 🏃 ❓
-
-🚥 👆 🚫 💅 🔃 🙆 👉 ⚖ & 👆 💪 🚮 💂♂ ⏮️ 🤝 ⚓️ 🔛 🆔 & 🔐 *▶️️ 🔜*, 🚶 ⏭ 📃.
-
-## Oauth2️⃣
-
-Oauth2️⃣ 🔧 👈 🔬 📚 🌌 🍵 🤝 & ✔.
-
-⚫️ 🔬 🔧 & 📔 📚 🏗 ⚙️ 💼.
-
-⚫️ 🔌 🌌 🔓 ⚙️ "🥉 🥳".
-
-👈 ⚫️❔ 🌐 ⚙️ ⏮️ "💳 ⏮️ 👱📔, 🇺🇸🔍, 👱📔, 📂" ⚙️ 🔘.
-
-### ✳ 1️⃣
-
-📤 ✳ 1️⃣, ❔ 📶 🎏 ⚪️➡️ Oauth2️⃣, & 🌖 🏗, ⚫️ 🔌 🔗 🔧 🔛 ❔ 🗜 📻.
-
-⚫️ 🚫 📶 🌟 ⚖️ ⚙️ 🛎.
-
-Oauth2️⃣ 🚫 ✔ ❔ 🗜 📻, ⚫️ ⌛ 👆 ✔️ 👆 🈸 🍦 ⏮️ 🇺🇸🔍.
-
-/// tip
-
-📄 🔃 **🛠️** 👆 🔜 👀 ❔ ⚒ 🆙 🇺🇸🔍 🆓, ⚙️ Traefik & ➡️ 🗜.
-
-///
-
-## 👩💻 🔗
-
-👩💻 🔗 ➕1️⃣ 🔧, 🧢 🔛 **Oauth2️⃣**.
-
-⚫️ ↔ Oauth2️⃣ ✔ 👜 👈 📶 🌌 Oauth2️⃣, 🔄 ⚒ ⚫️ 🌅 🛠️.
-
-🖼, 🇺🇸🔍 💳 ⚙️ 👩💻 🔗 (❔ 🔘 ⚙️ Oauth2️⃣).
-
-✋️ 👱📔 💳 🚫 🐕🦺 👩💻 🔗. ⚫️ ✔️ 🚮 👍 🍛 Oauth2️⃣.
-
-### 👩💻 (🚫 "👩💻 🔗")
-
-📤 "👩💻" 🔧. 👈 🔄 ❎ 🎏 👜 **👩💻 🔗**, ✋️ 🚫 ⚓️ 🔛 Oauth2️⃣.
-
-, ⚫️ 🏁 🌖 ⚙️.
-
-⚫️ 🚫 📶 🌟 ⚖️ ⚙️ 🛎.
-
-## 🗄
-
-🗄 (⏪ 💭 🦁) 📂 🔧 🏗 🔗 (🔜 🍕 💾 🏛).
-
-**FastAPI** ⚓️ 🔛 **🗄**.
-
-👈 ⚫️❔ ⚒ ⚫️ 💪 ✔️ 💗 🏧 🎓 🧾 🔢, 📟 ⚡, ♒️.
-
-🗄 ✔️ 🌌 🔬 💗 💂♂ "⚖".
-
-⚙️ 👫, 👆 💪 ✊ 📈 🌐 👫 🐩-⚓️ 🧰, 🔌 👉 🎓 🧾 ⚙️.
-
-🗄 🔬 📄 💂♂ ⚖:
-
-* `apiKey`: 🈸 🎯 🔑 👈 💪 👟 ⚪️➡️:
- * 🔢 🔢.
- * 🎚.
- * 🍪.
-* `http`: 🐩 🇺🇸🔍 🤝 ⚙️, 🔌:
- * `bearer`: 🎚 `Authorization` ⏮️ 💲 `Bearer ` ➕ 🤝. 👉 😖 ⚪️➡️ Oauth2️⃣.
- * 🇺🇸🔍 🔰 🤝.
- * 🇺🇸🔍 📰, ♒️.
-* `oauth2`: 🌐 Oauth2️⃣ 🌌 🍵 💂♂ (🤙 "💧").
- * 📚 👫 💧 ☑ 🏗 ✳ 2️⃣.0️⃣ 🤝 🐕🦺 (💖 🇺🇸🔍, 👱📔, 👱📔, 📂, ♒️):
- * `implicit`
- * `clientCredentials`
- * `authorizationCode`
- * ✋️ 📤 1️⃣ 🎯 "💧" 👈 💪 👌 ⚙️ 🚚 🤝 🎏 🈸 🔗:
- * `password`: ⏭ 📃 🔜 📔 🖼 👉.
-* `openIdConnect`: ✔️ 🌌 🔬 ❔ 🔎 Oauth2️⃣ 🤝 📊 🔁.
- * 👉 🏧 🔍 ⚫️❔ 🔬 👩💻 🔗 🔧.
-
-
-/// tip
-
-🛠️ 🎏 🤝/✔ 🐕🦺 💖 🇺🇸🔍, 👱📔, 👱📔, 📂, ♒️. 💪 & 📶 ⏩.
-
-🌅 🏗 ⚠ 🏗 🤝/✔ 🐕🦺 💖 👈, ✋️ **FastAPI** 🤝 👆 🧰 ⚫️ 💪, ⏪ 🔨 🏋️ 🏋♂ 👆.
-
-///
-
-## **FastAPI** 🚙
-
-FastAPI 🚚 📚 🧰 🔠 👉 💂♂ ⚖ `fastapi.security` 🕹 👈 📉 ⚙️ 👉 💂♂ 🛠️.
-
-⏭ 📃 👆 🔜 👀 ❔ 🚮 💂♂ 👆 🛠️ ⚙️ 📚 🧰 🚚 **FastAPI**.
-
-& 👆 🔜 👀 ❔ ⚫️ 🤚 🔁 🛠️ 🔘 🎓 🧾 ⚙️.
diff --git a/docs/em/docs/tutorial/security/oauth2-jwt.md b/docs/em/docs/tutorial/security/oauth2-jwt.md
deleted file mode 100644
index ee7bc2d28a..0000000000
--- a/docs/em/docs/tutorial/security/oauth2-jwt.md
+++ /dev/null
@@ -1,275 +0,0 @@
-# Oauth2️⃣ ⏮️ 🔐 (& 🔁), 📨 ⏮️ 🥙 🤝
-
-🔜 👈 👥 ✔️ 🌐 💂♂ 💧, ➡️ ⚒ 🈸 🤙 🔐, ⚙️ 🥙 🤝 & 🔐 🔐 🔁.
-
-👉 📟 🕳 👆 💪 🤙 ⚙️ 👆 🈸, 🖊 🔐 #️⃣ 👆 💽, ♒️.
-
-👥 🔜 ▶️ ⚪️➡️ 🌐❔ 👥 ◀️ ⏮️ 📃 & 📈 ⚫️.
-
-## 🔃 🥙
-
-🥙 ⛓ "🎻 🕸 🤝".
-
-⚫️ 🐩 🚫 🎻 🎚 📏 💧 🎻 🍵 🚀. ⚫️ 👀 💖 👉:
-
-```
-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
-```
-
-⚫️ 🚫 🗜,, 🙆 💪 🛡 ℹ ⚪️➡️ 🎚.
-
-✋️ ⚫️ 🛑. , 🕐❔ 👆 📨 🤝 👈 👆 ♨, 👆 💪 ✔ 👈 👆 🤙 ♨ ⚫️.
-
-👈 🌌, 👆 💪 ✍ 🤝 ⏮️ 👔, ➡️ 💬, 1️⃣ 🗓️. & ⤴️ 🕐❔ 👩💻 👟 🔙 ⏭ 📆 ⏮️ 🤝, 👆 💭 👈 👩💻 🕹 👆 ⚙️.
-
-⏮️ 🗓️, 🤝 🔜 🕛 & 👩💻 🔜 🚫 ✔ & 🔜 ✔️ 🛑 🔄 🤚 🆕 🤝. & 🚥 👩💻 (⚖️ 🥉 🥳) 🔄 🔀 🤝 🔀 👔, 👆 🔜 💪 🔎 ⚫️, ↩️ 💳 🔜 🚫 🏏.
-
-🚥 👆 💚 🤾 ⏮️ 🥙 🤝 & 👀 ❔ 👫 👷, ✅ https://jwt.io.
-
-## ❎ `python-jose`
-
-👥 💪 ❎ `python-jose` 🏗 & ✔ 🥙 🤝 🐍:
-
-
-
-✔ 🈸 🎏 🌌 ⏭.
-
-⚙️ 🎓:
-
-🆔: `johndoe`
-🔐: `secret`
-
-/// check
-
-👀 👈 🕳 📟 🔢 🔐 "`secret`", 👥 🕴 ✔️ #️⃣ ⏬.
-
-///
-
-
-
-🤙 🔗 `/users/me/`, 👆 🔜 🤚 📨:
-
-```JSON
-{
- "username": "johndoe",
- "email": "johndoe@example.com",
- "full_name": "John Doe",
- "disabled": false
-}
-```
-
-
-
-🚥 👆 📂 👩💻 🧰, 👆 💪 👀 ❔ 📊 📨 🕴 🔌 🤝, 🔐 🕴 📨 🥇 📨 🔓 👩💻 & 🤚 👈 🔐 🤝, ✋️ 🚫 ⏮️:
-
-
-
-/// note
-
-👀 🎚 `Authorization`, ⏮️ 💲 👈 ▶️ ⏮️ `Bearer `.
-
-///
-
-## 🏧 ⚙️ ⏮️ `scopes`
-
-Oauth2️⃣ ✔️ 🔑 "↔".
-
-👆 💪 ⚙️ 👫 🚮 🎯 ⚒ ✔ 🥙 🤝.
-
-⤴️ 👆 💪 🤝 👉 🤝 👩💻 🔗 ⚖️ 🥉 🥳, 🔗 ⏮️ 👆 🛠️ ⏮️ ⚒ 🚫.
-
-👆 💪 💡 ❔ ⚙️ 👫 & ❔ 👫 🛠️ 🔘 **FastAPI** ⏪ **🏧 👩💻 🦮**.
-
-## 🌃
-
-⏮️ ⚫️❔ 👆 ✔️ 👀 🆙 🔜, 👆 💪 ⚒ 🆙 🔐 **FastAPI** 🈸 ⚙️ 🐩 💖 Oauth2️⃣ & 🥙.
-
-🌖 🙆 🛠️ 🚚 💂♂ ▶️️ 👍 🏗 📄 🔜.
-
-📚 📦 👈 📉 ⚫️ 📚 ✔️ ⚒ 📚 ⚠ ⏮️ 💽 🏷, 💽, & 💪 ⚒. & 👉 📦 👈 📉 👜 💁♂️ 🌅 🤙 ✔️ 💂♂ ⚠ 🔘.
-
----
-
-**FastAPI** 🚫 ⚒ 🙆 ⚠ ⏮️ 🙆 💽, 💽 🏷 ⚖️ 🧰.
-
-⚫️ 🤝 👆 🌐 💪 ⚒ 🕐 👈 👖 👆 🏗 🏆.
-
-& 👆 💪 ⚙️ 🔗 📚 👍 🚧 & 🛎 ⚙️ 📦 💖 `passlib` & `python-jose`, ↩️ **FastAPI** 🚫 🚚 🙆 🏗 🛠️ 🛠️ 🔢 📦.
-
-✋️ ⚫️ 🚚 👆 🧰 📉 🛠️ 🌅 💪 🍵 🎯 💪, ⚖, ⚖️ 💂♂.
-
-& 👆 💪 ⚙️ & 🛠️ 🔐, 🐩 🛠️, 💖 Oauth2️⃣ 📶 🙅 🌌.
-
-👆 💪 💡 🌅 **🏧 👩💻 🦮** 🔃 ❔ ⚙️ Oauth2️⃣ "↔", 🌖 👌-🧽 ✔ ⚙️, 📄 👫 🎏 🐩. Oauth2️⃣ ⏮️ ↔ 🛠️ ⚙️ 📚 🦏 🤝 🐕🦺, 💖 👱📔, 🇺🇸🔍, 📂, 🤸♂, 👱📔, ♒️. ✔ 🥉 🥳 🈸 🔗 ⏮️ 👫 🔗 🔛 👨💼 👫 👩💻.
diff --git a/docs/em/docs/tutorial/security/simple-oauth2.md b/docs/em/docs/tutorial/security/simple-oauth2.md
deleted file mode 100644
index 1fd513d48d..0000000000
--- a/docs/em/docs/tutorial/security/simple-oauth2.md
+++ /dev/null
@@ -1,289 +0,0 @@
-# 🙅 Oauth2️⃣ ⏮️ 🔐 & 📨
-
-🔜 ➡️ 🏗 ⚪️➡️ ⏮️ 📃 & 🚮 ❌ 🍕 ✔️ 🏁 💂♂ 💧.
-
-## 🤚 `username` & `password`
-
-👥 🔜 ⚙️ **FastAPI** 💂♂ 🚙 🤚 `username` & `password`.
-
-Oauth2️⃣ ✔ 👈 🕐❔ ⚙️ "🔐 💧" (👈 👥 ⚙️) 👩💻/👩💻 🔜 📨 `username` & `password` 🏑 📨 💽.
-
-& 🔌 💬 👈 🏑 ✔️ 🌟 💖 👈. `user-name` ⚖️ `email` 🚫🔜 👷.
-
-✋️ 🚫 😟, 👆 💪 🎦 ⚫️ 👆 🎋 👆 🏁 👩💻 🕸.
-
-& 👆 💽 🏷 💪 ⚙️ 🙆 🎏 📛 👆 💚.
-
-✋️ 💳 *➡ 🛠️*, 👥 💪 ⚙️ 👉 📛 🔗 ⏮️ 🔌 (& 💪, 🖼, ⚙️ 🛠️ 🛠️ 🧾 ⚙️).
-
-🔌 🇵🇸 👈 `username` & `password` 🔜 📨 📨 💽 (, 🙅♂ 🎻 📥).
-
-### `scope`
-
-🔌 💬 👈 👩💻 💪 📨 ➕1️⃣ 📨 🏑 "`scope`".
-
-📨 🏑 📛 `scope` (⭐), ✋️ ⚫️ 🤙 📏 🎻 ⏮️ "↔" 🎏 🚀.
-
-🔠 "↔" 🎻 (🍵 🚀).
-
-👫 🛎 ⚙️ 📣 🎯 💂♂ ✔, 🖼:
-
-* `users:read` ⚖️ `users:write` ⚠ 🖼.
-* `instagram_basic` ⚙️ 👱📔 / 👱📔.
-* `https://www.googleapis.com/auth/drive` ⚙️ 🇺🇸🔍.
-
-/// info
-
-Oauth2️⃣ "↔" 🎻 👈 📣 🎯 ✔ ✔.
-
-⚫️ 🚫 🤔 🚥 ⚫️ ✔️ 🎏 🦹 💖 `:` ⚖️ 🚥 ⚫️ 📛.
-
-👈 ℹ 🛠️ 🎯.
-
-Oauth2️⃣ 👫 🎻.
-
-///
-
-## 📟 🤚 `username` & `password`
-
-🔜 ➡️ ⚙️ 🚙 🚚 **FastAPI** 🍵 👉.
-
-### `OAuth2PasswordRequestForm`
-
-🥇, 🗄 `OAuth2PasswordRequestForm`, & ⚙️ ⚫️ 🔗 ⏮️ `Depends` *➡ 🛠️* `/token`:
-
-{* ../../docs_src/security/tutorial003.py hl[4,76] *}
-
-`OAuth2PasswordRequestForm` 🎓 🔗 👈 📣 📨 💪 ⏮️:
-
-* `username`.
-* `password`.
-* 📦 `scope` 🏑 🦏 🎻, ✍ 🎻 🎏 🚀.
-* 📦 `grant_type`.
-
-/// tip
-
-Oauth2️⃣ 🔌 🤙 *🚚* 🏑 `grant_type` ⏮️ 🔧 💲 `password`, ✋️ `OAuth2PasswordRequestForm` 🚫 🛠️ ⚫️.
-
-🚥 👆 💪 🛠️ ⚫️, ⚙️ `OAuth2PasswordRequestFormStrict` ↩️ `OAuth2PasswordRequestForm`.
-
-///
-
-* 📦 `client_id` (👥 🚫 💪 ⚫️ 👆 🖼).
-* 📦 `client_secret` (👥 🚫 💪 ⚫️ 👆 🖼).
-
-/// info
-
-`OAuth2PasswordRequestForm` 🚫 🎁 🎓 **FastAPI** `OAuth2PasswordBearer`.
-
-`OAuth2PasswordBearer` ⚒ **FastAPI** 💭 👈 ⚫️ 💂♂ ⚖. ⚫️ 🚮 👈 🌌 🗄.
-
-✋️ `OAuth2PasswordRequestForm` 🎓 🔗 👈 👆 💪 ✔️ ✍ 👆, ⚖️ 👆 💪 ✔️ 📣 `Form` 🔢 🔗.
-
-✋️ ⚫️ ⚠ ⚙️ 💼, ⚫️ 🚚 **FastAPI** 🔗, ⚒ ⚫️ ⏩.
-
-///
-
-### ⚙️ 📨 💽
-
-/// tip
-
-👐 🔗 🎓 `OAuth2PasswordRequestForm` 🏆 🚫 ✔️ 🔢 `scope` ⏮️ 📏 🎻 👽 🚀, ↩️, ⚫️ 🔜 ✔️ `scopes` 🔢 ⏮️ ☑ 📇 🎻 🔠 ↔ 📨.
-
-👥 🚫 ⚙️ `scopes` 👉 🖼, ✋️ 🛠️ 📤 🚥 👆 💪 ⚫️.
-
-///
-
-🔜, 🤚 👩💻 📊 ⚪️➡️ (❌) 💽, ⚙️ `username` ⚪️➡️ 📨 🏑.
-
-🚥 📤 🙅♂ ✅ 👩💻, 👥 📨 ❌ 💬 "❌ 🆔 ⚖️ 🔐".
-
-❌, 👥 ⚙️ ⚠ `HTTPException`:
-
-{* ../../docs_src/security/tutorial003.py hl[3,77:79] *}
-
-### ✅ 🔐
-
-👉 ☝ 👥 ✔️ 👩💻 📊 ⚪️➡️ 👆 💽, ✋️ 👥 🚫 ✅ 🔐.
-
-➡️ 🚮 👈 💽 Pydantic `UserInDB` 🏷 🥇.
-
-👆 🔜 🙅 🖊 🔢 🔐,, 👥 🔜 ⚙️ (❌) 🔐 🔁 ⚙️.
-
-🚥 🔐 🚫 🏏, 👥 📨 🎏 ❌.
-
-#### 🔐 🔁
-
-"🔁" ⛓: 🏭 🎚 (🔐 👉 💼) 🔘 🔁 🔢 (🎻) 👈 👀 💖 🙃.
-
-🕐❔ 👆 🚶♀️ ⚫️❔ 🎏 🎚 (⚫️❔ 🎏 🔐) 👆 🤚 ⚫️❔ 🎏 🙃.
-
-✋️ 👆 🚫🔜 🗜 ⚪️➡️ 🙃 🔙 🔐.
-
-##### ⚫️❔ ⚙️ 🔐 🔁
-
-🚥 👆 💽 📎, 🧙♀ 🏆 🚫 ✔️ 👆 👩💻' 🔢 🔐, 🕴#️⃣.
-
-, 🧙♀ 🏆 🚫 💪 🔄 ⚙️ 👈 🎏 🔐 ➕1️⃣ ⚙️ (📚 👩💻 ⚙️ 🎏 🔐 🌐, 👉 🔜 ⚠).
-
-{* ../../docs_src/security/tutorial003.py hl[80:83] *}
-
-#### 🔃 `**user_dict`
-
-`UserInDB(**user_dict)` ⛓:
-
-*🚶♀️ 🔑 & 💲 `user_dict` 🔗 🔑-💲 ❌, 🌓:*
-
-```Python
-UserInDB(
- username = user_dict["username"],
- email = user_dict["email"],
- full_name = user_dict["full_name"],
- disabled = user_dict["disabled"],
- hashed_password = user_dict["hashed_password"],
-)
-```
-
-/// info
-
-🌅 🏁 🔑 `**👩💻_ #️⃣ ` ✅ 🔙 [🧾 **➕ 🏷**](../extra-models.md#user_indict){.internal-link target=_blank}.
-
-///
-
-## 📨 🤝
-
-📨 `token` 🔗 🔜 🎻 🎚.
-
-⚫️ 🔜 ✔️ `token_type`. 👆 💼, 👥 ⚙️ "📨" 🤝, 🤝 🆎 🔜 "`bearer`".
-
-& ⚫️ 🔜 ✔️ `access_token`, ⏮️ 🎻 ⚗ 👆 🔐 🤝.
-
-👉 🙅 🖼, 👥 🔜 🍕 😟 & 📨 🎏 `username` 🤝.
-
-/// tip
-
-⏭ 📃, 👆 🔜 👀 🎰 🔐 🛠️, ⏮️ 🔐 #️⃣ & 🥙 🤝.
-
-✋️ 🔜, ➡️ 🎯 🔛 🎯 ℹ 👥 💪.
-
-///
-
-{* ../../docs_src/security/tutorial003.py hl[85] *}
-
-/// tip
-
-🔌, 👆 🔜 📨 🎻 ⏮️ `access_token` & `token_type`, 🎏 👉 🖼.
-
-👉 🕳 👈 👆 ✔️ 👆 👆 📟, & ⚒ 💭 👆 ⚙️ 📚 🎻 🔑.
-
-⚫️ 🌖 🕴 👜 👈 👆 ✔️ 💭 ☑ 👆, 🛠️ ⏮️ 🔧.
-
-🎂, **FastAPI** 🍵 ⚫️ 👆.
-
-///
-
-## ℹ 🔗
-
-🔜 👥 🔜 ℹ 👆 🔗.
-
-👥 💚 🤚 `current_user` *🕴* 🚥 👉 👩💻 🦁.
-
-, 👥 ✍ 🌖 🔗 `get_current_active_user` 👈 🔄 ⚙️ `get_current_user` 🔗.
-
-👯♂️ 👉 🔗 🔜 📨 🇺🇸🔍 ❌ 🚥 👩💻 🚫 🔀, ⚖️ 🚥 🔕.
-
-, 👆 🔗, 👥 🔜 🕴 🤚 👩💻 🚥 👩💻 🔀, ☑ 🔓, & 🦁:
-
-{* ../../docs_src/security/tutorial003.py hl[58:66,69:72,90] *}
-
-/// info
-
-🌖 🎚 `WWW-Authenticate` ⏮️ 💲 `Bearer` 👥 🛬 📥 🍕 🔌.
-
-🙆 🇺🇸🔍 (❌) 👔 📟 4️⃣0️⃣1️⃣ "⛔" 🤔 📨 `WWW-Authenticate` 🎚.
-
-💼 📨 🤝 (👆 💼), 💲 👈 🎚 🔜 `Bearer`.
-
-👆 💪 🤙 🚶 👈 ➕ 🎚 & ⚫️ 🔜 👷.
-
-✋️ ⚫️ 🚚 📥 🛠️ ⏮️ 🔧.
-
-, 📤 5️⃣📆 🧰 👈 ⌛ & ⚙️ ⚫️ (🔜 ⚖️ 🔮) & 👈 💪 ⚠ 👆 ⚖️ 👆 👩💻, 🔜 ⚖️ 🔮.
-
-👈 💰 🐩...
-
-///
-
-## 👀 ⚫️ 🎯
-
-📂 🎓 🩺: http://127.0.0.1:8000/docs.
-
-### 🔓
-
-🖊 "✔" 🔼.
-
-⚙️ 🎓:
-
-👩💻: `johndoe`
-
-🔐: `secret`
-
-
-
-⏮️ 🔗 ⚙️, 👆 🔜 👀 ⚫️ 💖:
-
-
-
-### 🤚 👆 👍 👩💻 💽
-
-🔜 ⚙️ 🛠️ `GET` ⏮️ ➡ `/users/me`.
-
-👆 🔜 🤚 👆 👩💻 📊, 💖:
-
-```JSON
-{
- "username": "johndoe",
- "email": "johndoe@example.com",
- "full_name": "John Doe",
- "disabled": false,
- "hashed_password": "fakehashedsecret"
-}
-```
-
-
-
-🚥 👆 🖊 🔒 ℹ & ⏏, & ⤴️ 🔄 🎏 🛠️ 🔄, 👆 🔜 🤚 🇺🇸🔍 4️⃣0️⃣1️⃣ ❌:
-
-```JSON
-{
- "detail": "Not authenticated"
-}
-```
-
-### 🔕 👩💻
-
-🔜 🔄 ⏮️ 🔕 👩💻, 🔓 ⏮️:
-
-👩💻: `alice`
-
-🔐: `secret2`
-
-& 🔄 ⚙️ 🛠️ `GET` ⏮️ ➡ `/users/me`.
-
-👆 🔜 🤚 "🔕 👩💻" ❌, 💖:
-
-```JSON
-{
- "detail": "Inactive user"
-}
-```
-
-## 🌃
-
-👆 🔜 ✔️ 🧰 🛠️ 🏁 💂♂ ⚙️ ⚓️ 🔛 `username` & `password` 👆 🛠️.
-
-⚙️ 👫 🧰, 👆 💪 ⚒ 💂♂ ⚙️ 🔗 ⏮️ 🙆 💽 & ⏮️ 🙆 👩💻 ⚖️ 💽 🏷.
-
-🕴 ℹ ❌ 👈 ⚫️ 🚫 🤙 "🔐".
-
-⏭ 📃 👆 🔜 👀 ❔ ⚙️ 🔐 🔐 🔁 🗃 & 🥙 🤝.
diff --git a/docs/em/docs/tutorial/static-files.md b/docs/em/docs/tutorial/static-files.md
deleted file mode 100644
index 27685c06d9..0000000000
--- a/docs/em/docs/tutorial/static-files.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# 🎻 📁
-
-👆 💪 🍦 🎻 📁 🔁 ⚪️➡️ 📁 ⚙️ `StaticFiles`.
-
-## ⚙️ `StaticFiles`
-
-* 🗄 `StaticFiles`.
-* "🗻" `StaticFiles()` 👐 🎯 ➡.
-
-{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.staticfiles import StaticFiles`.
-
-**FastAPI** 🚚 🎏 `starlette.staticfiles` `fastapi.staticfiles` 🏪 👆, 👩💻. ✋️ ⚫️ 🤙 👟 🔗 ⚪️➡️ 💃.
-
-///
-
-### ⚫️❔ "🗜"
-
-"🗜" ⛓ ❎ 🏁 "🔬" 🈸 🎯 ➡, 👈 ⤴️ ✊ 💅 🚚 🌐 🎧-➡.
-
-👉 🎏 ⚪️➡️ ⚙️ `APIRouter` 🗻 🈸 🍕 🔬. 🗄 & 🩺 ⚪️➡️ 👆 👑 🈸 🏆 🚫 🔌 🕳 ⚪️➡️ 🗻 🈸, ♒️.
-
-👆 💪 ✍ 🌅 🔃 👉 **🏧 👩💻 🦮**.
-
-## ℹ
-
-🥇 `"/static"` 🔗 🎧-➡ 👉 "🎧-🈸" 🔜 "🗻" 🔛. , 🙆 ➡ 👈 ▶️ ⏮️ `"/static"` 🔜 🍵 ⚫️.
-
-`directory="static"` 🔗 📛 📁 👈 🔌 👆 🎻 📁.
-
-`name="static"` 🤝 ⚫️ 📛 👈 💪 ⚙️ 🔘 **FastAPI**.
-
-🌐 👫 🔢 💪 🎏 🌘 "`static`", 🔆 👫 ⏮️ 💪 & 🎯 ℹ 👆 👍 🈸.
-
-## 🌅 ℹ
-
-🌖 ℹ & 🎛 ✅ 💃 🩺 🔃 🎻 📁.
diff --git a/docs/em/docs/tutorial/testing.md b/docs/em/docs/tutorial/testing.md
deleted file mode 100644
index 2e4a531f7d..0000000000
--- a/docs/em/docs/tutorial/testing.md
+++ /dev/null
@@ -1,185 +0,0 @@
-# 🔬
-
-👏 💃, 🔬 **FastAPI** 🈸 ⏩ & 😌.
-
-⚫️ ⚓️ 🔛 🇸🇲, ❔ 🔄 🏗 ⚓️ 🔛 📨, ⚫️ 📶 😰 & 🏋️.
-
-⏮️ ⚫️, 👆 💪 ⚙️ ✳ 🔗 ⏮️ **FastAPI**.
-
-## ⚙️ `TestClient`
-
-/// info
-
-⚙️ `TestClient`, 🥇 ❎ `httpx`.
-
-🤶 Ⓜ. `pip install httpx`.
-
-///
-
-🗄 `TestClient`.
-
-✍ `TestClient` 🚶♀️ 👆 **FastAPI** 🈸 ⚫️.
-
-✍ 🔢 ⏮️ 📛 👈 ▶️ ⏮️ `test_` (👉 🐩 `pytest` 🏛).
-
-⚙️ `TestClient` 🎚 🎏 🌌 👆 ⏮️ `httpx`.
-
-✍ 🙅 `assert` 📄 ⏮️ 🐩 🐍 🧬 👈 👆 💪 ✅ (🔄, 🐩 `pytest`).
-
-{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
-
-/// tip
-
-👀 👈 🔬 🔢 😐 `def`, 🚫 `async def`.
-
- & 🤙 👩💻 😐 🤙, 🚫 ⚙️ `await`.
-
-👉 ✔ 👆 ⚙️ `pytest` 🔗 🍵 🤢.
-
-///
-
-/// note | 📡 ℹ
-
-👆 💪 ⚙️ `from starlette.testclient import TestClient`.
-
-**FastAPI** 🚚 🎏 `starlette.testclient` `fastapi.testclient` 🏪 👆, 👩💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃.
-
-///
-
-/// tip
-
-🚥 👆 💚 🤙 `async` 🔢 👆 💯 ↖️ ⚪️➡️ 📨 📨 👆 FastAPI 🈸 (✅ 🔁 💽 🔢), ✔️ 👀 [🔁 💯](../advanced/async-tests.md){.internal-link target=_blank} 🏧 🔰.
-
-///
-
-## 🎏 💯
-
-🎰 🈸, 👆 🎲 🔜 ✔️ 👆 💯 🎏 📁.
-
-& 👆 **FastAPI** 🈸 5️⃣📆 ✍ 📚 📁/🕹, ♒️.
-
-### **FastAPI** 📱 📁
-
-➡️ 💬 👆 ✔️ 📁 📊 🔬 [🦏 🈸](bigger-applications.md){.internal-link target=_blank}:
-
-```
-.
-├── app
-│ ├── __init__.py
-│ └── main.py
-```
-
-📁 `main.py` 👆 ✔️ 👆 **FastAPI** 📱:
-
-
-{* ../../docs_src/app_testing/main.py *}
-
-### 🔬 📁
-
-⤴️ 👆 💪 ✔️ 📁 `test_main.py` ⏮️ 👆 💯. ⚫️ 💪 🖖 🔛 🎏 🐍 📦 (🎏 📁 ⏮️ `__init__.py` 📁):
-
-``` hl_lines="5"
-.
-├── app
-│ ├── __init__.py
-│ ├── main.py
-│ └── test_main.py
-```
-
-↩️ 👉 📁 🎏 📦, 👆 💪 ⚙️ ⚖ 🗄 🗄 🎚 `app` ⚪️➡️ `main` 🕹 (`main.py`):
-
-{* ../../docs_src/app_testing/test_main.py hl[3] *}
-
-...& ✔️ 📟 💯 💖 ⏭.
-
-## 🔬: ↔ 🖼
-
-🔜 ➡️ ↔ 👉 🖼 & 🚮 🌖 ℹ 👀 ❔ 💯 🎏 🍕.
-
-### ↔ **FastAPI** 📱 📁
-
-➡️ 😣 ⏮️ 🎏 📁 📊 ⏭:
-
-```
-.
-├── app
-│ ├── __init__.py
-│ ├── main.py
-│ └── test_main.py
-```
-
-➡️ 💬 👈 🔜 📁 `main.py` ⏮️ 👆 **FastAPI** 📱 ✔️ 🎏 **➡ 🛠️**.
-
-⚫️ ✔️ `GET` 🛠️ 👈 💪 📨 ❌.
-
-⚫️ ✔️ `POST` 🛠️ 👈 💪 📨 📚 ❌.
-
-👯♂️ *➡ 🛠️* 🚚 `X-Token` 🎚.
-
-{* ../../docs_src/app_testing/app_b/main.py *}
-
-### ↔ 🔬 📁
-
-👆 💪 ⤴️ ℹ `test_main.py` ⏮️ ↔ 💯:
-
-{* ../../docs_src/app_testing/app_b/test_main.py *}
-
-🕐❔ 👆 💪 👩💻 🚶♀️ ℹ 📨 & 👆 🚫 💭 ❔, 👆 💪 🔎 (🇺🇸🔍) ❔ ⚫️ `httpx`, ⚖️ ❔ ⚫️ ⏮️ `requests`, 🇸🇲 🔧 ⚓️ 🔛 📨' 🔧.
-
-⤴️ 👆 🎏 👆 💯.
-
-🤶 Ⓜ.:
-
-* 🚶♀️ *➡* ⚖️ *🔢* 🔢, 🚮 ⚫️ 📛 ⚫️.
-* 🚶♀️ 🎻 💪, 🚶♀️ 🐍 🎚 (✅ `dict`) 🔢 `json`.
-* 🚥 👆 💪 📨 *📨 💽* ↩️ 🎻, ⚙️ `data` 🔢 ↩️.
-* 🚶♀️ *🎚*, ⚙️ `dict` `headers` 🔢.
-* *🍪*, `dict` `cookies` 🔢.
-
-🌖 ℹ 🔃 ❔ 🚶♀️ 💽 👩💻 (⚙️ `httpx` ⚖️ `TestClient`) ✅ 🇸🇲 🧾.
-
-/// info
-
-🗒 👈 `TestClient` 📨 💽 👈 💪 🗜 🎻, 🚫 Pydantic 🏷.
-
-🚥 👆 ✔️ Pydantic 🏷 👆 💯 & 👆 💚 📨 🚮 💽 🈸 ⏮️ 🔬, 👆 💪 ⚙️ `jsonable_encoder` 🔬 [🎻 🔗 🔢](encoder.md){.internal-link target=_blank}.
-
-///
-
-## 🏃 ⚫️
-
-⏮️ 👈, 👆 💪 ❎ `pytest`:
-
-+With great-/// +Avoid inadvertently doing harm. -That command also created a dummy file `docs/la/index.md` for the main page, you can start by translating that one. - -You can continue with the previous instructions for an "Existing Language" for that process. - -You can make the first pull request with those two files, `docs/la/mkdocs.yml` and `docs/la/index.md`. 🎉 - -#### Preview the result - -As already mentioned above, you can use the `./scripts/docs.py` with the `live` command to preview the results (or `mkdocs serve`). - -Once you are done, you can also test it all as it would look online, including all the other languages. - -To do that, first build all the docs: - -powertools comes great responsibility. +
+
## **Typer**, the FastAPI of CLIs { #typer-the-fastapi-of-clis }
@@ -225,21 +193,7 @@ And still, the editor knows it is a `str`, and provides support for that.
You would do the same to declare `tuple`s and `set`s:
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial007_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial007.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
This means:
@@ -254,21 +208,7 @@ The first type parameter is for the keys of the `dict`.
The second type parameter is for the values of the `dict`:
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
This means:
@@ -292,10 +232,10 @@ In Python 3.10 there's also a **new syntax** where you can put the possible type
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008b.py!}
+{!> ../../docs_src/python_types/tutorial008b_py39.py!}
```
////
@@ -309,7 +249,7 @@ You can declare that a value could have a type, like `str`, but that it could al
In Python 3.6 and above (including Python 3.10) you can declare it by importing and using `Optional` from the `typing` module.
```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
+{!../../docs_src/python_types/tutorial009_py39.py!}
```
Using `Optional[str]` instead of just `str` will let the editor help you detect errors where you could be assuming that a value is always a `str`, when it could actually be `None` too.
@@ -326,18 +266,18 @@ This also means that in Python 3.10, you can use `Something | None`:
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009.py!}
+{!> ../../docs_src/python_types/tutorial009_py39.py!}
```
////
-//// tab | Python 3.8+ alternative
+//// tab | Python 3.9+ alternative
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009b.py!}
+{!> ../../docs_src/python_types/tutorial009b_py39.py!}
```
////
@@ -357,7 +297,7 @@ It's just about the words and names. But those words can affect how you and your
As an example, let's take this function:
-{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *}
+{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter:
@@ -390,10 +330,10 @@ You can use the same builtin types as generics (with square brackets and types i
* `set`
* `dict`
-And the same as with Python 3.8, from the `typing` module:
+And the same as with previous Python versions, from the `typing` module:
* `Union`
-* `Optional` (the same as with Python 3.8)
+* `Optional`
* ...and others.
In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the vertical bar (`|`) to declare unions of types, that's a lot better and simpler.
@@ -409,7 +349,7 @@ You can use the same builtin types as generics (with square brackets and types i
* `set`
* `dict`
-And the same as with Python 3.8, from the `typing` module:
+And generics from the `typing` module:
* `Union`
* `Optional`
@@ -417,29 +357,17 @@ And the same as with Python 3.8, from the `typing` module:
////
-//// tab | Python 3.8+
-
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Union`
-* `Optional`
-* ...and others.
-
-////
-
### Classes as types { #classes-as-types }
You can also declare a class as the type of a variable.
Let's say you have a class `Person`, with a name:
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
+{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
Then you can declare a variable to be of type `Person`:
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
+{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
And then, again, you get all the editor support:
@@ -463,29 +391,7 @@ And you get all the editor support with that resulting object.
An example from the official Pydantic docs:
-//// tab | Python 3.10+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py310.py!}
-```
-
-////
-
-//// tab | Python 3.9+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial011_py310.py *}
/// info
@@ -507,27 +413,9 @@ Pydantic has a special behavior when you use `Optional` or `Union[Something, Non
Python also has a feature that allows putting **additional metadata** in these type hints using `Annotated`.
-//// tab | Python 3.9+
+Since Python 3.9, `Annotated` is a part of the standard library, so you can import it from `typing`.
-In Python 3.9, `Annotated` is part of the standard library, so you can import it from `typing`.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-In versions below Python 3.9, you import `Annotated` from `typing_extensions`.
-
-It will already be installed with **FastAPI**.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
Python itself doesn't do anything with this `Annotated`. And for editors and other tools, the type is still `str`.
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 9380fb7d43..45bc9873ec 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -9,6 +9,400 @@ hide:
### Docs
+* 📝 Add contribution instructions about LLM generated code and comments and automated tools for PRs. PR [#14706](https://github.com/fastapi/fastapi/pull/14706) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Update docs for management tasks. PR [#14705](https://github.com/fastapi/fastapi/pull/14705) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Update docs about managing translations. PR [#14704](https://github.com/fastapi/fastapi/pull/14704) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Update docs for contributing with translations. PR [#14701](https://github.com/fastapi/fastapi/pull/14701) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Specify language code for code block. PR [#14656](https://github.com/fastapi/fastapi/pull/14656) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+### Translations
+
+* 🌐 Update translations for ko (update-outdated). PR [#14589](https://github.com/fastapi/fastapi/pull/14589) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update translations for uk (update-outdated). PR [#14587](https://github.com/fastapi/fastapi/pull/14587) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update translations for es (update-outdated). PR [#14686](https://github.com/fastapi/fastapi/pull/14686) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add LLM prompt file for Turkish, generated from the existing translations. PR [#14547](https://github.com/fastapi/fastapi/pull/14547) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add LLM prompt file for Traditional Chinese, generated from the existing translations. PR [#14550](https://github.com/fastapi/fastapi/pull/14550) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add LLM prompt file for Simplified Chinese, generated from the existing translations. PR [#14549](https://github.com/fastapi/fastapi/pull/14549) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* ⬆ Bump actions/cache from 4 to 5. PR [#14511](https://github.com/fastapi/fastapi/pull/14511) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump actions/upload-artifact from 5 to 6. PR [#14525](https://github.com/fastapi/fastapi/pull/14525) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump actions/download-artifact from 6 to 7. PR [#14526](https://github.com/fastapi/fastapi/pull/14526) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 👷 Tweak CI input names. PR [#14688](https://github.com/fastapi/fastapi/pull/14688) by [@tiangolo](https://github.com/tiangolo).
+* 🔨 Refactor translation script to allow committing in place. PR [#14687](https://github.com/fastapi/fastapi/pull/14687) by [@tiangolo](https://github.com/tiangolo).
+* 🐛 Fix translation script path. PR [#14685](https://github.com/fastapi/fastapi/pull/14685) by [@tiangolo](https://github.com/tiangolo).
+* ✅ Enable tests in CI for scripts. PR [#14684](https://github.com/fastapi/fastapi/pull/14684) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add pre-commit local script to fix language translations. PR [#14683](https://github.com/fastapi/fastapi/pull/14683) by [@tiangolo](https://github.com/tiangolo).
+* ⬆️ Migrate to uv. PR [#14676](https://github.com/fastapi/fastapi/pull/14676) by [@DoctorJohn](https://github.com/DoctorJohn).
+* 🔨 Add LLM translations tool fixer. PR [#14652](https://github.com/fastapi/fastapi/pull/14652) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 👥 Update FastAPI People - Sponsors. PR [#14626](https://github.com/fastapi/fastapi/pull/14626) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI GitHub topic repositories. PR [#14630](https://github.com/fastapi/fastapi/pull/14630) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI People - Contributors and Translators. PR [#14625](https://github.com/fastapi/fastapi/pull/14625) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update translation prompts. PR [#14619](https://github.com/fastapi/fastapi/pull/14619) by [@tiangolo](https://github.com/tiangolo).
+* 🔨 Update LLM translation script to guide reviewers to change the prompt. PR [#14614](https://github.com/fastapi/fastapi/pull/14614) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Do not run translations on cron while finishing updating existing languages. PR [#14613](https://github.com/fastapi/fastapi/pull/14613) by [@tiangolo](https://github.com/tiangolo).
+* 🔥 Remove test variants for Pydantic v1 in test_request_params. PR [#14612](https://github.com/fastapi/fastapi/pull/14612) by [@tiangolo](https://github.com/tiangolo).
+* 🔥 Remove Pydantic v1 specific test variants. PR [#14611](https://github.com/fastapi/fastapi/pull/14611) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.128.0
+
+### Breaking Changes
+
+* ➖ Drop support for `pydantic.v1`. PR [#14609](https://github.com/fastapi/fastapi/pull/14609) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* ✅ Run performance tests only on Pydantic v2. PR [#14608](https://github.com/fastapi/fastapi/pull/14608) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.127.1
+
+### Refactors
+
+* 🔊 Add a custom `FastAPIDeprecationWarning`. PR [#14605](https://github.com/fastapi/fastapi/pull/14605) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* 📝 Add documentary to website. PR [#14600](https://github.com/fastapi/fastapi/pull/14600) by [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* 🌐 Update translations for de (update-outdated). PR [#14602](https://github.com/fastapi/fastapi/pull/14602) by [@nilslindemann](https://github.com/nilslindemann).
+* 🌐 Update translations for de (update-outdated). PR [#14581](https://github.com/fastapi/fastapi/pull/14581) by [@nilslindemann](https://github.com/nilslindemann).
+
+### Internal
+
+* 🔧 Update pre-commit to use local Ruff instead of hook. PR [#14604](https://github.com/fastapi/fastapi/pull/14604) by [@tiangolo](https://github.com/tiangolo).
+* ✅ Add missing tests for code examples. PR [#14569](https://github.com/fastapi/fastapi/pull/14569) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 👷 Remove `lint` job from `test` CI workflow. PR [#14593](https://github.com/fastapi/fastapi/pull/14593) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 👷 Update secrets check. PR [#14592](https://github.com/fastapi/fastapi/pull/14592) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Run CodSpeed tests in parallel to other tests to speed up CI. PR [#14586](https://github.com/fastapi/fastapi/pull/14586) by [@tiangolo](https://github.com/tiangolo).
+* 🔨 Update scripts and pre-commit to autofix files. PR [#14585](https://github.com/fastapi/fastapi/pull/14585) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.127.0
+
+### Breaking Changes
+
+* 🔊 Add deprecation warnings when using `pydantic.v1`. PR [#14583](https://github.com/fastapi/fastapi/pull/14583) by [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* 🔧 Add LLM prompt file for Korean, generated from the existing translations. PR [#14546](https://github.com/fastapi/fastapi/pull/14546) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add LLM prompt file for Japanese, generated from the existing translations. PR [#14545](https://github.com/fastapi/fastapi/pull/14545) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* ⬆️ Upgrade OpenAI model for translations to gpt-5.2. PR [#14579](https://github.com/fastapi/fastapi/pull/14579) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.126.0
+
+### Upgrades
+
+* ➖ Drop support for Pydantic v1, keeping short temporary support for Pydantic v2's `pydantic.v1`. PR [#14575](https://github.com/fastapi/fastapi/pull/14575) by [@tiangolo](https://github.com/tiangolo).
+ * The minimum version of Pydantic installed is now `pydantic >=2.7.0`.
+ * The `standard` dependencies now include `pydantic-settings >=2.0.0` and `pydantic-extra-types >=2.0.0`.
+
+### Docs
+
+* 📝 Fix duplicated variable in `docs_src/python_types/tutorial005_py39.py`. PR [#14565](https://github.com/fastapi/fastapi/pull/14565) by [@paras-verma7454](https://github.com/paras-verma7454).
+
+### Translations
+
+* 🔧 Add LLM prompt file for Ukrainian, generated from the existing translations. PR [#14548](https://github.com/fastapi/fastapi/pull/14548) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* 🔧 Tweak pre-commit to allow committing release-notes. PR [#14577](https://github.com/fastapi/fastapi/pull/14577) by [@tiangolo](https://github.com/tiangolo).
+* ⬆️ Use prek as a pre-commit alternative. PR [#14572](https://github.com/fastapi/fastapi/pull/14572) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Add performance tests with CodSpeed. PR [#14558](https://github.com/fastapi/fastapi/pull/14558) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.125.0
+
+### Breaking Changes
+
+* 🔧 Drop support for Python 3.8. PR [#14563](https://github.com/fastapi/fastapi/pull/14563) by [@tiangolo](https://github.com/tiangolo).
+ * This would actually not be a _breaking_ change as no code would really break. Any Python 3.8 installer would just refuse to install the latest version of FastAPI and would only install 0.124.4. Only marking it as a "breaking change" to make it visible.
+
+### Refactors
+
+* ♻️ Upgrade internal syntax to Python 3.9+ 🎉. PR [#14564](https://github.com/fastapi/fastapi/pull/14564) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* ⚰️ Remove Python 3.8 from CI and remove Python 3.8 examples from source docs. PR [#14559](https://github.com/fastapi/fastapi/pull/14559) by [@YuriiMotov](https://github.com/YuriiMotov) and [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* 🌐 Update translations for pt (add-missing). PR [#14539](https://github.com/fastapi/fastapi/pull/14539) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add LLM prompt file for French, generated from the existing French docs. PR [#14544](https://github.com/fastapi/fastapi/pull/14544) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Sync Portuguese docs (pages found with script). PR [#14554](https://github.com/fastapi/fastapi/pull/14554) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🌐 Sync Spanish docs (outdated pages found with script). PR [#14553](https://github.com/fastapi/fastapi/pull/14553) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🌐 Sync German docs. PR [#14519](https://github.com/fastapi/fastapi/pull/14519) by [@nilslindemann](https://github.com/nilslindemann).
+* 🔥 Remove inactive/scarce translations to Vietnamese. PR [#14543](https://github.com/fastapi/fastapi/pull/14543) by [@tiangolo](https://github.com/tiangolo).
+* 🔥 Remove inactive/scarce translations to Persian. PR [#14542](https://github.com/fastapi/fastapi/pull/14542) by [@tiangolo](https://github.com/tiangolo).
+* 🔥 Remove translation to emoji to simplify the new setup with LLM autotranslations. PR [#14541](https://github.com/fastapi/fastapi/pull/14541) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update translations for pt (update-outdated). PR [#14537](https://github.com/fastapi/fastapi/pull/14537) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update translations for es (update-outdated). PR [#14532](https://github.com/fastapi/fastapi/pull/14532) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update translations for es (add-missing). PR [#14533](https://github.com/fastapi/fastapi/pull/14533) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Remove translations for removed docs. PR [#14516](https://github.com/fastapi/fastapi/pull/14516) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* ⬆ Bump `markdown-include-variants` from 0.0.7 to 0.0.8. PR [#14556](https://github.com/fastapi/fastapi/pull/14556) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🔧 Temporarily disable translations still in progress, being migrated to the new LLM setup. PR [#14555](https://github.com/fastapi/fastapi/pull/14555) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🔧 Update test workflow config, remove commented code. PR [#14540](https://github.com/fastapi/fastapi/pull/14540) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Configure coverage, error on main tests, don't wait for Smokeshow. PR [#14536](https://github.com/fastapi/fastapi/pull/14536) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Run Smokeshow always, even on test failures. PR [#14538](https://github.com/fastapi/fastapi/pull/14538) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Make Pydantic versions customizable in CI. PR [#14535](https://github.com/fastapi/fastapi/pull/14535) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Fix checkout GitHub Action fetch-depth for LLM translations, enable cron monthly. PR [#14531](https://github.com/fastapi/fastapi/pull/14531) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Fix Typer command for CI LLM translations. PR [#14530](https://github.com/fastapi/fastapi/pull/14530) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Update LLM translation CI, add language matrix and extra commands, prepare for scheduled run. PR [#14529](https://github.com/fastapi/fastapi/pull/14529) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Update github-actions user for GitHub Actions workflows. PR [#14528](https://github.com/fastapi/fastapi/pull/14528) by [@tiangolo](https://github.com/tiangolo).
+* ➕ Add requirements for translations. PR [#14515](https://github.com/fastapi/fastapi/pull/14515) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.124.4
+
+### Fixes
+
+* 🐛 Fix parameter aliases. PR [#14371](https://github.com/fastapi/fastapi/pull/14371) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+## 0.124.3
+
+### Fixes
+
+* 🐛 Fix support for tagged union with discriminator inside of `Annotated` with `Body()`. PR [#14512](https://github.com/fastapi/fastapi/pull/14512) by [@tiangolo](https://github.com/tiangolo).
+
+### Refactors
+
+* ✅ Add set of tests for request parameters and alias. PR [#14358](https://github.com/fastapi/fastapi/pull/14358) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+### Docs
+
+* 📝 Tweak links format. PR [#14505](https://github.com/fastapi/fastapi/pull/14505) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Update docs about re-raising validation errors, do not include string as is to not leak information. PR [#14487](https://github.com/fastapi/fastapi/pull/14487) by [@tiangolo](https://github.com/tiangolo).
+* 🔥 Remove external links section. PR [#14486](https://github.com/fastapi/fastapi/pull/14486) by [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* 🌐 Sync Russian docs. PR [#14509](https://github.com/fastapi/fastapi/pull/14509) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🌐 Sync German docs. PR [#14488](https://github.com/fastapi/fastapi/pull/14488) by [@nilslindemann](https://github.com/nilslindemann).
+
+### Internal
+
+* 👷 Tweak coverage to not pass Smokeshow max file size limit. PR [#14507](https://github.com/fastapi/fastapi/pull/14507) by [@tiangolo](https://github.com/tiangolo).
+* ✅ Expand test matrix to include Windows and MacOS. PR [#14171](https://github.com/fastapi/fastapi/pull/14171) by [@svlandeg](https://github.com/svlandeg).
+
+## 0.124.2
+
+### Fixes
+
+* 🐛 Fix support for `if TYPE_CHECKING`, non-evaluated stringified annotations. PR [#14485](https://github.com/fastapi/fastapi/pull/14485) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.124.1
+
+### Fixes
+
+* 🐛 Fix handling arbitrary types when using `arbitrary_types_allowed=True`. PR [#14482](https://github.com/fastapi/fastapi/pull/14482) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* 📝 Add variants for code examples in "Advanced User Guide". PR [#14413](https://github.com/fastapi/fastapi/pull/14413) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 📝 Update tech stack in project generation docs. PR [#14472](https://github.com/fastapi/fastapi/pull/14472) by [@alejsdev](https://github.com/alejsdev).
+
+### Internal
+
+* ✅ Add test for Pydantic v2, dataclasses, UUID, and `__annotations__`. PR [#14477](https://github.com/fastapi/fastapi/pull/14477) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.124.0
+
+### Features
+
+* 🚸 Improve tracebacks by adding endpoint metadata. PR [#14306](https://github.com/fastapi/fastapi/pull/14306) by [@savannahostrowski](https://github.com/savannahostrowski).
+
+### Internal
+
+* ✏️ Fix typo in `scripts/mkdocs_hooks.py`. PR [#14457](https://github.com/fastapi/fastapi/pull/14457) by [@yujiteshima](https://github.com/yujiteshima).
+
+## 0.123.10
+
+### Fixes
+
+* 🐛 Fix using class (not instance) dependency that has `__call__` method. PR [#14458](https://github.com/fastapi/fastapi/pull/14458) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🐛 Fix `separate_input_output_schemas=False` with `computed_field`. PR [#14453](https://github.com/fastapi/fastapi/pull/14453) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+## 0.123.9
+
+### Fixes
+
+* 🐛 Fix OAuth2 scopes in OpenAPI in extra corner cases, parent dependency with scopes, sub-dependency security scheme without scopes. PR [#14459](https://github.com/fastapi/fastapi/pull/14459) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.123.8
+
+### Fixes
+
+* 🐛 Fix OpenAPI security scheme OAuth2 scopes declaration, deduplicate security schemes with different scopes. PR [#14455](https://github.com/fastapi/fastapi/pull/14455) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.123.7
+
+### Fixes
+
+* 🐛 Fix evaluating stringified annotations in Python 3.10. PR [#11355](https://github.com/fastapi/fastapi/pull/11355) by [@chaen](https://github.com/chaen).
+
+## 0.123.6
+
+### Fixes
+
+* 🐛 Fix support for functools wraps and partial combined, for async and regular functions and classes in path operations and dependencies. PR [#14448](https://github.com/fastapi/fastapi/pull/14448) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.123.5
+
+### Features
+
+* ✨ Allow using dependables with `functools.partial()`. PR [#9753](https://github.com/fastapi/fastapi/pull/9753) by [@lieryan](https://github.com/lieryan).
+* ✨ Add support for wrapped functions (e.g. `@functools.wraps()`) used with forward references. PR [#5077](https://github.com/fastapi/fastapi/pull/5077) by [@lucaswiman](https://github.com/lucaswiman).
+* ✨ Handle wrapped dependencies. PR [#9555](https://github.com/fastapi/fastapi/pull/9555) by [@phy1729](https://github.com/phy1729).
+
+### Fixes
+
+* 🐛 Fix optional sequence handling with new union syntax from Python 3.10. PR [#14430](https://github.com/fastapi/fastapi/pull/14430) by [@Viicos](https://github.com/Viicos).
+
+### Refactors
+
+* 🔥 Remove dangling extra condiitonal no longer needed. PR [#14435](https://github.com/fastapi/fastapi/pull/14435) by [@tiangolo](https://github.com/tiangolo).
+* ♻️ Refactor internals, update `is_coroutine` check to reuse internal supported variants (unwrap, check class). PR [#14434](https://github.com/fastapi/fastapi/pull/14434) by [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* 🌐 Sync German docs. PR [#14367](https://github.com/fastapi/fastapi/pull/14367) by [@nilslindemann](https://github.com/nilslindemann).
+
+## 0.123.4
+
+### Fixes
+
+* 🐛 Fix OpenAPI schema support for computed fields when using `separate_input_output_schemas=False`. PR [#13207](https://github.com/fastapi/fastapi/pull/13207) by [@vgrafe](https://github.com/vgrafe).
+
+### Docs
+
+* 📝 Fix docstring of `servers` parameter. PR [#14405](https://github.com/fastapi/fastapi/pull/14405) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+## 0.123.3
+
+### Fixes
+
+* 🐛 Fix Query\Header\Cookie parameter model alias. PR [#14360](https://github.com/fastapi/fastapi/pull/14360) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🐛 Fix optional sequence handling in `serialize sequence value` with Pydantic V2. PR [#14297](https://github.com/fastapi/fastapi/pull/14297) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+## 0.123.2
+
+### Fixes
+
+* 🐛 Fix unformatted `{type_}` in FastAPIError. PR [#14416](https://github.com/fastapi/fastapi/pull/14416) by [@Just-Helpful](https://github.com/Just-Helpful).
+* 🐛 Fix parsing extra non-body parameter list. PR [#14356](https://github.com/fastapi/fastapi/pull/14356) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🐛 Fix parsing extra `Form` parameter list. PR [#14303](https://github.com/fastapi/fastapi/pull/14303) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🐛 Fix support for form values with empty strings interpreted as missing (`None` if that's the default), for compatibility with HTML forms. PR [#13537](https://github.com/fastapi/fastapi/pull/13537) by [@MarinPostma](https://github.com/MarinPostma).
+
+### Docs
+
+* 📝 Add tip on how to install `pip` in case of `No module named pip` error in `virtual-environments.md`. PR [#14211](https://github.com/fastapi/fastapi/pull/14211) by [@zadevhub](https://github.com/zadevhub).
+* 📝 Update Primary Key notes for the SQL databases tutorial to avoid confusion. PR [#14120](https://github.com/fastapi/fastapi/pull/14120) by [@FlaviusRaducu](https://github.com/FlaviusRaducu).
+* 📝 Clarify estimation note in documentation. PR [#14070](https://github.com/fastapi/fastapi/pull/14070) by [@SaisakthiM](https://github.com/SaisakthiM).
+
+## 0.123.1
+
+### Fixes
+
+* 🐛 Avoid accessing non-existing "$ref" key for Pydantic v2 compat remapping. PR [#14361](https://github.com/fastapi/fastapi/pull/14361) by [@svlandeg](https://github.com/svlandeg).
+* 🐛 Fix `TypeError` when encoding a decimal with a `NaN` or `Infinity` value. PR [#12935](https://github.com/fastapi/fastapi/pull/12935) by [@kentwelcome](https://github.com/kentwelcome).
+
+### Internal
+
+* 🐛 Fix Windows UnicodeEncodeError in CLI test. PR [#14295](https://github.com/fastapi/fastapi/pull/14295) by [@hemanth-thirthahalli](https://github.com/hemanth-thirthahalli).
+* 🔧 Update sponsors: add Greptile. PR [#14429](https://github.com/fastapi/fastapi/pull/14429) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI GitHub topic repositories. PR [#14426](https://github.com/fastapi/fastapi/pull/14426) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Bump markdown-include-variants from 0.0.6 to 0.0.7. PR [#14423](https://github.com/fastapi/fastapi/pull/14423) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 👥 Update FastAPI People - Sponsors. PR [#14422](https://github.com/fastapi/fastapi/pull/14422) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI People - Contributors and Translators. PR [#14420](https://github.com/fastapi/fastapi/pull/14420) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.123.0
+
+### Fixes
+
+* 🐛 Cache dependencies that don't use scopes and don't have sub-dependencies with scopes. PR [#14419](https://github.com/fastapi/fastapi/pull/14419) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.122.1
+
+### Fixes
+
+* 🐛 Fix hierarchical security scope propagation. PR [#5624](https://github.com/fastapi/fastapi/pull/5624) by [@kristjanvalur](https://github.com/kristjanvalur).
+
+### Docs
+
+* 💅 Update CSS to explicitly use emoji font. PR [#14415](https://github.com/fastapi/fastapi/pull/14415) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* ⬆ Bump markdown-include-variants from 0.0.5 to 0.0.6. PR [#14418](https://github.com/fastapi/fastapi/pull/14418) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+## 0.122.0
+
+### Fixes
+
+* 🐛 Use `401` status code in security classes when credentials are missing. PR [#13786](https://github.com/fastapi/fastapi/pull/13786) by [@YuriiMotov](https://github.com/YuriiMotov).
+ * If your code depended on these classes raising the old (less correct) `403` status code, check the new docs about how to override the classes, to use the same old behavior: [Use Old 403 Authentication Error Status Codes](https://fastapi.tiangolo.com/how-to/authentication-error-status-code/).
+
+### Internal
+
+* 🔧 Configure labeler to exclude files that start from underscore for `lang-all` label. PR [#14213](https://github.com/fastapi/fastapi/pull/14213) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 👷 Add pre-commit config with local script for permalinks. PR [#14398](https://github.com/fastapi/fastapi/pull/14398) by [@tiangolo](https://github.com/tiangolo).
+* 💄 Use font Fira Code to fix display of Rich panels in docs in Windows. PR [#14387](https://github.com/fastapi/fastapi/pull/14387) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Add custom pre-commit CI. PR [#14397](https://github.com/fastapi/fastapi/pull/14397) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Bump actions/checkout from 5 to 6. PR [#14381](https://github.com/fastapi/fastapi/pull/14381) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 👷 Upgrade `latest-changes` GitHub Action and pin `actions/checkout@v5`. PR [#14403](https://github.com/fastapi/fastapi/pull/14403) by [@svlandeg](https://github.com/svlandeg).
+* 🛠️ Add `add-permalinks` and `add-permalinks-page` to `scripts/docs.py`. PR [#14033](https://github.com/fastapi/fastapi/pull/14033) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🔧 Upgrade Material for MkDocs and remove insiders. PR [#14375](https://github.com/fastapi/fastapi/pull/14375) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.121.3
+
+### Refactors
+
+* ♻️ Make the result of `Depends()` and `Security()` hashable, as a workaround for other tools interacting with these internal parts. PR [#14372](https://github.com/fastapi/fastapi/pull/14372) by [@tiangolo](https://github.com/tiangolo).
+
+### Upgrades
+
+* ⬆️ Bump Starlette to <`0.51.0`. PR [#14282](https://github.com/fastapi/fastapi/pull/14282) by [@musicinmybrain](https://github.com/musicinmybrain).
+
+### Docs
+
+* 📝 Add missing hash part. PR [#14369](https://github.com/fastapi/fastapi/pull/14369) by [@nilslindemann](https://github.com/nilslindemann).
+* 📝 Fix typos in code comments. PR [#14364](https://github.com/fastapi/fastapi/pull/14364) by [@Edge-Seven](https://github.com/Edge-Seven).
+* 📝 Add docs for using FastAPI Cloud. PR [#14359](https://github.com/fastapi/fastapi/pull/14359) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.121.2
+
+### Fixes
+
+* 🐛 Fix handling of JSON Schema attributes named "$ref". PR [#14349](https://github.com/fastapi/fastapi/pull/14349) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* 📝 Add EuroPython talk & podcast episode with Sebastián Ramírez. PR [#14260](https://github.com/fastapi/fastapi/pull/14260) by [@clytaemnestra](https://github.com/clytaemnestra).
+* ✏️ Fix links and add missing permalink in docs. PR [#14217](https://github.com/fastapi/fastapi/pull/14217) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+### Translations
+
+* 🌐 Update Portuguese translations with LLM prompt. PR [#14228](https://github.com/fastapi/fastapi/pull/14228) by [@ceb10n](https://github.com/ceb10n).
+* 🔨 Add Portuguese translations LLM prompt. PR [#14208](https://github.com/fastapi/fastapi/pull/14208) by [@ceb10n](https://github.com/ceb10n).
+* 🌐 Sync Russian docs. PR [#14331](https://github.com/fastapi/fastapi/pull/14331) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🌐 Sync German docs. PR [#14317](https://github.com/fastapi/fastapi/pull/14317) by [@nilslindemann](https://github.com/nilslindemann).
+
+## 0.121.1
+
+### Fixes
+
+* 🐛 Fix `Depends(func, scope='function')` for top level (parameterless) dependencies. PR [#14301](https://github.com/fastapi/fastapi/pull/14301) by [@luzzodev](https://github.com/luzzodev).
+
+### Docs
+
* 📝 Upate docs for advanced dependencies with `yield`, noting the changes in 0.121.0, adding `scope`. PR [#14287](https://github.com/fastapi/fastapi/pull/14287) by [@tiangolo](https://github.com/tiangolo).
### Internal
diff --git a/docs/en/docs/resources/index.md b/docs/en/docs/resources/index.md
index caefdf1254..f7d48576ff 100644
--- a/docs/en/docs/resources/index.md
+++ b/docs/en/docs/resources/index.md
@@ -1,3 +1,3 @@
# Resources { #resources }
-Additional resources, external links, articles and more. ✈️
+Additional resources, external links, and more. ✈️
diff --git a/docs/en/docs/tutorial/background-tasks.md b/docs/en/docs/tutorial/background-tasks.md
index ab44f89c19..be7ecd5871 100644
--- a/docs/en/docs/tutorial/background-tasks.md
+++ b/docs/en/docs/tutorial/background-tasks.md
@@ -15,7 +15,7 @@ This includes, for example:
First, import `BackgroundTasks` and define a parameter in your *path operation function* with a type declaration of `BackgroundTasks`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
**FastAPI** will create the object of type `BackgroundTasks` for you and pass it as that parameter.
@@ -31,13 +31,13 @@ In this case, the task function will write to a file (simulating sending an emai
And as the write operation doesn't use `async` and `await`, we define the function with normal `def`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
## Add the background task { #add-the-background-task }
Inside of your *path operation function*, pass your task function to the *background tasks* object with the method `.add_task()`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
`.add_task()` receives as arguments:
diff --git a/docs/en/docs/tutorial/bigger-applications.md b/docs/en/docs/tutorial/bigger-applications.md
index 74daa54835..f6cee8036b 100644
--- a/docs/en/docs/tutorial/bigger-applications.md
+++ b/docs/en/docs/tutorial/bigger-applications.md
@@ -56,7 +56,7 @@ from app.routers import items
The same file structure with comments:
-```
+```bash
.
├── app # "app" is a Python package
│ ├── __init__.py # this file makes "app" a "Python package"
@@ -85,9 +85,7 @@ You can create the *path operations* for that module using `APIRouter`.
You import it and create an "instance" the same way you would with the class `FastAPI`:
-```Python hl_lines="1 3" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
### *Path operations* with `APIRouter` { #path-operations-with-apirouter }
@@ -95,9 +93,7 @@ And then you use it to declare your *path operations*.
Use it the same way you would use the `FastAPI` class:
-```Python hl_lines="6 11 16" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
You can think of `APIRouter` as a "mini `FastAPI`" class.
@@ -121,35 +117,7 @@ So we put them in their own `dependencies` module (`app/dependencies.py`).
We will now use a simple dependency to read a custom `X-Token` header:
-//// tab | Python 3.9+
-
-```Python hl_lines="3 6-8" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 5-7" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app_an/dependencies.py!}
-```
-
-////
-
-//// tab | Python 3.8+ non-Annotated
-
-/// tip
-
-Prefer to use the `Annotated` version if possible.
-
-///
-
-```Python hl_lines="1 4-6" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app/dependencies.py!}
-```
-
-////
+{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
/// tip
@@ -181,9 +149,7 @@ We know all the *path operations* in this module have the same:
So, instead of adding all that to each *path operation*, we can add it to the `APIRouter`.
-```Python hl_lines="5-10 16 21" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
As the path of each *path operation* has to start with `/`, like in:
@@ -242,9 +208,7 @@ And we need to get the dependency function from the module `app.dependencies`, t
So we use a relative import with `..` for the dependencies:
-```Python hl_lines="3" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
#### How relative imports work { #how-relative-imports-work }
@@ -315,9 +279,7 @@ We are not adding the prefix `/items` nor the `tags=["items"]` to each *path ope
But we can still add _more_ `tags` that will be applied to a specific *path operation*, and also some extra `responses` specific to that *path operation*:
-```Python hl_lines="30-31" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *}
/// tip
@@ -343,17 +305,13 @@ You import and create a `FastAPI` class as normally.
And we can even declare [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank} that will be combined with the dependencies for each `APIRouter`:
-```Python hl_lines="1 3 7" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
### Import the `APIRouter` { #import-the-apirouter }
Now we import the other submodules that have `APIRouter`s:
-```Python hl_lines="4-5" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
As the files `app/routers/users.py` and `app/routers/items.py` are submodules that are part of the same Python package `app`, we can use a single dot `.` to import them using "relative imports".
@@ -416,17 +374,13 @@ the `router` from `users` would overwrite the one from `items` and we wouldn't b
So, to be able to use both of them in the same file, we import the submodules directly:
-```Python hl_lines="5" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *}
### Include the `APIRouter`s for `users` and `items` { #include-the-apirouters-for-users-and-items }
Now, let's include the `router`s from the submodules `users` and `items`:
-```Python hl_lines="10-11" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *}
/// info
@@ -466,17 +420,13 @@ It contains an `APIRouter` with some admin *path operations* that your organizat
For this example it will be super simple. But let's say that because it is shared with other projects in the organization, we cannot modify it and add a `prefix`, `dependencies`, `tags`, etc. directly to the `APIRouter`:
-```Python hl_lines="3" title="app/internal/admin.py"
-{!../../docs_src/bigger_applications/app/internal/admin.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
But we still want to set a custom `prefix` when including the `APIRouter` so that all its *path operations* start with `/admin`, we want to secure it with the `dependencies` we already have for this project, and we want to include `tags` and `responses`.
We can declare all that without having to modify the original `APIRouter` by passing those parameters to `app.include_router()`:
-```Python hl_lines="14-17" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *}
That way, the original `APIRouter` will stay unmodified, so we can still share that same `app/internal/admin.py` file with other projects in the organization.
@@ -497,9 +447,7 @@ We can also add *path operations* directly to the `FastAPI` app.
Here we do it... just to show that we can 🤷:
-```Python hl_lines="21-23" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *}
and it will work correctly, together with all the other *path operations* added with `app.include_router()`.
diff --git a/docs/en/docs/tutorial/body-nested-models.md b/docs/en/docs/tutorial/body-nested-models.md
index 445235a420..5fd83a8f3c 100644
--- a/docs/en/docs/tutorial/body-nested-models.md
+++ b/docs/en/docs/tutorial/body-nested-models.md
@@ -14,35 +14,15 @@ This will make `tags` be a list, although it doesn't declare the type of the ele
But Python has a specific way to declare lists with internal types, or "type parameters":
-### Import typing's `List` { #import-typings-list }
-
-In Python 3.9 and above you can use the standard `list` to declare these type annotations as we'll see below. 💡
-
-But in Python versions before 3.9 (3.6 and above), you first need to import `List` from standard Python's `typing` module:
-
-{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
-
### Declare a `list` with a type parameter { #declare-a-list-with-a-type-parameter }
-To declare types that have type parameters (internal types), like `list`, `dict`, `tuple`:
-
-* If you are in a Python version lower than 3.9, import their equivalent version from the `typing` module
-* Pass the internal type(s) as "type parameters" using square brackets: `[` and `]`
-
-In Python 3.9 it would be:
+To declare types that have type parameters (internal types), like `list`, `dict`, `tuple`,
+pass the internal type(s) as "type parameters" using square brackets: `[` and `]`
```Python
my_list: list[str]
```
-In versions of Python before 3.9, it would be:
-
-```Python
-from typing import List
-
-my_list: List[str]
-```
-
That's all standard Python syntax for type declarations.
Use that same standard syntax for model attributes with internal types.
@@ -178,12 +158,6 @@ Notice how `Offer` has a list of `Item`s, which in turn have an optional list of
If the top level value of the JSON body you expect is a JSON `array` (a Python `list`), you can declare the type in the parameter of the function, the same as in Pydantic models:
-```Python
-images: List[Image]
-```
-
-or in Python 3.9 and above:
-
```Python
images: list[Image]
```
diff --git a/docs/en/docs/tutorial/body-updates.md b/docs/en/docs/tutorial/body-updates.md
index baeb53ec6f..1b7fd70661 100644
--- a/docs/en/docs/tutorial/body-updates.md
+++ b/docs/en/docs/tutorial/body-updates.md
@@ -50,14 +50,6 @@ If you want to receive partial updates, it's very useful to use the parameter `e
Like `item.model_dump(exclude_unset=True)`.
-/// info
-
-In Pydantic v1 the method was called `.dict()`, it was deprecated (but still supported) in Pydantic v2, and renamed to `.model_dump()`.
-
-The examples here use `.dict()` for compatibility with Pydantic v1, but you should use `.model_dump()` instead if you can use Pydantic v2.
-
-///
-
That would generate a `dict` with only the data that was set when creating the `item` model, excluding default values.
Then you can use this to generate a `dict` with only the data that was set (sent in the request), omitting default values:
@@ -68,14 +60,6 @@ Then you can use this to generate a `dict` with only the data that was set (sent
Now, you can create a copy of the existing model using `.model_copy()`, and pass the `update` parameter with a `dict` containing the data to update.
-/// info
-
-In Pydantic v1 the method was called `.copy()`, it was deprecated (but still supported) in Pydantic v2, and renamed to `.model_copy()`.
-
-The examples here use `.copy()` for compatibility with Pydantic v1, but you should use `.model_copy()` instead if you can use Pydantic v2.
-
-///
-
Like `stored_item_model.model_copy(update=update_data)`:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
diff --git a/docs/en/docs/tutorial/body.md b/docs/en/docs/tutorial/body.md
index a820802f74..2d0dfcbb59 100644
--- a/docs/en/docs/tutorial/body.md
+++ b/docs/en/docs/tutorial/body.md
@@ -128,14 +128,6 @@ Inside of the function, you can access all the attributes of the model object di
{* ../../docs_src/body/tutorial002_py310.py *}
-/// info
-
-In Pydantic v1 the method was called `.dict()`, it was deprecated (but still supported) in Pydantic v2, and renamed to `.model_dump()`.
-
-The examples here use `.dict()` for compatibility with Pydantic v1, but you should use `.model_dump()` instead if you can use Pydantic v2.
-
-///
-
## Request body + path parameters { #request-body-path-parameters }
You can declare path parameters and request body at the same time.
@@ -163,7 +155,7 @@ The function parameters will be recognized as follows:
FastAPI will know that the value of `q` is not required because of the default value `= None`.
-The `str | None` (Python 3.10+) or `Union` in `Union[str, None]` (Python 3.8+) is not used by FastAPI to determine that the value is not required, it will know it's not required because it has a default value of `= None`.
+The `str | None` (Python 3.10+) or `Union` in `Union[str, None]` (Python 3.9+) is not used by FastAPI to determine that the value is not required, it will know it's not required because it has a default value of `= None`.
But adding the type annotations will allow your editor to give you better support and detect errors.
diff --git a/docs/en/docs/tutorial/cookie-param-models.md b/docs/en/docs/tutorial/cookie-param-models.md
index 96dc5cf3d8..016a65d7f0 100644
--- a/docs/en/docs/tutorial/cookie-param-models.md
+++ b/docs/en/docs/tutorial/cookie-param-models.md
@@ -50,7 +50,7 @@ Your API now has the power to control its own FastAPI Cloud, go and join the waiting list if you haven't. 🚀
+
+If you already have a **FastAPI Cloud** account (we invited you from the waiting list 😉), you can deploy your application with one command.
+
+Before deploying, make sure you are logged in:
+
+kwargs. Even if they don't have a default value.
-{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
### Better with `Annotated` { #better-with-annotated }
diff --git a/docs/en/docs/tutorial/path-params.md b/docs/en/docs/tutorial/path-params.md
index 457cc2713f..ea4307900c 100644
--- a/docs/en/docs/tutorial/path-params.md
+++ b/docs/en/docs/tutorial/path-params.md
@@ -2,7 +2,7 @@
You can declare path "parameters" or "variables" with the same syntax used by Python format strings:
-{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
+{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
The value of the path parameter `item_id` will be passed to your function as the argument `item_id`.
@@ -16,7 +16,7 @@ So, if you run this example and go to Enumerations (or enums) are available in Python since version 3.4.
-
-///
+{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *}
/// tip
@@ -158,7 +152,7 @@ If you are wondering, "AlexNet", "ResNet", and "LeNet" are just names of Machine
Then create a *path parameter* with a type annotation using the enum class you created (`ModelName`):
-{* ../../docs_src/path_params/tutorial005.py hl[16] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
### Check the docs { #check-the-docs }
@@ -174,13 +168,13 @@ The value of the *path parameter* will be an *enumeration member*.
You can compare it with the *enumeration member* in your created enum `ModelName`:
-{* ../../docs_src/path_params/tutorial005.py hl[17] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *}
#### Get the *enumeration value* { #get-the-enumeration-value }
You can get the actual value (a `str` in this case) using `model_name.value`, or in general, `your_enum_member.value`:
-{* ../../docs_src/path_params/tutorial005.py hl[20] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *}
/// tip
@@ -194,7 +188,7 @@ You can return *enum members* from your *path operation*, even nested in a JSON
They will be converted to their corresponding values (strings in this case) before returning them to the client:
-{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *}
In your client you will get a JSON response like:
@@ -233,7 +227,7 @@ In this case, the name of the parameter is `file_path`, and the last part, `:pat
So, you can use it with:
-{* ../../docs_src/path_params/tutorial004.py hl[6] *}
+{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *}
/// tip
diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md
index adf08a9249..4b8cc9d297 100644
--- a/docs/en/docs/tutorial/query-params-str-validations.md
+++ b/docs/en/docs/tutorial/query-params-str-validations.md
@@ -55,7 +55,7 @@ q: str | None = None
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
q: Union[str, None] = None
@@ -73,7 +73,7 @@ q: Annotated[str | None] = None
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
q: Annotated[Union[str, None]] = None
@@ -206,20 +206,6 @@ If you feel lost with all these **"regular expression"** ideas, don't worry. The
Now you know that whenever you need them you can use them in **FastAPI**.
-### Pydantic v1 `regex` instead of `pattern` { #pydantic-v1-regex-instead-of-pattern }
-
-Before Pydantic version 2 and before FastAPI 0.100.0, the parameter was called `regex` instead of `pattern`, but it's now deprecated.
-
-You could still see some code using it:
-
-//// tab | Pydantic v1
-
-{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *}
-
-////
-
-But know that this is deprecated and it should be updated to use the new parameter `pattern`. 🤓
-
## Default values { #default-values }
You can, of course, use default values other than `None`.
diff --git a/docs/en/docs/tutorial/query-params.md b/docs/en/docs/tutorial/query-params.md
index 2323b83c7c..3c9c225fb0 100644
--- a/docs/en/docs/tutorial/query-params.md
+++ b/docs/en/docs/tutorial/query-params.md
@@ -2,7 +2,7 @@
When you declare other function parameters that are not part of the path parameters, they are automatically interpreted as "query" parameters.
-{* ../../docs_src/query_params/tutorial001.py hl[9] *}
+{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *}
The query is the set of key-value pairs that go after the `?` in a URL, separated by `&` characters.
@@ -128,7 +128,7 @@ If you don't want to add a specific value but just make it optional, set the def
But when you want to make a query parameter required, you can just not declare any default value:
-{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
+{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
Here the query parameter `needy` is a required query parameter of type `str`.
diff --git a/docs/en/docs/tutorial/response-model.md b/docs/en/docs/tutorial/response-model.md
index 5090dbcdfd..b287cdf50a 100644
--- a/docs/en/docs/tutorial/response-model.md
+++ b/docs/en/docs/tutorial/response-model.md
@@ -183,7 +183,7 @@ There might be cases where you return something that is not a valid Pydantic fie
The most common case would be [returning a Response directly as explained later in the advanced docs](../advanced/response-directly.md){.internal-link target=_blank}.
-{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
+{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
This simple case is handled automatically by FastAPI because the return type annotation is the class (or a subclass of) `Response`.
@@ -193,7 +193,7 @@ And tools will also be happy because both `RedirectResponse` and `JSONResponse`
You can also use a subclass of `Response` in the type annotation:
-{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
+{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
This will also work because `RedirectResponse` is a subclass of `Response`, and FastAPI will automatically handle this simple case.
@@ -252,20 +252,6 @@ So, if you send a request to that *path operation* for the item with ID `foo`, t
/// info
-In Pydantic v1 the method was called `.dict()`, it was deprecated (but still supported) in Pydantic v2, and renamed to `.model_dump()`.
-
-The examples here use `.dict()` for compatibility with Pydantic v1, but you should use `.model_dump()` instead if you can use Pydantic v2.
-
-///
-
-/// info
-
-FastAPI uses Pydantic model's `.dict()` with its `exclude_unset` parameter to achieve this.
-
-///
-
-/// info
-
You can also use:
* `response_model_exclude_defaults=True`
diff --git a/docs/en/docs/tutorial/response-status-code.md b/docs/en/docs/tutorial/response-status-code.md
index a2d9757b28..6389592486 100644
--- a/docs/en/docs/tutorial/response-status-code.md
+++ b/docs/en/docs/tutorial/response-status-code.md
@@ -8,7 +8,7 @@ The same way you can specify a response model, you can also declare the HTTP sta
* `@app.delete()`
* etc.
-{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
+{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
/// note
@@ -74,7 +74,7 @@ To know more about each status code and which code is for what, check the Pydantic's docs: Configuration.
+You can use the attribute `model_config` that takes a `dict` as described in Pydantic's docs: Configuration.
You can set `"json_schema_extra"` with a `dict` containing any additional data you would like to show up in the generated JSON Schema, including `examples`.
-////
-
-//// tab | Pydantic v1
-
-In Pydantic version 1, you would use an internal class `Config` and `schema_extra`, as described in Pydantic's docs: Schema customization.
-
-You can set `schema_extra` with a `dict` containing any additional data you would like to show up in the generated JSON Schema, including `examples`.
-
-////
-
/// tip
You could use the same technique to extend the JSON Schema and add your own custom extra info.
diff --git a/docs/en/docs/tutorial/sql-databases.md b/docs/en/docs/tutorial/sql-databases.md
index cfa1c9073f..b42e9ba2f2 100644
--- a/docs/en/docs/tutorial/sql-databases.md
+++ b/docs/en/docs/tutorial/sql-databases.md
@@ -65,7 +65,7 @@ There are a few differences:
* `Field(primary_key=True)` tells SQLModel that the `id` is the **primary key** in the SQL database (you can learn more about SQL primary keys in the SQLModel docs).
- By having the type as `int | None`, SQLModel will know that this column should be an `INTEGER` in the SQL database and that it should be `NULLABLE`.
+ **Note:** We use `int | None` for the primary key field so that in Python code we can *create an object without an `id`* (`id=None`), assuming the database will *generate it when saving*. SQLModel understands that the database will provide the `id` and *defines the column as a non-null `INTEGER`* in the database schema. See SQLModel docs on primary keys for details.
* `Field(index=True)` tells SQLModel that it should create a **SQL index** for this column, that would allow faster lookups in the database when reading data filtered by this column.
diff --git a/docs/en/docs/tutorial/static-files.md b/docs/en/docs/tutorial/static-files.md
index 66b934d4f6..8cf5a5a8ae 100644
--- a/docs/en/docs/tutorial/static-files.md
+++ b/docs/en/docs/tutorial/static-files.md
@@ -7,7 +7,7 @@ You can serve static files automatically from a directory using `StaticFiles`.
* Import `StaticFiles`.
* "Mount" a `StaticFiles()` instance in a specific path.
-{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
+{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *}
/// note | Technical Details
diff --git a/docs/en/docs/tutorial/testing.md b/docs/en/docs/tutorial/testing.md
index 3dcf5dc4ac..f61e62ac54 100644
--- a/docs/en/docs/tutorial/testing.md
+++ b/docs/en/docs/tutorial/testing.md
@@ -30,7 +30,7 @@ Use the `TestClient` object the same way as you do with `httpx`.
Write simple `assert` statements with the standard Python expressions that you need to check (again, standard `pytest`).
-{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
+{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *}
/// tip
@@ -76,7 +76,7 @@ Let's say you have a file structure as described in [Bigger Applications](bigger
In the file `main.py` you have your **FastAPI** app:
-{* ../../docs_src/app_testing/main.py *}
+{* ../../docs_src/app_testing/app_a_py39/main.py *}
### Testing file { #testing-file }
@@ -92,7 +92,7 @@ Then you could have a file `test_main.py` with your tests. It could live on the
Because this file is in the same package, you can use relative imports to import the object `app` from the `main` module (`main.py`):
-{* ../../docs_src/app_testing/test_main.py hl[3] *}
+{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *}
...and have the code for the tests just like before.
@@ -121,63 +121,13 @@ It has a `POST` operation that could return several errors.
Both *path operations* require an `X-Token` header.
-//// tab | Python 3.10+
-
-```Python
-{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
-```
-
-////
-
-//// tab | Python 3.9+
-
-```Python
-{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python
-{!> ../../docs_src/app_testing/app_b_an/main.py!}
-```
-
-////
-
-//// tab | Python 3.10+ non-Annotated
-
-/// tip
-
-Prefer to use the `Annotated` version if possible.
-
-///
-
-```Python
-{!> ../../docs_src/app_testing/app_b_py310/main.py!}
-```
-
-////
-
-//// tab | Python 3.8+ non-Annotated
-
-/// tip
-
-Prefer to use the `Annotated` version if possible.
-
-///
-
-```Python
-{!> ../../docs_src/app_testing/app_b/main.py!}
-```
-
-////
+{* ../../docs_src/app_testing/app_b_an_py310/main.py *}
### Extended testing file { #extended-testing-file }
You could then update `test_main.py` with the extended tests:
-{* ../../docs_src/app_testing/app_b/test_main.py *}
+{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *}
Whenever you need the client to pass information in the request and you don't know how to, you can search (Google) how to do it in `httpx`, or even how to do it with `requests`, as HTTPX's design is based on Requests' design.
diff --git a/docs/en/docs/virtual-environments.md b/docs/en/docs/virtual-environments.md
index e9b0a9fc45..c02e43ab91 100644
--- a/docs/en/docs/virtual-environments.md
+++ b/docs/en/docs/virtual-environments.md
@@ -242,6 +242,26 @@ $ python -m pip install --upgrade pip
lt
+* XWT
+* PSGI
+
+### El abbr da una explicación { #the-abbr-gives-an-explanation }
+
+* clúster
+* Deep Learning
+
+### El abbr da una frase completa y una explicación { #the-abbr-gives-a-full-phrase-and-an-explanation }
+
+* MDN
+* I/O.
+
+////
+
+//// tab | Info
+
+Los atributos "title" de los elementos "abbr" se traducen siguiendo instrucciones específicas.
+
+Las traducciones pueden añadir sus propios elementos "abbr" que el LLM no debe eliminar. P. ej., para explicar palabras en inglés.
+
+Consulta la sección `### HTML abbr elements` en el prompt general en `scripts/translate.py`.
+
+////
+
+## Encabezados { #headings }
+
+//// tab | Prueba
+
+### Desarrolla una webapp - un tutorial { #develop-a-webapp-a-tutorial }
+
+Hola.
+
+### Anotaciones de tipos y -anotaciones { #type-hints-and-annotations }
+
+Hola de nuevo.
+
+### Superclases y subclases { #super-and-subclasses }
+
+Hola de nuevo.
+
+////
+
+//// tab | Info
+
+La única regla estricta para los encabezados es que el LLM deje la parte del hash dentro de llaves sin cambios, lo que asegura que los enlaces no se rompan.
+
+Consulta la sección `### Headings` en el prompt general en `scripts/translate.py`.
+
+Para instrucciones específicas del idioma, mira p. ej. la sección `### Headings` en `docs/de/llm-prompt.md`.
+
+////
+
+## Términos usados en la documentación { #terms-used-in-the-docs }
+
+//// tab | Prueba
+
+* tú
+* tu
+
+* p. ej.
+* etc.
+
+* `foo` como un `int`
+* `bar` como un `str`
+* `baz` como una `list`
+
+* el Tutorial - Guía de usuario
+* la Guía de usuario avanzada
+* la documentación de SQLModel
+* la documentación de la API
+* la documentación automática
+
+* Ciencia de datos
+* Deep Learning
+* Machine Learning
+* Inyección de dependencias
+* autenticación HTTP Basic
+* HTTP Digest
+* formato ISO
+* el estándar JSON Schema
+* el JSON Schema
+* la definición del esquema
+* Flujo de contraseña
+* Móvil
+
+* obsoleto
+* diseñado
+* inválido
+* sobre la marcha
+* estándar
+* por defecto
+* sensible a mayúsculas/minúsculas
+* insensible a mayúsculas/minúsculas
+
+* servir la aplicación
+* servir la página
+
+* la app
+* la aplicación
+
+* la request
+* la response
+* la response de error
+
+* la path operation
+* el decorador de path operation
+* la path operation function
+
+* el body
+* el request body
+* el response body
+* el body JSON
+* el body del formulario
+* el body de archivo
+* el cuerpo de la función
+
+* el parámetro
+* el parámetro del body
+* el parámetro del path
+* el parámetro de query
+* el parámetro de cookie
+* el parámetro de header
+* el parámetro del formulario
+* el parámetro de la función
+
+* el evento
+* el evento de inicio
+* el inicio del servidor
+* el evento de apagado
+* el evento de lifespan
+
+* el manejador
+* el manejador de eventos
+* el manejador de excepciones
+* manejar
+
+* el modelo
+* el modelo de Pydantic
+* el modelo de datos
+* el modelo de base de datos
+* el modelo de formulario
+* el objeto del modelo
+
+* la clase
+* la clase base
+* la clase padre
+* la subclase
+* la clase hija
+* la clase hermana
+* el método de clase
+
+* el header
+* los headers
+* el header de autorización
+* el header `Authorization`
+* el header forwarded
+
+* el sistema de inyección de dependencias
+* la dependencia
+* el dependable
+* el dependiente
+
+* limitado por I/O
+* limitado por CPU
+* concurrencia
+* paralelismo
+* multiprocesamiento
+
+* la env var
+* la variable de entorno
+* el `PATH`
+* la variable `PATH`
+
+* la autenticación
+* el proveedor de autenticación
+* la autorización
+* el formulario de autorización
+* el proveedor de autorización
+* el usuario se autentica
+* el sistema autentica al usuario
+
+* la CLI
+* la interfaz de línea de comandos
+
+* el servidor
+* el cliente
+
+* el proveedor en la nube
+* el servicio en la nube
+
+* el desarrollo
+* las etapas de desarrollo
+
+* el dict
+* el diccionario
+* la enumeración
+* el enum
+* el miembro del enum
+
+* el codificador
+* el decodificador
+* codificar
+* decodificar
+
+* la excepción
+* lanzar
+
+* la expresión
+* el statement
+
+* el frontend
+* el backend
+
+* la discusión de GitHub
+* el issue de GitHub
+
+* el rendimiento
+* la optimización de rendimiento
+
+* el tipo de retorno
+* el valor de retorno
+
+* la seguridad
+* el esquema de seguridad
+
+* la tarea
+* la tarea en segundo plano
+* la función de tarea
+
+* la plantilla
+* el motor de plantillas
+
+* la anotación de tipos
+* las anotaciones de tipos
+
+* el worker del servidor
+* el worker de Uvicorn
+* el Gunicorn Worker
+* el worker process
+* la worker class
+* la carga de trabajo
+
+* el despliegue
+* desplegar
+
+* el SDK
+* el kit de desarrollo de software
+
+* el `APIRouter`
+* el `requirements.txt`
+* el Bearer Token
+* el cambio incompatible
+* el bug
+* el botón
+* el invocable
+* el código
+* el commit
+* el context manager
+* la corrutina
+* la sesión de base de datos
+* el disco
+* el dominio
+* el motor
+* el X falso
+* el método HTTP GET
+* el ítem
+* el paquete
+* el lifespan
+* el lock
+* el middleware
+* la aplicación móvil
+* el módulo
+* el montaje
+* la red
+* el origen
+* el override
+* el payload
+* el procesador
+* la propiedad
+* el proxy
+* el pull request
+* la query
+* la RAM
+* la máquina remota
+* el código de estado
+* el string
+* la etiqueta
+* el framework web
+* el comodín
+* devolver
+* validar
+
+////
+
+//// tab | Info
+
+Esta es una lista no completa y no normativa de términos (mayormente) técnicos vistos en la documentación. Puede ayudar a la persona que diseña el prompt a identificar para qué términos el LLM necesita una mano. Por ejemplo cuando sigue revirtiendo una buena traducción a una traducción subóptima. O cuando tiene problemas conjugando/declinando un término en tu idioma.
+
+Mira p. ej. la sección `### List of English terms and their preferred German translations` en `docs/de/llm-prompt.md`.
+
+////
diff --git a/docs/es/docs/about/index.md b/docs/es/docs/about/index.md
index e83400a8dc..fa152c62db 100644
--- a/docs/es/docs/about/index.md
+++ b/docs/es/docs/about/index.md
@@ -1,3 +1,3 @@
-# Acerca de
+# Acerca de { #about }
Acerca de FastAPI, su diseño, inspiración y más. 🤓
diff --git a/docs/es/docs/advanced/additional-responses.md b/docs/es/docs/advanced/additional-responses.md
index 7788bccd97..d0baa97a45 100644
--- a/docs/es/docs/advanced/additional-responses.md
+++ b/docs/es/docs/advanced/additional-responses.md
@@ -1,4 +1,4 @@
-# Responses Adicionales en OpenAPI
+# Responses Adicionales en OpenAPI { #additional-responses-in-openapi }
/// warning | Advertencia
@@ -14,7 +14,7 @@ Esos responses adicionales se incluirán en el esquema de OpenAPI, por lo que ta
Pero para esos responses adicionales tienes que asegurarte de devolver un `Response` como `JSONResponse` directamente, con tu código de estado y contenido.
-## Response Adicional con `model`
+## Response Adicional con `model` { #additional-response-with-model }
Puedes pasar a tus *decoradores de path operation* un parámetro `responses`.
@@ -26,7 +26,7 @@ Cada uno de esos `dict`s de response puede tener una clave `model`, conteniendo
Por ejemplo, para declarar otro response con un código de estado `404` y un modelo Pydantic `Message`, puedes escribir:
-{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *}
+{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *}
/// note | Nota
@@ -169,13 +169,13 @@ Los esquemas se referencian a otro lugar dentro del esquema de OpenAPI:
}
```
-## Media types adicionales para el response principal
+## Media types adicionales para el response principal { #additional-media-types-for-the-main-response }
Puedes usar este mismo parámetro `responses` para agregar diferentes media type para el mismo response principal.
Por ejemplo, puedes agregar un media type adicional de `image/png`, declarando que tu *path operation* puede devolver un objeto JSON (con media type `application/json`) o una imagen PNG:
-{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *}
+{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
/// note | Nota
@@ -191,25 +191,25 @@ Pero si has especificado una clase de response personalizada con `None` como su
///
-## Combinando información
+## Combinando información { #combining-information }
También puedes combinar información de response de múltiples lugares, incluyendo los parámetros `response_model`, `status_code`, y `responses`.
-Puedes declarar un `response_model`, usando el código de estado predeterminado `200` (o uno personalizado si lo necesitas), y luego declarar información adicional para ese mismo response en `responses`, directamente en el esquema de OpenAPI.
+Puedes declarar un `response_model`, usando el código de estado por defecto `200` (o uno personalizado si lo necesitas), y luego declarar información adicional para ese mismo response en `responses`, directamente en el esquema de OpenAPI.
-**FastAPI** manterá la información adicional de `responses` y la combinará con el JSON Schema de tu modelo.
+**FastAPI** mantendrá la información adicional de `responses` y la combinará con el JSON Schema de tu modelo.
Por ejemplo, puedes declarar un response con un código de estado `404` que usa un modelo Pydantic y tiene una `description` personalizada.
Y un response con un código de estado `200` que usa tu `response_model`, pero incluye un `example` personalizado:
-{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *}
+{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *}
Todo se combinará e incluirá en tu OpenAPI, y se mostrará en la documentación de la API:
-## Combina responses predefinidos y personalizados
+## Combina responses predefinidos y personalizados { #combine-predefined-responses-and-custom-ones }
Es posible que desees tener algunos responses predefinidos que se apliquen a muchas *path operations*, pero que quieras combinarlos con responses personalizados necesarios por cada *path operation*.
@@ -237,9 +237,9 @@ Puedes usar esa técnica para reutilizar algunos responses predefinidos en tus *
Por ejemplo:
-{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
+{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *}
-## Más información sobre responses OpenAPI
+## Más información sobre responses OpenAPI { #more-information-about-openapi-responses }
Para ver exactamente qué puedes incluir en los responses, puedes revisar estas secciones en la especificación OpenAPI:
diff --git a/docs/es/docs/advanced/additional-status-codes.md b/docs/es/docs/advanced/additional-status-codes.md
index df7737aacc..9adfa65cf3 100644
--- a/docs/es/docs/advanced/additional-status-codes.md
+++ b/docs/es/docs/advanced/additional-status-codes.md
@@ -1,10 +1,10 @@
-# Códigos de Estado Adicionales
+# Códigos de Estado Adicionales { #additional-status-codes }
Por defecto, **FastAPI** devolverá los responses usando un `JSONResponse`, colocando el contenido que devuelves desde tu *path operation* dentro de ese `JSONResponse`.
Usará el código de estado por defecto o el que configures en tu *path operation*.
-## Códigos de estado adicionales
+## Códigos de estado adicionales { #additional-status-codes_1 }
Si quieres devolver códigos de estado adicionales aparte del principal, puedes hacerlo devolviendo un `Response` directamente, como un `JSONResponse`, y configurando el código de estado adicional directamente.
@@ -34,7 +34,7 @@ También podrías usar `from starlette.responses import JSONResponse`.
///
-## OpenAPI y documentación de API
+## OpenAPI y documentación de API { #openapi-and-api-docs }
Si devuelves códigos de estado adicionales y responses directamente, no se incluirán en el esquema de OpenAPI (la documentación de la API), porque FastAPI no tiene una forma de saber de antemano qué vas a devolver.
diff --git a/docs/es/docs/advanced/advanced-dependencies.md b/docs/es/docs/advanced/advanced-dependencies.md
index dd3c63a37b..622a2caa26 100644
--- a/docs/es/docs/advanced/advanced-dependencies.md
+++ b/docs/es/docs/advanced/advanced-dependencies.md
@@ -1,6 +1,6 @@
-# Dependencias Avanzadas
+# Dependencias Avanzadas { #advanced-dependencies }
-## Dependencias con parámetros
+## Dependencias con parámetros { #parameterized-dependencies }
Todas las dependencias que hemos visto son una función o clase fija.
@@ -10,7 +10,7 @@ Imaginemos que queremos tener una dependencia que revise si el parámetro de que
Pero queremos poder parametrizar ese contenido fijo.
-## Una *instance* "callable"
+## Una *instance* "callable" { #a-callable-instance }
En Python hay una forma de hacer que una instance de una clase sea un "callable".
@@ -22,7 +22,7 @@ Para hacer eso, declaramos un método `__call__`:
En este caso, este `__call__` es lo que **FastAPI** usará para comprobar parámetros adicionales y sub-dependencias, y es lo que llamará para pasar un valor al parámetro en tu *path operation function* más adelante.
-## Parametrizar la instance
+## Parametrizar la instance { #parameterize-the-instance }
Y ahora, podemos usar `__init__` para declarar los parámetros de la instance que podemos usar para "parametrizar" la dependencia:
@@ -30,7 +30,7 @@ Y ahora, podemos usar `__init__` para declarar los parámetros de la instance qu
En este caso, **FastAPI** nunca tocará ni se preocupará por `__init__`, lo usaremos directamente en nuestro código.
-## Crear una instance
+## Crear una instance { #create-an-instance }
Podríamos crear una instance de esta clase con:
@@ -38,7 +38,7 @@ Podríamos crear una instance de esta clase con:
Y de esa manera podemos "parametrizar" nuestra dependencia, que ahora tiene `"bar"` dentro de ella, como el atributo `checker.fixed_content`.
-## Usar la instance como una dependencia
+## Usar la instance como una dependencia { #use-the-instance-as-a-dependency }
Luego, podríamos usar este `checker` en un `Depends(checker)`, en lugar de `Depends(FixedContentQueryChecker)`, porque la dependencia es la instance, `checker`, no la clase en sí.
@@ -63,3 +63,101 @@ En los capítulos sobre seguridad, hay funciones utilitarias que se implementan
Si entendiste todo esto, ya sabes cómo funcionan por debajo esas herramientas de utilidad para seguridad.
///
+
+## Dependencias con `yield`, `HTTPException`, `except` y Tareas en segundo plano { #dependencies-with-yield-httpexception-except-and-background-tasks }
+
+/// warning | Advertencia
+
+Muy probablemente no necesites estos detalles técnicos.
+
+Estos detalles son útiles principalmente si tenías una aplicación de FastAPI anterior a la 0.121.0 y estás enfrentando problemas con dependencias con `yield`.
+
+///
+
+Las dependencias con `yield` han evolucionado con el tiempo para cubrir diferentes casos de uso y arreglar algunos problemas; aquí tienes un resumen de lo que ha cambiado.
+
+### Dependencias con `yield` y `scope` { #dependencies-with-yield-and-scope }
+
+En la versión 0.121.0, FastAPI agregó soporte para `Depends(scope="function")` para dependencias con `yield`.
+
+Usando `Depends(scope="function")`, el código de salida después de `yield` se ejecuta justo después de que la *path operation function* termina, antes de que la response se envíe de vuelta al cliente.
+
+Y al usar `Depends(scope="request")` (el valor por defecto), el código de salida después de `yield` se ejecuta después de que la response es enviada.
+
+Puedes leer más al respecto en la documentación de [Dependencias con `yield` - Salida temprana y `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope).
+
+### Dependencias con `yield` y `StreamingResponse`, detalles técnicos { #dependencies-with-yield-and-streamingresponse-technical-details }
+
+Antes de FastAPI 0.118.0, si usabas una dependencia con `yield`, ejecutaba el código de salida después de que la *path operation function* retornaba pero justo antes de enviar la response.
+
+La intención era evitar retener recursos por más tiempo del necesario, esperando a que la response viajara por la red.
+
+Este cambio también significaba que si retornabas un `StreamingResponse`, el código de salida de la dependencia con `yield` ya se habría ejecutado.
+
+Por ejemplo, si tenías una sesión de base de datos en una dependencia con `yield`, el `StreamingResponse` no podría usar esa sesión mientras hace streaming de datos porque la sesión ya se habría cerrado en el código de salida después de `yield`.
+
+Este comportamiento se revirtió en la 0.118.0, para hacer que el código de salida después de `yield` se ejecute después de que la response sea enviada.
+
+/// info | Información
+
+Como verás abajo, esto es muy similar al comportamiento anterior a la versión 0.106.0, pero con varias mejoras y arreglos de bugs para casos límite.
+
+///
+
+#### Casos de uso con salida temprana del código { #use-cases-with-early-exit-code }
+
+Hay algunos casos de uso con condiciones específicas que podrían beneficiarse del comportamiento antiguo de ejecutar el código de salida de dependencias con `yield` antes de enviar la response.
+
+Por ejemplo, imagina que tienes código que usa una sesión de base de datos en una dependencia con `yield` solo para verificar un usuario, pero la sesión de base de datos no se vuelve a usar en la *path operation function*, solo en la dependencia, y la response tarda mucho en enviarse, como un `StreamingResponse` que envía datos lentamente, pero que por alguna razón no usa la base de datos.
+
+En este caso, la sesión de base de datos se mantendría hasta que la response termine de enviarse, pero si no la usas, entonces no sería necesario mantenerla.
+
+Así es como se vería:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py *}
+
+El código de salida, el cierre automático de la `Session` en:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+...se ejecutaría después de que la response termine de enviar los datos lentos:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
+
+Pero como `generate_stream()` no usa la sesión de base de datos, no es realmente necesario mantener la sesión abierta mientras se envía la response.
+
+Si tienes este caso de uso específico usando SQLModel (o SQLAlchemy), podrías cerrar explícitamente la sesión después de que ya no la necesites:
+
+{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *}
+
+De esa manera la sesión liberaría la conexión a la base de datos, para que otras requests puedan usarla.
+
+Si tienes un caso de uso diferente que necesite salir temprano desde una dependencia con `yield`, por favor crea una Pregunta de Discusión en GitHub con tu caso de uso específico y por qué te beneficiaría tener cierre temprano para dependencias con `yield`.
+
+Si hay casos de uso convincentes para el cierre temprano en dependencias con `yield`, consideraría agregar una nueva forma de optar por el cierre temprano.
+
+### Dependencias con `yield` y `except`, detalles técnicos { #dependencies-with-yield-and-except-technical-details }
+
+Antes de FastAPI 0.110.0, si usabas una dependencia con `yield`, y luego capturabas una excepción con `except` en esa dependencia, y no volvías a elevar la excepción, la excepción se elevaría/remitiría automáticamente a cualquier manejador de excepciones o al manejador de error interno del servidor.
+
+Esto cambió en la versión 0.110.0 para arreglar consumo de memoria no manejado por excepciones reenviadas sin un manejador (errores internos del servidor), y para hacerlo consistente con el comportamiento del código Python normal.
+
+### Tareas en segundo plano y dependencias con `yield`, detalles técnicos { #background-tasks-and-dependencies-with-yield-technical-details }
+
+Antes de FastAPI 0.106.0, elevar excepciones después de `yield` no era posible, el código de salida en dependencias con `yield` se ejecutaba después de que la response era enviada, por lo que [Manejadores de Excepciones](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} ya habrían corrido.
+
+Esto se diseñó así principalmente para permitir usar los mismos objetos devueltos con `yield` por las dependencias dentro de tareas en segundo plano, porque el código de salida se ejecutaría después de que las tareas en segundo plano terminaran.
+
+Esto cambió en FastAPI 0.106.0 con la intención de no retener recursos mientras se espera a que la response viaje por la red.
+
+/// tip | Consejo
+
+Adicionalmente, una tarea en segundo plano normalmente es un conjunto independiente de lógica que debería manejarse por separado, con sus propios recursos (por ejemplo, su propia conexión a la base de datos).
+
+Así, probablemente tendrás un código más limpio.
+
+///
+
+Si solías depender de este comportamiento, ahora deberías crear los recursos para las tareas en segundo plano dentro de la propia tarea en segundo plano, y usar internamente solo datos que no dependan de los recursos de dependencias con `yield`.
+
+Por ejemplo, en lugar de usar la misma sesión de base de datos, crearías una nueva sesión de base de datos dentro de la tarea en segundo plano, y obtendrías los objetos de la base de datos usando esta nueva sesión. Y entonces, en lugar de pasar el objeto de la base de datos como parámetro a la función de la tarea en segundo plano, pasarías el ID de ese objeto y luego obtendrías el objeto de nuevo dentro de la función de la tarea en segundo plano.
diff --git a/docs/es/docs/advanced/async-tests.md b/docs/es/docs/advanced/async-tests.md
index f89db15335..4627e9bd18 100644
--- a/docs/es/docs/advanced/async-tests.md
+++ b/docs/es/docs/advanced/async-tests.md
@@ -1,4 +1,4 @@
-# Tests Asíncronos
+# Tests Asíncronos { #async-tests }
Ya has visto cómo probar tus aplicaciones de **FastAPI** usando el `TestClient` proporcionado. Hasta ahora, solo has visto cómo escribir tests sincrónicos, sin usar funciones `async`.
@@ -6,11 +6,11 @@ Poder usar funciones asíncronas en tus tests puede ser útil, por ejemplo, cuan
Veamos cómo podemos hacer que esto funcione.
-## pytest.mark.anyio
+## pytest.mark.anyio { #pytest-mark-anyio }
Si queremos llamar funciones asíncronas en nuestros tests, nuestras funciones de test tienen que ser asíncronas. AnyIO proporciona un plugin útil para esto, que nos permite especificar que algunas funciones de test deben ser llamadas de manera asíncrona.
-## HTTPX
+## HTTPX { #httpx }
Incluso si tu aplicación de **FastAPI** usa funciones `def` normales en lugar de `async def`, sigue siendo una aplicación `async` por debajo.
@@ -18,7 +18,7 @@ El `TestClient` hace algo de magia interna para llamar a la aplicación FastAPI
El `TestClient` está basado en HTTPX, y afortunadamente, podemos usarlo directamente para probar la API.
-## Ejemplo
+## Ejemplo { #example }
Para un ejemplo simple, consideremos una estructura de archivos similar a la descrita en [Aplicaciones Más Grandes](../tutorial/bigger-applications.md){.internal-link target=_blank} y [Testing](../tutorial/testing.md){.internal-link target=_blank}:
@@ -32,13 +32,13 @@ Para un ejemplo simple, consideremos una estructura de archivos similar a la des
El archivo `main.py` tendría:
-{* ../../docs_src/async_tests/main.py *}
+{* ../../docs_src/async_tests/app_a_py39/main.py *}
El archivo `test_main.py` tendría los tests para `main.py`, podría verse así ahora:
-{* ../../docs_src/async_tests/test_main.py *}
+{* ../../docs_src/async_tests/app_a_py39/test_main.py *}
-## Ejecútalo
+## Ejecútalo { #run-it }
Puedes ejecutar tus tests como de costumbre vía:
@@ -52,21 +52,21 @@ $ pytest
-## En Detalle
+## En Detalle { #in-detail }
El marcador `@pytest.mark.anyio` le dice a pytest que esta función de test debe ser llamada asíncronamente:
-{* ../../docs_src/async_tests/test_main.py hl[7] *}
+{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *}
/// tip | Consejo
-Note que la función de test ahora es `async def` en lugar de solo `def` como antes al usar el `TestClient`.
+Nota que la función de test ahora es `async def` en lugar de solo `def` como antes al usar el `TestClient`.
///
Luego podemos crear un `AsyncClient` con la app y enviar requests asíncronos a ella, usando `await`.
-{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
+{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *}
Esto es equivalente a:
@@ -88,12 +88,12 @@ Si tu aplicación depende de eventos de lifespan, el `AsyncClient` no activará
///
-## Otras Llamadas a Funciones Asíncronas
+## Otras Llamadas a Funciones Asíncronas { #other-asynchronous-function-calls }
Al ser la función de test asíncrona, ahora también puedes llamar (y `await`) otras funciones `async` además de enviar requests a tu aplicación FastAPI en tus tests, exactamente como las llamarías en cualquier otro lugar de tu código.
/// tip | Consejo
-Si encuentras un `RuntimeError: Task attached to a different loop` al integrar llamadas a funciones asíncronas en tus tests (por ejemplo, cuando usas MotorClient de MongoDB), recuerda crear instances de objetos que necesiten un loop de eventos solo dentro de funciones async, por ejemplo, en un callback `'@app.on_event("startup")`.
+Si encuentras un `RuntimeError: Task attached to a different loop` al integrar llamadas a funciones asíncronas en tus tests (por ejemplo, cuando usas MotorClient de MongoDB), recuerda crear instances de objetos que necesiten un loop de eventos solo dentro de funciones async, por ejemplo, en un callback `@app.on_event("startup")`.
///
diff --git a/docs/es/docs/advanced/behind-a-proxy.md b/docs/es/docs/advanced/behind-a-proxy.md
index 8c590cbe82..f81c16ee81 100644
--- a/docs/es/docs/advanced/behind-a-proxy.md
+++ b/docs/es/docs/advanced/behind-a-proxy.md
@@ -1,6 +1,105 @@
-# Detrás de un Proxy
+# Detrás de un Proxy { #behind-a-proxy }
-En algunas situaciones, podrías necesitar usar un **proxy** como Traefik o Nginx con una configuración que añade un prefijo de path extra que no es visto por tu aplicación.
+En muchas situaciones, usarías un **proxy** como Traefik o Nginx delante de tu app de FastAPI.
+
+Estos proxies podrían manejar certificados HTTPS y otras cosas.
+
+## Headers reenviados por el Proxy { #proxy-forwarded-headers }
+
+Un **proxy** delante de tu aplicación normalmente establecería algunos headers sobre la marcha antes de enviar los requests a tu **server** para que el servidor sepa que el request fue **reenviado** por el proxy, informándole la URL original (pública), incluyendo el dominio, que está usando HTTPS, etc.
+
+El programa **server** (por ejemplo **Uvicorn** a través de **FastAPI CLI**) es capaz de interpretar esos headers, y luego pasar esa información a tu aplicación.
+
+Pero por seguridad, como el server no sabe que está detrás de un proxy confiable, no interpretará esos headers.
+
+/// note | Detalles Técnicos
+
+Los headers del proxy son:
+
+* X-Forwarded-For
+* X-Forwarded-Proto
+* X-Forwarded-Host
+
+///
+
+### Habilitar headers reenviados por el Proxy { #enable-proxy-forwarded-headers }
+
+Puedes iniciar FastAPI CLI con la *Opción de CLI* `--forwarded-allow-ips` y pasar las direcciones IP que deberían ser confiables para leer esos headers reenviados.
+
+Si lo estableces a `--forwarded-allow-ips="*"`, confiaría en todas las IPs entrantes.
+
+Si tu **server** está detrás de un **proxy** confiable y solo el proxy le habla, esto haría que acepte cualquiera que sea la IP de ese **proxy**.
+
+
-## Responses disponibles
+## Responses disponibles { #available-responses }
Aquí hay algunos de los responses disponibles.
@@ -121,7 +121,7 @@ También podrías usar `from starlette.responses import HTMLResponse`.
///
-### `Response`
+### `Response` { #response }
La clase principal `Response`, todos los otros responses heredan de ella.
@@ -136,25 +136,25 @@ Acepta los siguientes parámetros:
FastAPI (de hecho Starlette) incluirá automáticamente un header Content-Length. También incluirá un header Content-Type, basado en el `media_type` y añadiendo un conjunto de caracteres para tipos de texto.
-{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
+{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
-### `HTMLResponse`
+### `HTMLResponse` { #htmlresponse }
Toma algún texto o bytes y devuelve un response HTML, como leíste arriba.
-### `PlainTextResponse`
+### `PlainTextResponse` { #plaintextresponse }
Toma algún texto o bytes y devuelve un response de texto plano.
-{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *}
-### `JSONResponse`
+### `JSONResponse` { #jsonresponse }
Toma algunos datos y devuelve un response codificado como `application/json`.
-Este es el response predeterminado usado en **FastAPI**, como leíste arriba.
+Este es el response usado por defecto en **FastAPI**, como leíste arriba.
-### `ORJSONResponse`
+### `ORJSONResponse` { #orjsonresponse }
Un response JSON rápido alternativo usando `orjson`, como leíste arriba.
@@ -164,7 +164,7 @@ Esto requiere instalar `orjson`, por ejemplo, con `pip install orjson`.
///
-### `UJSONResponse`
+### `UJSONResponse` { #ujsonresponse }
Un response JSON alternativo usando `ujson`.
@@ -180,7 +180,7 @@ Esto requiere instalar `ujson`, por ejemplo, con `pip install ujson`.
///
-{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
+{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *}
/// tip | Consejo
@@ -188,45 +188,45 @@ Es posible que `ORJSONResponse` sea una alternativa más rápida.
///
-### `RedirectResponse`
+### `RedirectResponse` { #redirectresponse }
Devuelve una redirección HTTP. Usa un código de estado 307 (Redirección Temporal) por defecto.
Puedes devolver un `RedirectResponse` directamente:
-{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
+{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *}
---
O puedes usarlo en el parámetro `response_class`:
-{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *}
Si haces eso, entonces puedes devolver la URL directamente desde tu *path operation function*.
-En este caso, el `status_code` utilizado será el predeterminado para `RedirectResponse`, que es `307`.
+En este caso, el `status_code` utilizado será el por defecto para `RedirectResponse`, que es `307`.
---
También puedes usar el parámetro `status_code` combinado con el parámetro `response_class`:
-{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *}
-### `StreamingResponse`
+### `StreamingResponse` { #streamingresponse }
Toma un generador `async` o un generador/iterador normal y transmite el cuerpo del response.
-{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
+{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *}
-#### Usando `StreamingResponse` con objetos similares a archivos
+#### Usando `StreamingResponse` con objetos similares a archivos { #using-streamingresponse-with-file-like-objects }
-Si tienes un objeto similar a un archivo (por ejemplo, el objeto devuelto por `open()`), puedes crear una función generadora para iterar sobre ese objeto similar a un archivo.
+Si tienes un objeto similar a un archivo (por ejemplo, el objeto devuelto por `open()`), puedes crear una función generadora para iterar sobre ese objeto similar a un archivo.
De esa manera, no tienes que leerlo todo primero en memoria, y puedes pasar esa función generadora al `StreamingResponse`, y devolverlo.
Esto incluye muchos paquetes para interactuar con almacenamiento en la nube, procesamiento de video y otros.
-{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *}
+{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *}
1. Esta es la función generadora. Es una "función generadora" porque contiene declaraciones `yield` dentro.
2. Al usar un bloque `with`, nos aseguramos de que el objeto similar a un archivo se cierre después de que la función generadora termine. Así, después de que termina de enviar el response.
@@ -242,7 +242,7 @@ Nota que aquí como estamos usando `open()` estándar que no admite `async` y `a
///
-### `FileResponse`
+### `FileResponse` { #fileresponse }
Transmite un archivo asincrónicamente como response.
@@ -255,15 +255,15 @@ Toma un conjunto diferente de argumentos para crear un instance que los otros ti
Los responses de archivos incluirán los headers apropiados `Content-Length`, `Last-Modified` y `ETag`.
-{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
+{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *}
También puedes usar el parámetro `response_class`:
-{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *}
+{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *}
En este caso, puedes devolver la path del archivo directamente desde tu *path operation* function.
-## Clase de response personalizada
+## Clase de response personalizada { #custom-response-class }
Puedes crear tu propia clase de response personalizada, heredando de `Response` y usándola.
@@ -273,7 +273,7 @@ Digamos que quieres que devuelva JSON con sangría y formato, por lo que quieres
Podrías crear un `CustomORJSONResponse`. Lo principal que tienes que hacer es crear un método `Response.render(content)` que devuelva el contenido como `bytes`:
-{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *}
+{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *}
Ahora en lugar de devolver:
@@ -291,7 +291,7 @@ Ahora en lugar de devolver:
Por supuesto, probablemente encontrarás formas mucho mejores de aprovechar esto que formatear JSON. 😉
-## Clase de response predeterminada
+## Clase de response por defecto { #default-response-class }
Al crear una instance de la clase **FastAPI** o un `APIRouter`, puedes especificar qué clase de response usar por defecto.
@@ -299,7 +299,7 @@ El parámetro que define esto es `default_response_class`.
En el ejemplo a continuación, **FastAPI** usará `ORJSONResponse` por defecto, en todas las *path operations*, en lugar de `JSONResponse`.
-{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *}
+{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *}
/// tip | Consejo
@@ -307,6 +307,6 @@ Todavía puedes sobrescribir `response_class` en *path operations* como antes.
///
-## Documentación adicional
+## Documentación adicional { #additional-documentation }
También puedes declarar el media type y muchos otros detalles en OpenAPI usando `responses`: [Responses Adicionales en OpenAPI](additional-responses.md){.internal-link target=_blank}.
diff --git a/docs/es/docs/advanced/dataclasses.md b/docs/es/docs/advanced/dataclasses.md
index 0ca1fd3b62..3a07482ad1 100644
--- a/docs/es/docs/advanced/dataclasses.md
+++ b/docs/es/docs/advanced/dataclasses.md
@@ -1,10 +1,10 @@
-# Usando Dataclasses
+# Usando Dataclasses { #using-dataclasses }
FastAPI está construido sobre **Pydantic**, y te he estado mostrando cómo usar modelos de Pydantic para declarar requests y responses.
Pero FastAPI también soporta el uso de `dataclasses` de la misma manera:
-{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *}
+{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
Esto sigue siendo soportado gracias a **Pydantic**, ya que tiene soporte interno para `dataclasses`.
@@ -28,11 +28,11 @@ Pero si tienes un montón de dataclasses por ahí, este es un buen truco para us
///
-## Dataclasses en `response_model`
+## Dataclasses en `response_model` { #dataclasses-in-response-model }
También puedes usar `dataclasses` en el parámetro `response_model`:
-{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
+{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
El dataclass será automáticamente convertido a un dataclass de Pydantic.
@@ -40,7 +40,7 @@ De esta manera, su esquema aparecerá en la interfaz de usuario de la documentac
-## Dataclasses en Estructuras de Datos Anidadas
+## Dataclasses en Estructuras de Datos Anidadas { #dataclasses-in-nested-data-structures }
También puedes combinar `dataclasses` con otras anotaciones de tipos para crear estructuras de datos anidadas.
@@ -48,7 +48,7 @@ En algunos casos, todavía podrías tener que usar la versión de `dataclasses`
En ese caso, simplemente puedes intercambiar los `dataclasses` estándar con `pydantic.dataclasses`, que es un reemplazo directo:
-{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *}
+{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. Todavía importamos `field` de los `dataclasses` estándar.
@@ -64,7 +64,7 @@ En ese caso, simplemente puedes intercambiar los `dataclasses` estándar con `py
6. Aquí estamos regresando un diccionario que contiene `items`, que es una lista de dataclasses.
- FastAPI todavía es capaz de serializar los datos a JSON.
+ FastAPI todavía es capaz de serializar los datos a JSON.
7. Aquí el `response_model` está usando una anotación de tipo de una lista de dataclasses `Author`.
@@ -84,12 +84,12 @@ Puedes combinar `dataclasses` con otras anotaciones de tipos en muchas combinaci
Revisa las anotaciones en el código arriba para ver más detalles específicos.
-## Aprende Más
+## Aprende Más { #learn-more }
También puedes combinar `dataclasses` con otros modelos de Pydantic, heredar de ellos, incluirlos en tus propios modelos, etc.
Para saber más, revisa la documentación de Pydantic sobre dataclasses.
-## Versión
+## Versión { #version }
Esto está disponible desde la versión `0.67.0` de FastAPI. 🔖
diff --git a/docs/es/docs/advanced/events.md b/docs/es/docs/advanced/events.md
index a33b517910..c2002a6f53 100644
--- a/docs/es/docs/advanced/events.md
+++ b/docs/es/docs/advanced/events.md
@@ -1,4 +1,4 @@
-# Eventos de Lifespan
+# Eventos de Lifespan { #lifespan-events }
Puedes definir lógica (código) que debería ser ejecutada antes de que la aplicación **inicie**. Esto significa que este código será ejecutado **una vez**, **antes** de que la aplicación **comience a recibir requests**.
@@ -8,7 +8,7 @@ Debido a que este código se ejecuta antes de que la aplicación **comience** a
Esto puede ser muy útil para configurar **recursos** que necesitas usar para toda la app, y que son **compartidos** entre requests, y/o que necesitas **limpiar** después. Por ejemplo, un pool de conexiones a una base de datos, o cargando un modelo de machine learning compartido.
-## Caso de Uso
+## Caso de Uso { #use-case }
Empecemos con un ejemplo de **caso de uso** y luego veamos cómo resolverlo con esto.
@@ -22,7 +22,7 @@ Podrías cargarlo en el nivel superior del módulo/archivo, pero eso también si
Eso es lo que resolveremos, vamos a cargar el modelo antes de que los requests sean manejados, pero solo justo antes de que la aplicación comience a recibir requests, no mientras el código se está cargando.
-## Lifespan
+## Lifespan { #lifespan }
Puedes definir esta lógica de *startup* y *shutdown* usando el parámetro `lifespan` de la app de `FastAPI`, y un "context manager" (te mostraré lo que es en un momento).
@@ -30,7 +30,7 @@ Comencemos con un ejemplo y luego veámoslo en detalle.
Creamos una función asíncrona `lifespan()` con `yield` así:
-{* ../../docs_src/events/tutorial003.py hl[16,19] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *}
Aquí estamos simulando la operación costosa de *startup* de cargar el modelo poniendo la función del (falso) modelo en el diccionario con modelos de machine learning antes del `yield`. Este código será ejecutado **antes** de que la aplicación **comience a tomar requests**, durante el *startup*.
@@ -44,25 +44,25 @@ Quizás necesites iniciar una nueva versión, o simplemente te cansaste de ejecu
///
-### Función de Lifespan
+### Función de Lifespan { #lifespan-function }
Lo primero que hay que notar es que estamos definiendo una función asíncrona con `yield`. Esto es muy similar a las Dependencias con `yield`.
-{* ../../docs_src/events/tutorial003.py hl[14:19] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *}
La primera parte de la función, antes del `yield`, será ejecutada **antes** de que la aplicación comience.
Y la parte después del `yield` será ejecutada **después** de que la aplicación haya terminado.
-### Async Context Manager
+### Async Context Manager { #async-context-manager }
Si revisas, la función está decorada con un `@asynccontextmanager`.
Eso convierte a la función en algo llamado un "**async context manager**".
-{* ../../docs_src/events/tutorial003.py hl[1,13] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *}
-Un **context manager** en Python es algo que puedes usar en una declaración `with`, por ejemplo, `open()` puede ser usado como un context manager:
+Un **context manager** en Python es algo que puedes usar en un statement `with`, por ejemplo, `open()` puede ser usado como un context manager:
```Python
with open("file.txt") as file:
@@ -82,9 +82,9 @@ En nuestro ejemplo de código arriba, no lo usamos directamente, pero se lo pasa
El parámetro `lifespan` de la app de `FastAPI` toma un **async context manager**, por lo que podemos pasar nuestro nuevo `lifespan` async context manager a él.
-{* ../../docs_src/events/tutorial003.py hl[22] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[22] *}
-## Eventos Alternativos (obsoleto)
+## Eventos Alternativos (obsoleto) { #alternative-events-deprecated }
/// warning | Advertencia
@@ -100,11 +100,11 @@ Puedes definir manejadores de eventos (funciones) que necesitan ser ejecutadas a
Estas funciones pueden ser declaradas con `async def` o `def` normal.
-### Evento `startup`
+### Evento `startup` { #startup-event }
Para añadir una función que debería ejecutarse antes de que la aplicación inicie, declárala con el evento `"startup"`:
-{* ../../docs_src/events/tutorial001.py hl[8] *}
+{* ../../docs_src/events/tutorial001_py39.py hl[8] *}
En este caso, la función manejadora del evento `startup` inicializará los ítems de la "base de datos" (solo un `dict`) con algunos valores.
@@ -112,11 +112,11 @@ Puedes añadir más de un manejador de eventos.
Y tu aplicación no comenzará a recibir requests hasta que todos los manejadores de eventos `startup` hayan completado.
-### Evento `shutdown`
+### Evento `shutdown` { #shutdown-event }
Para añadir una función que debería ejecutarse cuando la aplicación se esté cerrando, declárala con el evento `"shutdown"`:
-{* ../../docs_src/events/tutorial002.py hl[6] *}
+{* ../../docs_src/events/tutorial002_py39.py hl[6] *}
Aquí, la función manejadora del evento `shutdown` escribirá una línea de texto `"Application shutdown"` a un archivo `log.txt`.
@@ -138,7 +138,7 @@ Por eso, declaramos la función manejadora del evento con `def` estándar en vez
///
-### `startup` y `shutdown` juntos
+### `startup` y `shutdown` juntos { #startup-and-shutdown-together }
Hay una gran posibilidad de que la lógica para tu *startup* y *shutdown* esté conectada, podrías querer iniciar algo y luego finalizarlo, adquirir un recurso y luego liberarlo, etc.
@@ -146,7 +146,7 @@ Hacer eso en funciones separadas que no comparten lógica o variables juntas es
Debido a eso, ahora se recomienda en su lugar usar el `lifespan` como se explicó arriba.
-## Detalles Técnicos
+## Detalles Técnicos { #technical-details }
Solo un detalle técnico para los nerds curiosos. 🤓
@@ -160,6 +160,6 @@ Incluyendo cómo manejar el estado de lifespan que puede ser usado en otras áre
///
-## Sub Aplicaciones
+## Sub Aplicaciones { #sub-applications }
🚨 Ten en cuenta que estos eventos de lifespan (startup y shutdown) solo serán ejecutados para la aplicación principal, no para [Sub Aplicaciones - Mounts](sub-applications.md){.internal-link target=_blank}.
diff --git a/docs/es/docs/advanced/generate-clients.md b/docs/es/docs/advanced/generate-clients.md
index b664bceacf..daf6cefed3 100644
--- a/docs/es/docs/advanced/generate-clients.md
+++ b/docs/es/docs/advanced/generate-clients.md
@@ -1,115 +1,76 @@
-# Genera Clientes
+# Generando SDKs { #generating-sdks }
-Como **FastAPI** está basado en la especificación OpenAPI, obtienes compatibilidad automática con muchas herramientas, incluyendo la documentación automática de la API (proporcionada por Swagger UI).
+Como **FastAPI** está basado en la especificación **OpenAPI**, sus APIs se pueden describir en un formato estándar que muchas herramientas entienden.
-Una ventaja particular que no es necesariamente obvia es que puedes **generar clientes** (a veces llamados **SDKs** ) para tu API, para muchos **lenguajes de programación** diferentes.
+Esto facilita generar **documentación** actualizada, paquetes de cliente (**SDKs**) en múltiples lenguajes y **escribir pruebas** o **flujos de automatización** que se mantengan sincronizados con tu código.
-## Generadores de Clientes OpenAPI
+En esta guía, aprenderás a generar un **SDK de TypeScript** para tu backend con FastAPI.
-Hay muchas herramientas para generar clientes desde **OpenAPI**.
+## Generadores de SDKs de código abierto { #open-source-sdk-generators }
-Una herramienta común es OpenAPI Generator.
+Una opción versátil es el OpenAPI Generator, que soporta **muchos lenguajes de programación** y puede generar SDKs a partir de tu especificación OpenAPI.
-Si estás construyendo un **frontend**, una alternativa muy interesante es openapi-ts.
+Para **clientes de TypeScript**, Hey API es una solución diseñada específicamente, que ofrece una experiencia optimizada para el ecosistema de TypeScript.
-## Generadores de Clientes y SDKs - Sponsor
+Puedes descubrir más generadores de SDK en OpenAPI.Tools.
-También hay algunos generadores de Clientes y SDKs **respaldados por empresas** basados en OpenAPI (FastAPI), en algunos casos pueden ofrecerte **funcionalidades adicionales** además de SDKs/clientes generados de alta calidad.
+/// tip | Consejo
-Algunos de ellos también ✨ [**sponsorean FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, esto asegura el **desarrollo** continuo y saludable de FastAPI y su **ecosistema**.
+FastAPI genera automáticamente especificaciones **OpenAPI 3.1**, así que cualquier herramienta que uses debe soportar esta versión.
-Y muestra su verdadero compromiso con FastAPI y su **comunidad** (tú), ya que no solo quieren proporcionarte un **buen servicio** sino también asegurarse de que tengas un **buen y saludable framework**, FastAPI. 🙇
+///
+
+## Generadores de SDKs de sponsors de FastAPI { #sdk-generators-from-fastapi-sponsors }
+
+Esta sección destaca soluciones **respaldadas por empresas** y **venture-backed** de compañías que sponsorean FastAPI. Estos productos ofrecen **funcionalidades adicionales** e **integraciones** además de SDKs generados de alta calidad.
+
+Al ✨ [**sponsorear FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, estas compañías ayudan a asegurar que el framework y su **ecosistema** se mantengan saludables y **sustentables**.
+
+Su sponsorship también demuestra un fuerte compromiso con la **comunidad** de FastAPI (tú), mostrando que no solo les importa ofrecer un **gran servicio**, sino también apoyar un **framework robusto y próspero**, FastAPI. 🙇
Por ejemplo, podrías querer probar:
* Speakeasy
-* Stainless
-* liblab
+* Stainless
+* liblab
-También hay varias otras empresas que ofrecen servicios similares que puedes buscar y encontrar en línea. 🤓
+Algunas de estas soluciones también pueden ser open source u ofrecer niveles gratuitos, así que puedes probarlas sin un compromiso financiero. Hay otros generadores de SDK comerciales disponibles y se pueden encontrar en línea. 🤓
-## Genera un Cliente Frontend en TypeScript
+## Crea un SDK de TypeScript { #create-a-typescript-sdk }
Empecemos con una aplicación simple de FastAPI:
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
-Nota que las *path operations* definen los modelos que usan para el payload de la petición y el payload del response, usando los modelos `Item` y `ResponseMessage`.
+Nota que las *path operations* definen los modelos que usan para el payload del request y el payload del response, usando los modelos `Item` y `ResponseMessage`.
-### Documentación de la API
+### Documentación de la API { #api-docs }
-Si vas a la documentación de la API, verás que tiene los **esquemas** para los datos que se enviarán en las peticiones y se recibirán en los responses:
+Si vas a `/docs`, verás que tiene los **esquemas** para los datos a enviar en requests y recibir en responses:
-Puedes ver esos esquemas porque fueron declarados con los modelos en la aplicación.
+Puedes ver esos esquemas porque fueron declarados con los modelos en la app.
-Esa información está disponible en el **JSON Schema** de OpenAPI de la aplicación, y luego se muestra en la documentación de la API (por Swagger UI).
+Esa información está disponible en el **OpenAPI schema** de la app, y luego se muestra en la documentación de la API.
Y esa misma información de los modelos que está incluida en OpenAPI es lo que puede usarse para **generar el código del cliente**.
-### Genera un Cliente en TypeScript
+### Hey API { #hey-api }
-Ahora que tenemos la aplicación con los modelos, podemos generar el código del cliente para el frontend.
+Una vez que tenemos una app de FastAPI con los modelos, podemos usar Hey API para generar un cliente de TypeScript. La forma más rápida de hacerlo es con npx.
-#### Instalar `openapi-ts`
-
-Puedes instalar `openapi-ts` en tu código de frontend con:
-
-
@@ -131,17 +92,17 @@ El objeto de response también tendrá autocompletado:
-## App de FastAPI con Tags
+## App de FastAPI con tags { #fastapi-app-with-tags }
-En muchos casos tu aplicación de FastAPI será más grande, y probablemente usarás tags para separar diferentes grupos de *path operations*.
+En muchos casos tu app de FastAPI será más grande, y probablemente usarás tags para separar diferentes grupos de *path operations*.
-Por ejemplo, podrías tener una sección para **items** y otra sección para **usuarios**, y podrían estar separadas por tags:
+Por ejemplo, podrías tener una sección para **items** y otra sección para **users**, y podrían estar separadas por tags:
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *}
-### Genera un Cliente TypeScript con Tags
+### Genera un Cliente TypeScript con tags { #generate-a-typescript-client-with-tags }
-Si generas un cliente para una aplicación de FastAPI usando tags, normalmente también separará el código del cliente basándose en los tags.
+Si generas un cliente para una app de FastAPI usando tags, normalmente también separará el código del cliente basándose en los tags.
De esta manera podrás tener las cosas ordenadas y agrupadas correctamente para el código del cliente:
@@ -152,7 +113,7 @@ En este caso tienes:
* `ItemsService`
* `UsersService`
-### Nombres de los Métodos del Cliente
+### Nombres de los métodos del cliente { #client-method-names }
Ahora mismo los nombres de los métodos generados como `createItemItemsPost` no se ven muy limpios:
@@ -166,15 +127,15 @@ OpenAPI requiere que cada operation ID sea único a través de todas las *path o
Pero te mostraré cómo mejorar eso a continuación. 🤓
-## Operation IDs Personalizados y Mejores Nombres de Métodos
+## Operation IDs personalizados y mejores nombres de métodos { #custom-operation-ids-and-better-method-names }
Puedes **modificar** la forma en que estos operation IDs son **generados** para hacerlos más simples y tener **nombres de métodos más simples** en los clientes.
En este caso tendrás que asegurarte de que cada operation ID sea **único** de alguna otra manera.
-Por ejemplo, podrías asegurarte de que cada *path operation* tenga un tag, y luego generar el operation ID basado en el **tag** y el nombre de la *path operation* **name** (el nombre de la función).
+Por ejemplo, podrías asegurarte de que cada *path operation* tenga un tag, y luego generar el operation ID basado en el **tag** y el **name** de la *path operation* (el nombre de la función).
-### Función Personalizada para Generar ID Único
+### Función personalizada para generar ID único { #custom-generate-unique-id-function }
FastAPI usa un **ID único** para cada *path operation*, se usa para el **operation ID** y también para los nombres de cualquier modelo personalizado necesario, para requests o responses.
@@ -186,15 +147,15 @@ Puedes entonces pasar esa función personalizada a **FastAPI** como el parámetr
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *}
-### Generar un Cliente TypeScript con Operation IDs Personalizados
+### Genera un Cliente TypeScript con operation IDs personalizados { #generate-a-typescript-client-with-custom-operation-ids }
-Ahora si generas el cliente de nuevo, verás que tiene los nombres de métodos mejorados:
+Ahora, si generas el cliente de nuevo, verás que tiene los nombres de métodos mejorados:
Como ves, los nombres de métodos ahora tienen el tag y luego el nombre de la función, ahora no incluyen información del path de la URL y la operación HTTP.
-### Preprocesa la Especificación OpenAPI para el Generador de Clientes
+### Preprocesa la especificación OpenAPI para el generador de clientes { #preprocess-the-openapi-specification-for-the-client-generator }
El código generado aún tiene algo de **información duplicada**.
@@ -206,7 +167,7 @@ Pero para el cliente generado podríamos **modificar** los operation IDs de Open
Podríamos descargar el JSON de OpenAPI a un archivo `openapi.json` y luego podríamos **remover ese tag prefijado** con un script como este:
-{* ../../docs_src/generate_clients/tutorial004.py *}
+{* ../../docs_src/generate_clients/tutorial004_py39.py *}
//// tab | Node.js
@@ -218,44 +179,30 @@ Podríamos descargar el JSON de OpenAPI a un archivo `openapi.json` y luego podr
Con eso, los operation IDs serían renombrados de cosas como `items-get_items` a solo `get_items`, de esa manera el generador del cliente puede generar nombres de métodos más simples.
-### Generar un Cliente TypeScript con el OpenAPI Preprocesado
+### Genera un Cliente TypeScript con el OpenAPI preprocesado { #generate-a-typescript-client-with-the-preprocessed-openapi }
-Ahora como el resultado final está en un archivo `openapi.json`, modificarías el `package.json` para usar ese archivo local, por ejemplo:
+Como el resultado final ahora está en un archivo `openapi.json`, necesitas actualizar la ubicación de la entrada:
-```JSON hl_lines="7"
-{
- "name": "frontend-app",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios"
- },
- "author": "",
- "license": "",
- "devDependencies": {
- "@hey-api/openapi-ts": "^0.27.38",
- "typescript": "^4.6.2"
- }
-}
+```sh
+npx @hey-api/openapi-ts -i ./openapi.json -o src/client
```
Después de generar el nuevo cliente, ahora tendrías nombres de métodos **limpios**, con todo el **autocompletado**, **errores en línea**, etc:
-## Beneficios
+## Beneficios { #benefits }
-Cuando usas los clientes generados automáticamente obtendrás **autocompletado** para:
+Cuando uses los clientes generados automáticamente obtendrás **autocompletado** para:
* Métodos.
-* Payloads de peticiones en el cuerpo, parámetros de query, etc.
-* Payloads de responses.
+* Payloads de request en el body, parámetros de query, etc.
+* Payloads de response.
También tendrás **errores en línea** para todo.
Y cada vez que actualices el código del backend, y **regeneres** el frontend, tendrás las nuevas *path operations* disponibles como métodos, las antiguas eliminadas, y cualquier otro cambio se reflejará en el código generado. 🤓
-Esto también significa que si algo cambió será **reflejado** automáticamente en el código del cliente. Y si haces **build** del cliente, te dará error si tienes algún **desajuste** en los datos utilizados.
+Esto también significa que si algo cambió será **reflejado** automáticamente en el código del cliente. Y si haces **build** del cliente, dará error si tienes algún **desajuste** en los datos utilizados.
Así que, **detectarás muchos errores** muy temprano en el ciclo de desarrollo en lugar de tener que esperar a que los errores se muestren a tus usuarios finales en producción para luego intentar depurar dónde está el problema. ✨
diff --git a/docs/es/docs/advanced/index.md b/docs/es/docs/advanced/index.md
index 0626a1563a..f3f4bb85ce 100644
--- a/docs/es/docs/advanced/index.md
+++ b/docs/es/docs/advanced/index.md
@@ -1,6 +1,6 @@
-# Guía avanzada del usuario
+# Guía avanzada del usuario { #advanced-user-guide }
-## Funcionalidades adicionales
+## Funcionalidades adicionales { #additional-features }
El [Tutorial - Guía del usuario](../tutorial/index.md){.internal-link target=_blank} principal debería ser suficiente para darte un recorrido por todas las funcionalidades principales de **FastAPI**.
@@ -14,23 +14,8 @@ Y es posible que para tu caso de uso, la solución esté en una de ellas.
///
-## Lee primero el Tutorial
+## Lee primero el Tutorial { #read-the-tutorial-first }
Aún podrías usar la mayoría de las funcionalidades en **FastAPI** con el conocimiento del [Tutorial - Guía del usuario](../tutorial/index.md){.internal-link target=_blank} principal.
Y las siguientes secciones asumen que ya lo leíste y que conoces esas ideas principales.
-
-## Cursos externos
-
-Aunque el [Tutorial - Guía del usuario](../tutorial/index.md){.internal-link target=_blank} y esta **Guía avanzada del usuario** están escritos como un tutorial guiado (como un libro) y deberían ser suficientes para que **aprendas FastAPI**, podrías querer complementarlo con cursos adicionales.
-
-O podría ser que simplemente prefieras tomar otros cursos porque se adaptan mejor a tu estilo de aprendizaje.
-
-Algunos proveedores de cursos ✨ [**sponsorean FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, esto asegura el desarrollo continuo y saludable de FastAPI y su **ecosistema**.
-
-Y muestra su verdadero compromiso con FastAPI y su **comunidad** (tú), ya que no solo quieren brindarte una **buena experiencia de aprendizaje** sino que también quieren asegurarse de que tengas un **buen y saludable framework**, FastAPI. 🙇
-
-Podrías querer probar sus cursos:
-
-* Talk Python Training
-* Desarrollo guiado por pruebas
diff --git a/docs/es/docs/advanced/middleware.md b/docs/es/docs/advanced/middleware.md
index 0c8c44b882..7eead8ae14 100644
--- a/docs/es/docs/advanced/middleware.md
+++ b/docs/es/docs/advanced/middleware.md
@@ -1,4 +1,4 @@
-# Middleware Avanzado
+# Middleware Avanzado { #advanced-middleware }
En el tutorial principal leíste cómo agregar [Middleware Personalizado](../tutorial/middleware.md){.internal-link target=_blank} a tu aplicación.
@@ -6,9 +6,9 @@ Y luego también leíste cómo manejar [CORS con el `CORSMiddleware`](../tutoria
En esta sección veremos cómo usar otros middlewares.
-## Agregando middlewares ASGI
+## Agregando middlewares ASGI { #adding-asgi-middlewares }
-Como **FastAPI** está basado en Starlette e implementa la especificación ASGI, puedes usar cualquier middleware ASGI.
+Como **FastAPI** está basado en Starlette e implementa la especificación ASGI, puedes usar cualquier middleware ASGI.
Un middleware no tiene que estar hecho para FastAPI o Starlette para funcionar, siempre que siga la especificación ASGI.
@@ -39,7 +39,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow")
`app.add_middleware()` recibe una clase de middleware como primer argumento y cualquier argumento adicional que se le quiera pasar al middleware.
-## Middlewares integrados
+## Middlewares integrados { #integrated-middlewares }
**FastAPI** incluye varios middlewares para casos de uso común, veremos a continuación cómo usarlos.
@@ -51,40 +51,41 @@ Para los próximos ejemplos, también podrías usar `from starlette.middleware.s
///
-## `HTTPSRedirectMiddleware`
+## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware }
Impone que todas las requests entrantes deben ser `https` o `wss`.
Cualquier request entrante a `http` o `ws` será redirigida al esquema seguro.
-{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *}
+{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *}
-## `TrustedHostMiddleware`
+## `TrustedHostMiddleware` { #trustedhostmiddleware }
Impone que todas las requests entrantes tengan correctamente configurado el header `Host`, para proteger contra ataques de HTTP Host Header.
-{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *}
+{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *}
Se soportan los siguientes argumentos:
* `allowed_hosts` - Una list de nombres de dominio que deberían ser permitidos como nombres de host. Se soportan dominios comodín como `*.example.com` para hacer coincidir subdominios. Para permitir cualquier nombre de host, usa `allowed_hosts=["*"]` u omite el middleware.
+* `www_redirect` - Si se establece en True, las requests a versiones sin www de los hosts permitidos serán redirigidas a sus equivalentes con www. Por defecto es `True`.
Si una request entrante no se valida correctamente, se enviará un response `400`.
-## `GZipMiddleware`
+## `GZipMiddleware` { #gzipmiddleware }
Maneja responses GZip para cualquier request que incluya `"gzip"` en el header `Accept-Encoding`.
El middleware manejará tanto responses estándar como en streaming.
-{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *}
+{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *}
Se soportan los siguientes argumentos:
* `minimum_size` - No comprimir con GZip responses que sean más pequeñas que este tamaño mínimo en bytes. Por defecto es `500`.
* `compresslevel` - Usado durante la compresión GZip. Es un entero que varía de 1 a 9. Por defecto es `9`. Un valor más bajo resulta en una compresión más rápida pero archivos más grandes, mientras que un valor más alto resulta en una compresión más lenta pero archivos más pequeños.
-## Otros middlewares
+## Otros middlewares { #other-middlewares }
Hay muchos otros middlewares ASGI.
diff --git a/docs/es/docs/advanced/openapi-callbacks.md b/docs/es/docs/advanced/openapi-callbacks.md
index 60d5cb3cc9..caaa70fa89 100644
--- a/docs/es/docs/advanced/openapi-callbacks.md
+++ b/docs/es/docs/advanced/openapi-callbacks.md
@@ -1,18 +1,18 @@
-# OpenAPI Callbacks
+# Callbacks de OpenAPI { #openapi-callbacks }
Podrías crear una API con una *path operation* que podría desencadenar un request a una *API externa* creada por alguien más (probablemente el mismo desarrollador que estaría *usando* tu API).
-El proceso que ocurre cuando tu aplicación API llama a la *API externa* se llama un "callback". Porque el software que escribió el desarrollador externo envía un request a tu API y luego tu API *responde*, enviando un request a una *API externa* (que probablemente fue creada por el mismo desarrollador).
+El proceso que ocurre cuando tu aplicación API llama a la *API externa* se llama un "callback". Porque el software que escribió el desarrollador externo envía un request a tu API y luego tu API hace un *callback*, enviando un request a una *API externa* (que probablemente fue creada por el mismo desarrollador).
En este caso, podrías querer documentar cómo esa API externa *debería* verse. Qué *path operation* debería tener, qué cuerpo debería esperar, qué response debería devolver, etc.
-## Una aplicación con callbacks
+## Una aplicación con callbacks { #an-app-with-callbacks }
Veamos todo esto con un ejemplo.
Imagina que desarrollas una aplicación que permite crear facturas.
-Estas facturas tendrán un `id`, `title` (opcional), `customer`, y `total`.
+Estas facturas tendrán un `id`, `title` (opcional), `customer` y `total`.
El usuario de tu API (un desarrollador externo) creará una factura en tu API con un request POST.
@@ -23,15 +23,15 @@ Luego tu API (imaginemos):
* Enviará una notificación de vuelta al usuario de la API (el desarrollador externo).
* Esto se hará enviando un request POST (desde *tu API*) a alguna *API externa* proporcionada por ese desarrollador externo (este es el "callback").
-## La aplicación normal de **FastAPI**
+## La aplicación normal de **FastAPI** { #the-normal-fastapi-app }
-Primero veamos cómo sería la aplicación API normal antes de agregar el callback.
+Primero veamos cómo se vería la aplicación API normal antes de agregar el callback.
Tendrá una *path operation* que recibirá un cuerpo `Invoice`, y un parámetro de query `callback_url` que contendrá la URL para el callback.
Esta parte es bastante normal, probablemente ya estés familiarizado con la mayor parte del código:
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *}
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
/// tip | Consejo
@@ -39,9 +39,9 @@ El parámetro de query `callback_url` utiliza un tipo expresión OpenAPI 3 (ver más abajo) donde puede usar variables con parámetros y partes del request original enviado a *tu API*.
-### La expresión del path del callback
+### La expresión del path del callback { #the-callback-path-expression }
El *path* del callback puede tener una expresión OpenAPI 3 que puede contener partes del request original enviado a *tu API*.
@@ -134,7 +134,7 @@ con un JSON body de:
}
```
-luego *tu API* procesará la factura, y en algún momento después, enviará un request de callback al `callback_url` (la *API externa*):
+luego *tu API* procesará la factura y, en algún momento después, enviará un request de callback al `callback_url` (la *API externa*):
```
https://www.external.org/events/invoices/2expen51ve
@@ -163,13 +163,13 @@ Observa cómo la URL del callback utilizada contiene la URL recibida como parám
///
-### Agregar el router de callback
+### Agrega el router de callback { #add-the-callback-router }
-En este punto tienes las *path operation(s)* del callback necesarias (las que el *desarrollador externo* debería implementar en la *API externa*) en el router de callback que creaste antes.
+En este punto tienes las *path operation(s)* del callback necesarias (las que el *desarrollador externo* debería implementar en la *API externa*) en el router de callback que creaste arriba.
Ahora usa el parámetro `callbacks` en el *decorador de path operation de tu API* para pasar el atributo `.routes` (que en realidad es solo un `list` de rutas/*path operations*) de ese router de callback:
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *}
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
/// tip | Consejo
@@ -177,7 +177,7 @@ Observa que no estás pasando el router en sí (`invoices_callback_router`) a `c
///
-### Revisa la documentación
+### Revisa la documentación { #check-the-docs }
Ahora puedes iniciar tu aplicación e ir a http://127.0.0.1:8000/docs.
diff --git a/docs/es/docs/advanced/openapi-webhooks.md b/docs/es/docs/advanced/openapi-webhooks.md
index 01235b3ab9..c358a14008 100644
--- a/docs/es/docs/advanced/openapi-webhooks.md
+++ b/docs/es/docs/advanced/openapi-webhooks.md
@@ -1,4 +1,4 @@
-# Webhooks de OpenAPI
+# Webhooks de OpenAPI { #openapi-webhooks }
Hay casos donde quieres decirle a los **usuarios** de tu API que tu aplicación podría llamar a *su* aplicación (enviando una request) con algunos datos, normalmente para **notificar** de algún tipo de **evento**.
@@ -6,7 +6,7 @@ Esto significa que en lugar del proceso normal de tus usuarios enviando requests
Esto normalmente se llama un **webhook**.
-## Pasos de los webhooks
+## Pasos de los webhooks { #webhooks-steps }
El proceso normalmente es que **tú defines** en tu código cuál es el mensaje que enviarás, el **body de la request**.
@@ -16,7 +16,7 @@ Y **tus usuarios** definen de alguna manera (por ejemplo en un panel web en alg
Toda la **lógica** sobre cómo registrar los URLs para webhooks y el código para realmente enviar esas requests depende de ti. Lo escribes como quieras en **tu propio código**.
-## Documentando webhooks con **FastAPI** y OpenAPI
+## Documentando webhooks con **FastAPI** y OpenAPI { #documenting-webhooks-with-fastapi-and-openapi }
Con **FastAPI**, usando OpenAPI, puedes definir los nombres de estos webhooks, los tipos de operaciones HTTP que tu aplicación puede enviar (por ejemplo, `POST`, `PUT`, etc.) y los **bodies** de las requests que tu aplicación enviaría.
@@ -28,11 +28,11 @@ Los webhooks están disponibles en OpenAPI 3.1.0 y superiores, soportados por Fa
///
-## Una aplicación con webhooks
+## Una aplicación con webhooks { #an-app-with-webhooks }
Cuando creas una aplicación de **FastAPI**, hay un atributo `webhooks` que puedes usar para definir *webhooks*, de la misma manera que definirías *path operations*, por ejemplo con `@app.webhooks.post()`.
-{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *}
+{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *}
Los webhooks que defines terminarán en el esquema de **OpenAPI** y en la interfaz automática de **documentación**.
@@ -46,7 +46,7 @@ Nota que con los webhooks en realidad no estás declarando un *path* (como `/ite
Esto es porque se espera que **tus usuarios** definan el actual **URL path** donde quieren recibir la request del webhook de alguna otra manera (por ejemplo, un panel web).
-### Revisa la documentación
+### Revisa la documentación { #check-the-docs }
Ahora puedes iniciar tu app e ir a http://127.0.0.1:8000/docs.
diff --git a/docs/es/docs/advanced/path-operation-advanced-configuration.md b/docs/es/docs/advanced/path-operation-advanced-configuration.md
index 2b20819aa3..ea58a300ad 100644
--- a/docs/es/docs/advanced/path-operation-advanced-configuration.md
+++ b/docs/es/docs/advanced/path-operation-advanced-configuration.md
@@ -1,6 +1,6 @@
-# Configuración Avanzada de Path Operation
+# Configuración Avanzada de Path Operation { #path-operation-advanced-configuration }
-## operationId de OpenAPI
+## operationId de OpenAPI { #openapi-operationid }
/// warning | Advertencia
@@ -10,17 +10,17 @@ Si no eres un "experto" en OpenAPI, probablemente no necesites esto.
Puedes establecer el `operationId` de OpenAPI para ser usado en tu *path operation* con el parámetro `operation_id`.
-Tienes que asegurarte de que sea único para cada operación.
+Tendrías que asegurarte de que sea único para cada operación.
-{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
-### Usar el nombre de la *función de path operation* como el operationId
+### Usar el nombre de la *path operation function* como el operationId { #using-the-path-operation-function-name-as-the-operationid }
Si quieres usar los nombres de las funciones de tus APIs como `operationId`s, puedes iterar sobre todas ellas y sobrescribir el `operation_id` de cada *path operation* usando su `APIRoute.name`.
Deberías hacerlo después de agregar todas tus *path operations*.
-{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *}
/// tip | Consejo
@@ -30,29 +30,29 @@ Si llamas manualmente a `app.openapi()`, deberías actualizar los `operationId`s
/// warning | Advertencia
-Si haces esto, tienes que asegurarte de que cada una de tus *funciones de path operation* tenga un nombre único.
+Si haces esto, tienes que asegurarte de que cada una de tus *path operation functions* tenga un nombre único.
Incluso si están en diferentes módulos (archivos de Python).
///
-## Excluir de OpenAPI
+## Excluir de OpenAPI { #exclude-from-openapi }
Para excluir una *path operation* del esquema OpenAPI generado (y por lo tanto, de los sistemas de documentación automática), utiliza el parámetro `include_in_schema` y configúralo en `False`:
-{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *}
-## Descripción avanzada desde el docstring
+## Descripción avanzada desde el docstring { #advanced-description-from-docstring }
-Puedes limitar las líneas usadas del docstring de una *función de path operation* para OpenAPI.
+Puedes limitar las líneas usadas del docstring de una *path operation function* para OpenAPI.
-Añadir un `\f` (un carácter de separación de página escapado) hace que **FastAPI** trunque la salida usada para OpenAPI en este punto.
+Añadir un `\f` (un carácter "form feed" escapado) hace que **FastAPI** trunque la salida usada para OpenAPI en este punto.
No aparecerá en la documentación, pero otras herramientas (como Sphinx) podrán usar el resto.
-{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
-## Responses Adicionales
+## Responses Adicionales { #additional-responses }
Probablemente has visto cómo declarar el `response_model` y el `status_code` para una *path operation*.
@@ -62,11 +62,11 @@ También puedes declarar responses adicionales con sus modelos, códigos de esta
Hay un capítulo entero en la documentación sobre ello, puedes leerlo en [Responses Adicionales en OpenAPI](additional-responses.md){.internal-link target=_blank}.
-## OpenAPI Extra
+## OpenAPI Extra { #openapi-extra }
Cuando declaras una *path operation* en tu aplicación, **FastAPI** genera automáticamente los metadatos relevantes sobre esa *path operation* para incluirlos en el esquema de OpenAPI.
-/// note | Nota
+/// note | Detalles técnicos
En la especificación de OpenAPI se llama el Objeto de Operación.
@@ -88,11 +88,11 @@ Si solo necesitas declarar responses adicionales, una forma más conveniente de
Puedes extender el esquema de OpenAPI para una *path operation* usando el parámetro `openapi_extra`.
-### Extensiones de OpenAPI
+### Extensiones de OpenAPI { #openapi-extensions }
Este `openapi_extra` puede ser útil, por ejemplo, para declarar [Extensiones de OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
-{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *}
Si abres la documentación automática de la API, tu extensión aparecerá en la parte inferior de la *path operation* específica.
@@ -129,7 +129,7 @@ Y si ves el OpenAPI resultante (en `/openapi.json` en tu API), verás tu extensi
}
```
-### Esquema de *path operation* personalizada de OpenAPI
+### Esquema de *path operation* personalizada de OpenAPI { #custom-openapi-path-operation-schema }
El diccionario en `openapi_extra` se combinará profundamente con el esquema de OpenAPI generado automáticamente para la *path operation*.
@@ -139,61 +139,29 @@ Por ejemplo, podrías decidir leer y validar el request con tu propio código, s
Podrías hacer eso con `openapi_extra`:
-{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *}
-En este ejemplo, no declaramos ningún modelo Pydantic. De hecho, el cuerpo del request ni siquiera se parse como JSON, se lee directamente como `bytes`, y la función `magic_data_reader()` sería la encargada de parsearlo de alguna manera.
+En este ejemplo, no declaramos ningún modelo Pydantic. De hecho, el request body ni siquiera se parse como JSON, se lee directamente como `bytes`, y la función `magic_data_reader()` sería la encargada de parsearlo de alguna manera.
-Sin embargo, podemos declarar el esquema esperado para el cuerpo del request.
+Sin embargo, podemos declarar el esquema esperado para el request body.
-### Tipo de contenido personalizado de OpenAPI
+### Tipo de contenido personalizado de OpenAPI { #custom-openapi-content-type }
-Usando este mismo truco, podrías usar un modelo Pydantic para definir el esquema JSON que luego se incluye en la sección personalizada del esquema OpenAPI para la *path operation*.
+Usando este mismo truco, podrías usar un modelo Pydantic para definir el JSON Schema que luego se incluye en la sección personalizada del esquema OpenAPI para la *path operation*.
Y podrías hacer esto incluso si el tipo de datos en el request no es JSON.
-Por ejemplo, en esta aplicación no usamos la funcionalidad integrada de FastAPI para extraer el esquema JSON de los modelos Pydantic ni la validación automática para JSON. De hecho, estamos declarando el tipo de contenido del request como YAML, no JSON:
+Por ejemplo, en esta aplicación no usamos la funcionalidad integrada de FastAPI para extraer el JSON Schema de los modelos Pydantic ni la validación automática para JSON. De hecho, estamos declarando el tipo de contenido del request como YAML, no JSON:
-//// tab | Pydantic v2
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *}
-
-////
-
-//// tab | Pydantic v1
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *}
-
-////
-
-/// info | Información
-
-En la versión 1 de Pydantic el método para obtener el esquema JSON para un modelo se llamaba `Item.schema()`, en la versión 2 de Pydantic, el método se llama `Item.model_json_schema()`.
-
-///
-
-Sin embargo, aunque no estamos usando la funcionalidad integrada por defecto, aún estamos usando un modelo Pydantic para generar manualmente el esquema JSON para los datos que queremos recibir en YAML.
+Sin embargo, aunque no estamos usando la funcionalidad integrada por defecto, aún estamos usando un modelo Pydantic para generar manualmente el JSON Schema para los datos que queremos recibir en YAML.
Luego usamos el request directamente, y extraemos el cuerpo como `bytes`. Esto significa que FastAPI ni siquiera intentará parsear la carga útil del request como JSON.
Y luego en nuestro código, parseamos ese contenido YAML directamente, y nuevamente estamos usando el mismo modelo Pydantic para validar el contenido YAML:
-//// tab | Pydantic v2
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *}
-
-////
-
-//// tab | Pydantic v1
-
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *}
-
-////
-
-/// info | Información
-
-En la versión 1 de Pydantic el método para parsear y validar un objeto era `Item.parse_obj()`, en la versión 2 de Pydantic, el método se llama `Item.model_validate()`.
-
-///
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
/// tip | Consejo
diff --git a/docs/es/docs/advanced/response-change-status-code.md b/docs/es/docs/advanced/response-change-status-code.md
index e0889c4743..940f1dd3ff 100644
--- a/docs/es/docs/advanced/response-change-status-code.md
+++ b/docs/es/docs/advanced/response-change-status-code.md
@@ -1,10 +1,10 @@
-# Response - Cambiar Código de Estado
+# Response - Cambiar Código de Estado { #response-change-status-code }
Probablemente leíste antes que puedes establecer un [Código de Estado de Response](../tutorial/response-status-code.md){.internal-link target=_blank} por defecto.
Pero en algunos casos necesitas devolver un código de estado diferente al predeterminado.
-## Caso de uso
+## Caso de uso { #use-case }
Por ejemplo, imagina que quieres devolver un código de estado HTTP de "OK" `200` por defecto.
@@ -14,13 +14,13 @@ Pero todavía quieres poder filtrar y convertir los datos que devuelves con un `
Para esos casos, puedes usar un parámetro `Response`.
-## Usa un parámetro `Response`
+## Usa un parámetro `Response` { #use-a-response-parameter }
-Puedes declarar un parámetro de tipo `Response` en tu *función de path operation* (como puedes hacer para cookies y headers).
+Puedes declarar un parámetro de tipo `Response` en tu *path operation function* (como puedes hacer para cookies y headers).
Y luego puedes establecer el `status_code` en ese objeto de response *temporal*.
-{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *}
+{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *}
Y luego puedes devolver cualquier objeto que necesites, como lo harías normalmente (un `dict`, un modelo de base de datos, etc.).
diff --git a/docs/es/docs/advanced/response-cookies.md b/docs/es/docs/advanced/response-cookies.md
index 05b78528e0..550a5d97a7 100644
--- a/docs/es/docs/advanced/response-cookies.md
+++ b/docs/es/docs/advanced/response-cookies.md
@@ -1,12 +1,12 @@
-# Cookies de Response
+# Cookies de Response { #response-cookies }
-## Usar un parámetro `Response`
+## Usar un parámetro `Response` { #use-a-response-parameter }
Puedes declarar un parámetro de tipo `Response` en tu *path operation function*.
Y luego puedes establecer cookies en ese objeto de response *temporal*.
-{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *}
+{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *}
Y entonces puedes devolver cualquier objeto que necesites, como normalmente lo harías (un `dict`, un modelo de base de datos, etc).
@@ -16,7 +16,7 @@ Y si declaraste un `response_model`, todavía se utilizará para filtrar y conve
También puedes declarar el parámetro `Response` en las dependencias, y establecer cookies (y headers) en ellas.
-## Devolver una `Response` directamente
+## Devolver una `Response` directamente { #return-a-response-directly }
También puedes crear cookies al devolver una `Response` directamente en tu código.
@@ -24,7 +24,7 @@ Para hacer eso, puedes crear un response como se describe en [Devolver un Respon
Luego establece Cookies en ella, y luego devuélvela:
-{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
+{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *}
/// tip | Consejo
@@ -36,7 +36,7 @@ Y también que no estés enviando ningún dato que debería haber sido filtrado
///
-### Más información
+### Más información { #more-info }
/// note | Detalles Técnicos
diff --git a/docs/es/docs/advanced/response-directly.md b/docs/es/docs/advanced/response-directly.md
index 8594011d61..2da4e84e70 100644
--- a/docs/es/docs/advanced/response-directly.md
+++ b/docs/es/docs/advanced/response-directly.md
@@ -1,4 +1,4 @@
-# Devolver una Response Directamente
+# Devolver una Response Directamente { #return-a-response-directly }
Cuando creas una *path operation* en **FastAPI**, normalmente puedes devolver cualquier dato desde ella: un `dict`, una `list`, un modelo de Pydantic, un modelo de base de datos, etc.
@@ -10,7 +10,7 @@ Pero puedes devolver un `JSONResponse` directamente desde tus *path operations*.
Esto podría ser útil, por ejemplo, para devolver headers o cookies personalizados.
-## Devolver una `Response`
+## Devolver una `Response` { #return-a-response }
De hecho, puedes devolver cualquier `Response` o cualquier subclase de ella.
@@ -26,7 +26,7 @@ No hará ninguna conversión de datos con los modelos de Pydantic, no convertir
Esto te da mucha flexibilidad. Puedes devolver cualquier tipo de datos, sobrescribir cualquier declaración o validación de datos, etc.
-## Usar el `jsonable_encoder` en una `Response`
+## Usar el `jsonable_encoder` en una `Response` { #using-the-jsonable-encoder-in-a-response }
Como **FastAPI** no realiza cambios en una `Response` que devuelves, tienes que asegurarte de que sus contenidos estén listos para ello.
@@ -34,9 +34,9 @@ Por ejemplo, no puedes poner un modelo de Pydantic en un `JSONResponse` sin prim
Para esos casos, puedes usar el `jsonable_encoder` para convertir tus datos antes de pasarlos a un response:
-{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *}
+{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *}
-/// note | Nota
+/// note | Detalles técnicos
También podrías usar `from starlette.responses import JSONResponse`.
@@ -44,7 +44,7 @@ También podrías usar `from starlette.responses import JSONResponse`.
///
-## Devolver una `Response` personalizada
+## Devolver una `Response` personalizada { #returning-a-custom-response }
El ejemplo anterior muestra todas las partes que necesitas, pero aún no es muy útil, ya que podrías haber devuelto el `item` directamente, y **FastAPI** lo colocaría en un `JSONResponse` por ti, convirtiéndolo a un `dict`, etc. Todo eso por defecto.
@@ -54,9 +54,9 @@ Digamos que quieres devolver un response en usando el prefijo 'X-'.
+Ten en cuenta que los headers propietarios personalizados se pueden agregar usando el prefijo `X-`.
Pero si tienes headers personalizados que quieres que un cliente en un navegador pueda ver, necesitas agregarlos a tus configuraciones de CORS (leer más en [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), usando el parámetro `expose_headers` documentado en la documentación CORS de Starlette.
diff --git a/docs/es/docs/advanced/security/http-basic-auth.md b/docs/es/docs/advanced/security/http-basic-auth.md
index 629e6c50a0..440c081e06 100644
--- a/docs/es/docs/advanced/security/http-basic-auth.md
+++ b/docs/es/docs/advanced/security/http-basic-auth.md
@@ -1,4 +1,4 @@
-# HTTP Basic Auth
+# HTTP Basic Auth { #http-basic-auth }
Para los casos más simples, puedes usar HTTP Basic Auth.
@@ -12,7 +12,7 @@ Eso le dice al navegador que muestre el prompt integrado para un nombre de usuar
Luego, cuando escribes ese nombre de usuario y contraseña, el navegador los envía automáticamente en el header.
-## Simple HTTP Basic Auth
+## Simple HTTP Basic Auth { #simple-http-basic-auth }
* Importa `HTTPBasic` y `HTTPBasicCredentials`.
* Crea un "esquema de `security`" usando `HTTPBasic`.
@@ -26,7 +26,7 @@ Cuando intentas abrir la URL por primera vez (o haces clic en el botón "Execute
-## Revisa el nombre de usuario
+## Revisa el nombre de usuario { #check-the-username }
Aquí hay un ejemplo más completo.
@@ -46,13 +46,13 @@ Esto sería similar a:
```Python
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
- # Return some error
+ # Devuelve algún error
...
```
Pero al usar `secrets.compare_digest()` será seguro contra un tipo de ataques llamados "timing attacks".
-### Timing Attacks
+### Timing attacks { #timing-attacks }
¿Pero qué es un "timing attack"?
@@ -80,19 +80,19 @@ if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
Python tendrá que comparar todo `stanleyjobso` en ambos `stanleyjobsox` y `stanleyjobson` antes de darse cuenta de que ambas strings no son las mismas. Así que tomará algunos microsegundos extra para responder "Nombre de usuario o contraseña incorrectos".
-#### El tiempo de respuesta ayuda a los atacantes
+#### El tiempo de respuesta ayuda a los atacantes { #the-time-to-answer-helps-the-attackers }
En ese punto, al notar que el servidor tardó algunos microsegundos más en enviar el response "Nombre de usuario o contraseña incorrectos", los atacantes sabrán que acertaron en _algo_, algunas de las letras iniciales eran correctas.
Y luego pueden intentar de nuevo sabiendo que probablemente es algo más similar a `stanleyjobsox` que a `johndoe`.
-#### Un ataque "profesional"
+#### Un ataque "profesional" { #a-professional-attack }
Por supuesto, los atacantes no intentarían todo esto a mano, escribirían un programa para hacerlo, posiblemente con miles o millones de pruebas por segundo. Y obtendrían solo una letra correcta adicional a la vez.
Pero haciendo eso, en algunos minutos u horas, los atacantes habrían adivinado el nombre de usuario y la contraseña correctos, con la "ayuda" de nuestra aplicación, solo usando el tiempo tomado para responder.
-#### Arréglalo con `secrets.compare_digest()`
+#### Arréglalo con `secrets.compare_digest()` { #fix-it-with-secrets-compare-digest }
Pero en nuestro código estamos usando realmente `secrets.compare_digest()`.
@@ -100,7 +100,7 @@ En resumen, tomará el mismo tiempo comparar `stanleyjobsox` con `stanleyjobson`
De esa manera, usando `secrets.compare_digest()` en el código de tu aplicación, será seguro contra todo este rango de ataques de seguridad.
-### Devuelve el error
+### Devuelve el error { #return-the-error }
Después de detectar que las credenciales son incorrectas, regresa un `HTTPException` con un código de estado 401 (el mismo que se devuelve cuando no se proporcionan credenciales) y agrega el header `WWW-Authenticate` para que el navegador muestre el prompt de inicio de sesión nuevamente:
diff --git a/docs/es/docs/advanced/security/index.md b/docs/es/docs/advanced/security/index.md
index e4ccb59781..8b3e67fac4 100644
--- a/docs/es/docs/advanced/security/index.md
+++ b/docs/es/docs/advanced/security/index.md
@@ -1,6 +1,6 @@
-# Seguridad Avanzada
+# Seguridad Avanzada { #advanced-security }
-## Funcionalidades Adicionales
+## Funcionalidades Adicionales { #additional-features }
Hay algunas funcionalidades extra para manejar la seguridad aparte de las cubiertas en el [Tutorial - Guía del Usuario: Seguridad](../../tutorial/security/index.md){.internal-link target=_blank}.
@@ -12,8 +12,8 @@ Y es posible que para tu caso de uso, la solución esté en una de ellas.
///
-## Lee primero el Tutorial
+## Lee primero el Tutorial { #read-the-tutorial-first }
-Las siguientes secciones asumen que ya leíste el [Tutorial - Guía del Usuario: Seguridad](../../tutorial/security/index.md){.internal-link target=_blank}.
+Las siguientes secciones asumen que ya leíste el [Tutorial - Guía del Usuario: Seguridad](../../tutorial/security/index.md){.internal-link target=_blank} principal.
Todas están basadas en los mismos conceptos, pero permiten algunas funcionalidades adicionales.
diff --git a/docs/es/docs/advanced/security/oauth2-scopes.md b/docs/es/docs/advanced/security/oauth2-scopes.md
index a8d2f20b68..4e4580fde3 100644
--- a/docs/es/docs/advanced/security/oauth2-scopes.md
+++ b/docs/es/docs/advanced/security/oauth2-scopes.md
@@ -1,4 +1,4 @@
-# Scopes de OAuth2
+# Scopes de OAuth2 { #oauth2-scopes }
Puedes usar scopes de OAuth2 directamente con **FastAPI**, están integrados para funcionar de manera fluida.
@@ -26,7 +26,7 @@ Pero si sabes que lo necesitas, o tienes curiosidad, sigue leyendo.
///
-## Scopes de OAuth2 y OpenAPI
+## Scopes de OAuth2 y OpenAPI { #oauth2-scopes-and-openapi }
La especificación de OAuth2 define "scopes" como una lista de strings separados por espacios.
@@ -58,15 +58,15 @@ Para OAuth2 son solo strings.
///
-## Vista global
+## Vista global { #global-view }
Primero, echemos un vistazo rápido a las partes que cambian desde los ejemplos en el **Tutorial - User Guide** principal para [OAuth2 con Password (y hashing), Bearer con tokens JWT](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Ahora usando scopes de OAuth2:
-{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:125,129:135,140,156] *}
+{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *}
Ahora revisemos esos cambios paso a paso.
-## Esquema de seguridad OAuth2
+## Esquema de seguridad OAuth2 { #oauth2-security-scheme }
El primer cambio es que ahora estamos declarando el esquema de seguridad OAuth2 con dos scopes disponibles, `me` y `items`.
@@ -82,7 +82,7 @@ Este es el mismo mecanismo utilizado cuando das permisos al iniciar sesión con
-## Token JWT con scopes
+## Token JWT con scopes { #jwt-token-with-scopes }
Ahora, modifica la *path operation* del token para devolver los scopes solicitados.
@@ -98,9 +98,9 @@ Pero en tu aplicación, por seguridad, deberías asegurarte de añadir solo los
///
-{* ../../docs_src/security/tutorial005_an_py310.py hl[156] *}
+{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}
-## Declarar scopes en *path operations* y dependencias
+## Declarar scopes en *path operations* y dependencias { #declare-scopes-in-path-operations-and-dependencies }
Ahora declaramos que la *path operation* para `/users/me/items/` requiere el scope `items`.
@@ -124,7 +124,7 @@ Lo estamos haciendo aquí para demostrar cómo **FastAPI** maneja scopes declara
///
-{* ../../docs_src/security/tutorial005_an_py310.py hl[5,140,171] *}
+{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}
/// info | Información Técnica
@@ -136,7 +136,7 @@ Pero cuando importas `Query`, `Path`, `Depends`, `Security` y otros de `fastapi`
///
-## Usar `SecurityScopes`
+## Usar `SecurityScopes` { #use-securityscopes }
Ahora actualiza la dependencia `get_current_user`.
@@ -152,7 +152,7 @@ Esta clase `SecurityScopes` es similar a `Request` (`Request` se usó para obten
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}
-## Usar los `scopes`
+## Usar los `scopes` { #use-the-scopes }
El parámetro `security_scopes` será del tipo `SecurityScopes`.
@@ -166,7 +166,7 @@ En esta excepción, incluimos los scopes requeridos (si los hay) como un string
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}
-## Verificar el `username` y la forma de los datos
+## Verificar el `username` y la forma de los datos { #verify-the-username-and-data-shape }
Verificamos que obtenemos un `username`, y extraemos los scopes.
@@ -180,17 +180,17 @@ En lugar de, por ejemplo, un `dict`, o algo más, ya que podría romper la aplic
También verificamos que tenemos un usuario con ese username, y si no, lanzamos esa misma excepción que creamos antes.
-{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:128] *}
+{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}
-## Verificar los `scopes`
+## Verificar los `scopes` { #verify-the-scopes }
Ahora verificamos que todos los scopes requeridos, por esta dependencia y todos los dependientes (incluyendo *path operations*), estén incluidos en los scopes proporcionados en el token recibido, de lo contrario, lanzamos una `HTTPException`.
Para esto, usamos `security_scopes.scopes`, que contiene una `list` con todos estos scopes como `str`.
-{* ../../docs_src/security/tutorial005_an_py310.py hl[129:135] *}
+{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}
-## Árbol de dependencias y scopes
+## Árbol de dependencias y scopes { #dependency-tree-and-scopes }
Revisemos de nuevo este árbol de dependencias y los scopes.
@@ -223,7 +223,7 @@ Todo depende de los `scopes` declarados en cada *path operation* y cada dependen
///
-## Más detalles sobre `SecurityScopes`
+## Más detalles sobre `SecurityScopes` { #more-details-about-securityscopes }
Puedes usar `SecurityScopes` en cualquier punto, y en múltiples lugares, no tiene que ser en la dependencia "raíz".
@@ -233,7 +233,7 @@ Debido a que `SecurityScopes` tendrá todos los scopes declarados por dependient
Serán verificados independientemente para cada *path operation*.
-## Revisa
+## Revisa { #check-it }
Si abres la documentación de la API, puedes autenticarte y especificar qué scopes deseas autorizar.
@@ -245,7 +245,7 @@ Y si seleccionas el scope `me` pero no el scope `items`, podrás acceder a `/use
Eso es lo que pasaría a una aplicación de terceros que intentara acceder a una de estas *path operations* con un token proporcionado por un usuario, dependiendo de cuántos permisos el usuario otorgó a la aplicación.
-## Acerca de las integraciones de terceros
+## Acerca de las integraciones de terceros { #about-third-party-integrations }
En este ejemplo estamos usando el flujo de OAuth2 "password".
@@ -269,6 +269,6 @@ Pero al final, están implementando el mismo estándar OAuth2.
**FastAPI** incluye utilidades para todos estos flujos de autenticación OAuth2 en `fastapi.security.oauth2`.
-## `Security` en `dependencies` del decorador
+## `Security` en `dependencies` del decorador { #security-in-decorator-dependencies }
De la misma manera que puedes definir una `list` de `Depends` en el parámetro `dependencies` del decorador (como se explica en [Dependencias en decoradores de path operation](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), también podrías usar `Security` con `scopes` allí.
diff --git a/docs/es/docs/advanced/settings.md b/docs/es/docs/advanced/settings.md
index 7e591cc01b..a2d749103e 100644
--- a/docs/es/docs/advanced/settings.md
+++ b/docs/es/docs/advanced/settings.md
@@ -1,4 +1,4 @@
-# Configuraciones y Variables de Entorno
+# Configuraciones y Variables de Entorno { #settings-and-environment-variables }
En muchos casos, tu aplicación podría necesitar algunas configuraciones o ajustes externos, por ejemplo, claves secretas, credenciales de base de datos, credenciales para servicios de correo electrónico, etc.
@@ -12,17 +12,17 @@ Para entender las variables de entorno, puedes leer [Variables de Entorno](../en
///
-## Tipos y validación
+## Tipos y validación { #types-and-validation }
Estas variables de entorno solo pueden manejar strings de texto, ya que son externas a Python y tienen que ser compatibles con otros programas y el resto del sistema (e incluso con diferentes sistemas operativos, como Linux, Windows, macOS).
Eso significa que cualquier valor leído en Python desde una variable de entorno será un `str`, y cualquier conversión a un tipo diferente o cualquier validación tiene que hacerse en código.
-## Pydantic `Settings`
+## Pydantic `Settings` { #pydantic-settings }
Afortunadamente, Pydantic proporciona una gran utilidad para manejar estas configuraciones provenientes de variables de entorno con Pydantic: Settings management.
-### Instalar `pydantic-settings`
+### Instalar `pydantic-settings` { #install-pydantic-settings }
Primero, asegúrate de crear tu [entorno virtual](../virtual-environments.md){.internal-link target=_blank}, actívalo y luego instala el paquete `pydantic-settings`:
@@ -46,13 +46,7 @@ $ pip install "fastapi[all]"
fastapi run --workers 4 main.py -INFO Using path main.py -INFO Resolved absolute path /home/user/code/awesomeapp/main.py -INFO Searching for package file structure from directories with __init__.py files -INFO Importing from /home/user/code/awesomeapp +$ fastapi run --workers 4 main.py - ╭─ Python module file ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ + FastAPI Starting production server 🚀 -INFO Importing module main -INFO Found importable FastAPI app + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp - ╭─ Importable FastAPI app ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ + module 🐍 main.py -INFO Using import string main:app + code Importing the FastAPI app object from the module with the + following code: - ╭─────────── FastAPI CLI - Production mode ───────────╮ - │ │ - │ Serving at: http://0.0.0.0:8000 │ - │ │ - │ API docs: http://0.0.0.0:8000/docs │ - │ │ - │ Running in production mode, for development use: │ - │ │ - │ fastapi dev │ - │ │ - ╰─────────────────────────────────────────────────────╯ + from main import app -INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) -INFO: Started parent process [27365] -INFO: Started server process [27368] -INFO: Waiting for application startup. -INFO: Application startup complete. -INFO: Started server process [27369] -INFO: Waiting for application startup. -INFO: Application startup complete. -INFO: Started server process [27370] -INFO: Waiting for application startup. -INFO: Application startup complete. -INFO: Started server process [27367] -INFO: Waiting for application startup. -INFO: Application startup complete. -+ app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to + quit) + INFO Started parent process [27365] + INFO Started server process [27368] + INFO Started server process [27369] + INFO Started server process [27370] + INFO Started server process [27367] + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. ```
-## Cambiar el tema
+## Cambiar el tema { #change-the-theme }
De la misma manera, podrías configurar el tema del resaltado de sintaxis con la clave `"syntaxHighlight.theme"` (ten en cuenta que tiene un punto en el medio):
-{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *}
+{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *}
Esa configuración cambiaría el tema de color del resaltado de sintaxis:
-## Cambiar los parámetros predeterminados de Swagger UI
+## Cambiar los parámetros predeterminados de Swagger UI { #change-default-swagger-ui-parameters }
FastAPI incluye algunos parámetros de configuración predeterminados apropiados para la mayoría de los casos de uso.
Incluye estas configuraciones predeterminadas:
-{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *}
+{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *}
Puedes sobrescribir cualquiera de ellos estableciendo un valor diferente en el argumento `swagger_ui_parameters`.
Por ejemplo, para desactivar `deepLinking` podrías pasar estas configuraciones a `swagger_ui_parameters`:
-{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *}
+{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *}
-## Otros parámetros de Swagger UI
+## Otros parámetros de Swagger UI { #other-swagger-ui-parameters }
Para ver todas las demás configuraciones posibles que puedes usar, lee la documentación oficial de los parámetros de Swagger UI.
-## Configuraciones solo de JavaScript
+## Configuraciones solo de JavaScript { #javascript-only-settings }
Swagger UI también permite otras configuraciones que son objetos **solo de JavaScript** (por ejemplo, funciones de JavaScript).
diff --git a/docs/es/docs/how-to/custom-docs-ui-assets.md b/docs/es/docs/how-to/custom-docs-ui-assets.md
index 0a03ff330b..acd3f8d6d6 100644
--- a/docs/es/docs/how-to/custom-docs-ui-assets.md
+++ b/docs/es/docs/how-to/custom-docs-ui-assets.md
@@ -1,26 +1,26 @@
-# Recursos Estáticos Personalizados para la Docs UI (Self-Hosting)
+# Recursos Estáticos Personalizados para la Docs UI (self hosting) { #custom-docs-ui-static-assets-self-hosting }
La documentación de la API utiliza **Swagger UI** y **ReDoc**, y cada uno de estos necesita algunos archivos JavaScript y CSS.
-Por defecto, esos archivos se sirven desde un CDN.
+Por defecto, esos archivos se sirven desde un CDN.
Pero es posible personalizarlo, puedes establecer un CDN específico, o servir los archivos tú mismo.
-## CDN Personalizado para JavaScript y CSS
+## CDN Personalizado para JavaScript y CSS { #custom-cdn-for-javascript-and-css }
-Digamos que quieres usar un CDN diferente, por ejemplo, quieres usar `https://unpkg.com/`.
+Digamos que quieres usar un CDN diferente, por ejemplo, quieres usar `https://unpkg.com/`.
Esto podría ser útil si, por ejemplo, vives en un país que restringe algunas URLs.
-### Desactiva la documentación automática
+### Desactiva la documentación automática { #disable-the-automatic-docs }
-El primer paso es desactivar la documentación automática, ya que por defecto, esos usan el CDN predeterminado.
+El primer paso es desactivar la documentación automática, ya que por defecto, esos usan el CDN por defecto.
Para desactivarlos, establece sus URLs en `None` cuando crees tu aplicación de `FastAPI`:
-{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *}
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *}
-### Incluye la documentación personalizada
+### Incluye la documentación personalizada { #include-the-custom-docs }
Ahora puedes crear las *path operations* para la documentación personalizada.
@@ -28,13 +28,13 @@ Puedes reutilizar las funciones internas de FastAPI para crear las páginas HTML
* `openapi_url`: la URL donde la página HTML para la documentación puede obtener el OpenAPI esquema de tu API. Puedes usar aquí el atributo `app.openapi_url`.
* `title`: el título de tu API.
-* `oauth2_redirect_url`: puedes usar `app.swagger_ui_oauth2_redirect_url` aquí para usar el valor predeterminado.
+* `oauth2_redirect_url`: puedes usar `app.swagger_ui_oauth2_redirect_url` aquí para usar el valor por defecto.
* `swagger_js_url`: la URL donde el HTML para tu documentación de Swagger UI puede obtener el archivo **JavaScript**. Esta es la URL personalizada del CDN.
* `swagger_css_url`: la URL donde el HTML para tu documentación de Swagger UI puede obtener el archivo **CSS**. Esta es la URL personalizada del CDN.
Y de manera similar para ReDoc...
-{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *}
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *}
/// tip | Consejo
@@ -46,23 +46,23 @@ Swagger UI lo manejará detrás de escena para ti, pero necesita este auxiliar d
///
-### Crea una *path operation* para probarlo
+### Crea una *path operation* para probarlo { #create-a-path-operation-to-test-it }
Ahora, para poder probar que todo funciona, crea una *path operation*:
-{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *}
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *}
-### Pruébalo
+### Pruébalo { #test-it }
Ahora, deberías poder ir a tu documentación en http://127.0.0.1:8000/docs, y recargar la página, cargará esos recursos desde el nuevo CDN.
-## Self-hosting de JavaScript y CSS para la documentación
+## self hosting de JavaScript y CSS para la documentación { #self-hosting-javascript-and-css-for-docs }
-El self-hosting de JavaScript y CSS podría ser útil si, por ejemplo, necesitas que tu aplicación siga funcionando incluso offline, sin acceso a Internet, o en una red local.
+El self hosting de JavaScript y CSS podría ser útil si, por ejemplo, necesitas que tu aplicación siga funcionando incluso offline, sin acceso a Internet, o en una red local.
Aquí verás cómo servir esos archivos tú mismo, en la misma aplicación de FastAPI, y configurar la documentación para usarla.
-### Estructura de archivos del proyecto
+### Estructura de archivos del proyecto { #project-file-structure }
Supongamos que la estructura de archivos de tu proyecto se ve así:
@@ -85,7 +85,7 @@ Tu nueva estructura de archivos podría verse así:
└── static/
```
-### Descarga los archivos
+### Descarga los archivos { #download-the-files }
Descarga los archivos estáticos necesarios para la documentación y ponlos en ese directorio `static/`.
@@ -113,14 +113,14 @@ Después de eso, tu estructura de archivos podría verse así:
└── swagger-ui.css
```
-### Sirve los archivos estáticos
+### Sirve los archivos estáticos { #serve-the-static-files }
* Importa `StaticFiles`.
* "Monta" una instance de `StaticFiles()` en un path específico.
-{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *}
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *}
-### Prueba los archivos estáticos
+### Prueba los archivos estáticos { #test-the-static-files }
Inicia tu aplicación y ve a http://127.0.0.1:8000/static/redoc.standalone.js.
@@ -138,15 +138,15 @@ Eso confirma que puedes servir archivos estáticos desde tu aplicación, y que c
Ahora podemos configurar la aplicación para usar esos archivos estáticos para la documentación.
-### Desactiva la documentación automática para archivos estáticos
+### Desactiva la documentación automática para archivos estáticos { #disable-the-automatic-docs-for-static-files }
Igual que cuando usas un CDN personalizado, el primer paso es desactivar la documentación automática, ya que esos usan el CDN por defecto.
Para desactivarlos, establece sus URLs en `None` cuando crees tu aplicación de `FastAPI`:
-{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *}
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *}
-### Incluye la documentación personalizada para archivos estáticos
+### Incluye la documentación personalizada para archivos estáticos { #include-the-custom-docs-for-static-files }
Y de la misma manera que con un CDN personalizado, ahora puedes crear las *path operations* para la documentación personalizada.
@@ -154,13 +154,13 @@ Nuevamente, puedes reutilizar las funciones internas de FastAPI para crear las p
* `openapi_url`: la URL donde la página HTML para la documentación puede obtener el OpenAPI esquema de tu API. Puedes usar aquí el atributo `app.openapi_url`.
* `title`: el título de tu API.
-* `oauth2_redirect_url`: puedes usar `app.swagger_ui_oauth2_redirect_url` aquí para usar el valor predeterminado.
+* `oauth2_redirect_url`: puedes usar `app.swagger_ui_oauth2_redirect_url` aquí para usar el valor por defecto.
* `swagger_js_url`: la URL donde el HTML para tu documentación de Swagger UI puede obtener el archivo **JavaScript**. **Este es el que tu propia aplicación está sirviendo ahora**.
* `swagger_css_url`: la URL donde el HTML para tu documentación de Swagger UI puede obtener el archivo **CSS**. **Este es el que tu propia aplicación está sirviendo ahora**.
Y de manera similar para ReDoc...
-{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *}
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *}
/// tip | Consejo
@@ -172,13 +172,13 @@ Swagger UI lo manejará detrás de escena para ti, pero necesita este auxiliar d
///
-### Crea una *path operation* para probar archivos estáticos
+### Crea una *path operation* para probar archivos estáticos { #create-a-path-operation-to-test-static-files }
Ahora, para poder probar que todo funciona, crea una *path operation*:
-{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *}
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *}
-### Prueba la UI de Archivos Estáticos
+### Prueba la UI de Archivos Estáticos { #test-static-files-ui }
Ahora, deberías poder desconectar tu WiFi, ir a tu documentación en http://127.0.0.1:8000/docs, y recargar la página.
diff --git a/docs/es/docs/how-to/custom-request-and-route.md b/docs/es/docs/how-to/custom-request-and-route.md
index a6ea657d15..ff13196f8a 100644
--- a/docs/es/docs/how-to/custom-request-and-route.md
+++ b/docs/es/docs/how-to/custom-request-and-route.md
@@ -1,4 +1,4 @@
-# Clase personalizada de Request y APIRoute
+# Clase personalizada de Request y APIRoute { #custom-request-and-apiroute-class }
En algunos casos, puede que quieras sobrescribir la lógica utilizada por las clases `Request` y `APIRoute`.
@@ -14,7 +14,7 @@ Si apenas estás comenzando con **FastAPI**, quizás quieras saltar esta secció
///
-## Casos de uso
+## Casos de uso { #use-cases }
Algunos casos de uso incluyen:
@@ -22,13 +22,13 @@ Algunos casos de uso incluyen:
* Descomprimir cuerpos de requests comprimidos con gzip.
* Registrar automáticamente todos los request bodies.
-## Manejo de codificaciones personalizadas de request body
+## Manejo de codificaciones personalizadas de request body { #handling-custom-request-body-encodings }
Veamos cómo hacer uso de una subclase personalizada de `Request` para descomprimir requests gzip.
Y una subclase de `APIRoute` para usar esa clase de request personalizada.
-### Crear una clase personalizada `GzipRequest`
+### Crear una clase personalizada `GzipRequest` { #create-a-custom-gziprequest-class }
/// tip | Consejo
@@ -42,9 +42,9 @@ Si no hay `gzip` en el header, no intentará descomprimir el cuerpo.
De esa manera, la misma clase de ruta puede manejar requests comprimidos con gzip o no comprimidos.
-{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *}
+{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *}
-### Crear una clase personalizada `GzipRoute`
+### Crear una clase personalizada `GzipRoute` { #create-a-custom-gziproute-class }
A continuación, creamos una subclase personalizada de `fastapi.routing.APIRoute` que hará uso de `GzipRequest`.
@@ -54,7 +54,7 @@ Este método devuelve una función. Y esa función es la que recibirá un reques
Aquí lo usamos para crear un `GzipRequest` a partir del request original.
-{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *}
+{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *}
/// note | Detalles técnicos
@@ -78,7 +78,7 @@ Después de eso, toda la lógica de procesamiento es la misma.
Pero debido a nuestros cambios en `GzipRequest.body`, el request body se descomprimirá automáticamente cuando sea cargado por **FastAPI** si es necesario.
-## Accediendo al request body en un manejador de excepciones
+## Accediendo al request body en un manejador de excepciones { #accessing-the-request-body-in-an-exception-handler }
/// tip | Consejo
@@ -92,18 +92,18 @@ También podemos usar este mismo enfoque para acceder al request body en un mane
Todo lo que necesitamos hacer es manejar el request dentro de un bloque `try`/`except`:
-{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *}
+{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *}
Si ocurre una excepción, la `Request instance` aún estará en el alcance, así que podemos leer y hacer uso del request body cuando manejamos el error:
-{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *}
+{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *}
-## Clase personalizada `APIRoute` en un router
+## Clase personalizada `APIRoute` en un router { #custom-apiroute-class-in-a-router }
También puedes establecer el parámetro `route_class` de un `APIRouter`:
-{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *}
+{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *}
En este ejemplo, las *path operations* bajo el `router` usarán la clase personalizada `TimedRoute`, y tendrán un header `X-Response-Time` extra en el response con el tiempo que tomó generar el response:
-{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *}
+{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *}
diff --git a/docs/es/docs/how-to/extending-openapi.md b/docs/es/docs/how-to/extending-openapi.md
index 3dbdd666be..2611b6e1b6 100644
--- a/docs/es/docs/how-to/extending-openapi.md
+++ b/docs/es/docs/how-to/extending-openapi.md
@@ -1,10 +1,10 @@
-# Extender OpenAPI
+# Extender OpenAPI { #extending-openapi }
Hay algunos casos en los que podrías necesitar modificar el esquema de OpenAPI generado.
En esta sección verás cómo hacerlo.
-## El proceso normal
+## El proceso normal { #the-normal-process }
El proceso normal (por defecto) es el siguiente.
@@ -33,31 +33,31 @@ El parámetro `summary` está disponible en OpenAPI 3.1.0 y versiones superiores
///
-## Sobrescribir los valores por defecto
+## Sobrescribir los valores por defecto { #overriding-the-defaults }
Usando la información anterior, puedes usar la misma función de utilidad para generar el esquema de OpenAPI y sobrescribir cada parte que necesites.
Por ejemplo, vamos a añadir la extensión OpenAPI de ReDoc para incluir un logo personalizado.
-### **FastAPI** normal
+### **FastAPI** normal { #normal-fastapi }
Primero, escribe toda tu aplicación **FastAPI** como normalmente:
-{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *}
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[1,4,7:9] *}
-### Generar el esquema de OpenAPI
+### Generar el esquema de OpenAPI { #generate-the-openapi-schema }
Luego, usa la misma función de utilidad para generar el esquema de OpenAPI, dentro de una función `custom_openapi()`:
-{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *}
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[2,15:21] *}
-### Modificar el esquema de OpenAPI
+### Modificar el esquema de OpenAPI { #modify-the-openapi-schema }
Ahora puedes añadir la extensión de ReDoc, agregando un `x-logo` personalizado al "objeto" `info` en el esquema de OpenAPI:
-{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *}
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *}
-### Cachear el esquema de OpenAPI
+### Cachear el esquema de OpenAPI { #cache-the-openapi-schema }
Puedes usar la propiedad `.openapi_schema` como un "cache", para almacenar tu esquema generado.
@@ -65,15 +65,15 @@ De esa forma, tu aplicación no tendrá que generar el esquema cada vez que un u
Se generará solo una vez, y luego se usará el mismo esquema cacheado para las siguientes requests.
-{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *}
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *}
-### Sobrescribir el método
+### Sobrescribir el método { #override-the-method }
Ahora puedes reemplazar el método `.openapi()` por tu nueva función.
-{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *}
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *}
-### Revisa
+### Revisa { #check-it }
Una vez que vayas a http://127.0.0.1:8000/redoc verás que estás usando tu logo personalizado (en este ejemplo, el logo de **FastAPI**):
diff --git a/docs/es/docs/how-to/general.md b/docs/es/docs/how-to/general.md
index e10621ce51..3a3dc82943 100644
--- a/docs/es/docs/how-to/general.md
+++ b/docs/es/docs/how-to/general.md
@@ -1,39 +1,39 @@
-# General - Cómo Hacer - Recetas
+# General - Cómo Hacer - Recetas { #general-how-to-recipes }
Aquí tienes varias indicaciones hacia otros lugares en la documentación, para preguntas generales o frecuentes.
-## Filtrar Datos - Seguridad
+## Filtrar Datos - Seguridad { #filter-data-security }
Para asegurarte de que no devuelves más datos de los que deberías, lee la documentación para [Tutorial - Modelo de Response - Tipo de Retorno](../tutorial/response-model.md){.internal-link target=_blank}.
-## Etiquetas de Documentación - OpenAPI
+## Etiquetas de Documentación - OpenAPI { #documentation-tags-openapi }
Para agregar etiquetas a tus *path operations*, y agruparlas en la interfaz de usuario de la documentación, lee la documentación para [Tutorial - Configuraciones de Path Operation - Etiquetas](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}.
-## Resumen y Descripción de Documentación - OpenAPI
+## Resumen y Descripción de Documentación - OpenAPI { #documentation-summary-and-description-openapi }
Para agregar un resumen y descripción a tus *path operations*, y mostrarlos en la interfaz de usuario de la documentación, lee la documentación para [Tutorial - Configuraciones de Path Operation - Resumen y Descripción](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}.
-## Documentación de Descripción de Response - OpenAPI
+## Documentación de Descripción de Response - OpenAPI { #documentation-response-description-openapi }
Para definir la descripción del response, mostrada en la interfaz de usuario de la documentación, lee la documentación para [Tutorial - Configuraciones de Path Operation - Descripción del Response](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}.
-## Documentar la Deprecación de una *Path Operation* - OpenAPI
+## Documentar la Deprecación de una *Path Operation* - OpenAPI { #documentation-deprecate-a-path-operation-openapi }
Para deprecar una *path operation*, y mostrarla en la interfaz de usuario de la documentación, lee la documentación para [Tutorial - Configuraciones de Path Operation - Deprecación](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}.
-## Convertir cualquier Dato a Compatible con JSON
+## Convertir cualquier Dato a Compatible con JSON { #convert-any-data-to-json-compatible }
Para convertir cualquier dato a compatible con JSON, lee la documentación para [Tutorial - Codificador Compatible con JSON](../tutorial/encoder.md){.internal-link target=_blank}.
-## Metadatos OpenAPI - Documentación
+## Metadatos OpenAPI - Documentación { #openapi-metadata-docs }
Para agregar metadatos a tu esquema de OpenAPI, incluyendo una licencia, versión, contacto, etc, lee la documentación para [Tutorial - Metadatos y URLs de Documentación](../tutorial/metadata.md){.internal-link target=_blank}.
-## URL Personalizada de OpenAPI
+## URL Personalizada de OpenAPI { #openapi-custom-url }
Para personalizar la URL de OpenAPI (o eliminarla), lee la documentación para [Tutorial - Metadatos y URLs de Documentación](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}.
-## URLs de Documentación de OpenAPI
+## URLs de Documentación de OpenAPI { #openapi-docs-urls }
Para actualizar las URLs usadas para las interfaces de usuario de documentación generadas automáticamente, lee la documentación para [Tutorial - Metadatos y URLs de Documentación](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}.
diff --git a/docs/es/docs/how-to/graphql.md b/docs/es/docs/how-to/graphql.md
index 52f1638097..e50c1ae0ac 100644
--- a/docs/es/docs/how-to/graphql.md
+++ b/docs/es/docs/how-to/graphql.md
@@ -1,4 +1,4 @@
-# GraphQL
+# GraphQL { #graphql }
Como **FastAPI** se basa en el estándar **ASGI**, es muy fácil integrar cualquier paquete de **GraphQL** que también sea compatible con ASGI.
@@ -14,7 +14,7 @@ Asegúrate de evaluar si los **beneficios** para tu caso de uso compensan los **
///
-## Paquetes de GraphQL
+## Paquetes de GraphQL { #graphql-libraries }
Aquí algunos de los paquetes de **GraphQL** que tienen soporte **ASGI**. Podrías usarlos con **FastAPI**:
@@ -27,7 +27,7 @@ Aquí algunos de los paquetes de **GraphQL** que tienen soporte **ASGI**. Podrí
* Graphene
* Con starlette-graphene3
-## GraphQL con Strawberry
+## GraphQL con Strawberry { #graphql-with-strawberry }
Si necesitas o quieres trabajar con **GraphQL**, **Strawberry** es el paquete **recomendado** ya que tiene un diseño muy similar al diseño de **FastAPI**, todo basado en **anotaciones de tipos**.
@@ -35,13 +35,13 @@ Dependiendo de tu caso de uso, podrías preferir usar un paquete diferente, pero
Aquí tienes una pequeña vista previa de cómo podrías integrar Strawberry con FastAPI:
-{* ../../docs_src/graphql/tutorial001.py hl[3,22,25:26] *}
+{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
Puedes aprender más sobre Strawberry en la documentación de Strawberry.
Y también la documentación sobre Strawberry con FastAPI.
-## `GraphQLApp` viejo de Starlette
+## `GraphQLApp` viejo de Starlette { #older-graphqlapp-from-starlette }
Las versiones anteriores de Starlette incluían una clase `GraphQLApp` para integrar con Graphene.
@@ -53,7 +53,7 @@ Si necesitas GraphQL, aún te recomendaría revisar documentación oficial de GraphQL.
diff --git a/docs/es/docs/how-to/index.md b/docs/es/docs/how-to/index.md
index 152499af88..6f5988049a 100644
--- a/docs/es/docs/how-to/index.md
+++ b/docs/es/docs/how-to/index.md
@@ -1,4 +1,4 @@
-# How To - Recetas
+# Cómo hacer - Recetas { #how-to-recipes }
Aquí verás diferentes recetas o guías de "cómo hacer" para **varios temas**.
@@ -8,6 +8,6 @@ Si algo parece interesante y útil para tu proyecto, adelante y revísalo, pero
/// tip | Consejo
-Si quieres **aprender FastAPI** de una manera estructurada (recomendado), ve y lee el [Tutorial - User Guide](../tutorial/index.md){.internal-link target=_blank} capítulo por capítulo.
+Si quieres **aprender FastAPI** de una manera estructurada (recomendado), ve y lee el [Tutorial - Guía de Usuario](../tutorial/index.md){.internal-link target=_blank} capítulo por capítulo en su lugar.
///
diff --git a/docs/es/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/es/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
new file mode 100644
index 0000000000..c862ace902
--- /dev/null
+++ b/docs/es/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
@@ -0,0 +1,135 @@
+# Migra de Pydantic v1 a Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 }
+
+Si tienes una app de FastAPI antigua, podrías estar usando Pydantic versión 1.
+
+FastAPI versión 0.100.0 tenía compatibilidad con Pydantic v1 o v2. Usaba la que tuvieras instalada.
+
+FastAPI versión 0.119.0 introdujo compatibilidad parcial con Pydantic v1 desde dentro de Pydantic v2 (como `pydantic.v1`), para facilitar la migración a v2.
+
+FastAPI 0.126.0 eliminó la compatibilidad con Pydantic v1, aunque siguió soportando `pydantic.v1` por un poquito más de tiempo.
+
+/// warning | Advertencia
+
+El equipo de Pydantic dejó de dar soporte a Pydantic v1 para las versiones más recientes de Python, comenzando con **Python 3.14**.
+
+Esto incluye `pydantic.v1`, que ya no está soportado en Python 3.14 y superiores.
+
+Si quieres usar las funcionalidades más recientes de Python, tendrás que asegurarte de usar Pydantic v2.
+
+///
+
+Si tienes una app de FastAPI antigua con Pydantic v1, aquí te muestro cómo migrarla a Pydantic v2, y las **funcionalidades en FastAPI 0.119.0** para ayudarte con una migración gradual.
+
+## Guía oficial { #official-guide }
+
+Pydantic tiene una Guía de migración oficial de v1 a v2.
+
+También incluye qué cambió, cómo las validaciones ahora son más correctas y estrictas, posibles consideraciones, etc.
+
+Puedes leerla para entender mejor qué cambió.
+
+## Tests { #tests }
+
+Asegúrate de tener [tests](../tutorial/testing.md){.internal-link target=_blank} para tu app y de ejecutarlos en integración continua (CI).
+
+Así podrás hacer la actualización y asegurarte de que todo sigue funcionando como esperas.
+
+## `bump-pydantic` { #bump-pydantic }
+
+En muchos casos, cuando usas modelos de Pydantic normales sin personalizaciones, podrás automatizar gran parte del proceso de migración de Pydantic v1 a Pydantic v2.
+
+Puedes usar `bump-pydantic` del mismo equipo de Pydantic.
+
+Esta herramienta te ayudará a cambiar automáticamente la mayor parte del código que necesita cambiarse.
+
+Después de esto, puedes ejecutar los tests y revisa si todo funciona. Si es así, ya terminaste. 😎
+
+## Pydantic v1 en v2 { #pydantic-v1-in-v2 }
+
+Pydantic v2 incluye todo lo de Pydantic v1 como un submódulo `pydantic.v1`. Pero esto ya no está soportado en versiones por encima de Python 3.13.
+
+Esto significa que puedes instalar la versión más reciente de Pydantic v2 e importar y usar los componentes viejos de Pydantic v1 desde este submódulo, como si tuvieras instalado el Pydantic v1 antiguo.
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *}
+
+### Compatibilidad de FastAPI con Pydantic v1 en v2 { #fastapi-support-for-pydantic-v1-in-v2 }
+
+Desde FastAPI 0.119.0, también hay compatibilidad parcial para Pydantic v1 desde dentro de Pydantic v2, para facilitar la migración a v2.
+
+Así que podrías actualizar Pydantic a la última versión 2 y cambiar los imports para usar el submódulo `pydantic.v1`, y en muchos casos simplemente funcionaría.
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *}
+
+/// warning | Advertencia
+
+Ten en cuenta que, como el equipo de Pydantic ya no da soporte a Pydantic v1 en versiones recientes de Python, empezando por Python 3.14, usar `pydantic.v1` tampoco está soportado en Python 3.14 y superiores.
+
+///
+
+### Pydantic v1 y v2 en la misma app { #pydantic-v1-and-v2-on-the-same-app }
+
+**No está soportado** por Pydantic tener un modelo de Pydantic v2 con sus propios campos definidos como modelos de Pydantic v1 o viceversa.
+
+```mermaid
+graph TB
+ subgraph "❌ Not Supported"
+ direction TB
+ subgraph V2["Pydantic v2 Model"]
+ V1Field["Pydantic v1 Model"]
+ end
+ subgraph V1["Pydantic v1 Model"]
+ V2Field["Pydantic v2 Model"]
+ end
+ end
+
+ style V2 fill:#f9fff3
+ style V1 fill:#fff6f0
+ style V1Field fill:#fff6f0
+ style V2Field fill:#f9fff3
+```
+
+...pero puedes tener modelos separados usando Pydantic v1 y v2 en la misma app.
+
+```mermaid
+graph TB
+ subgraph "✅ Supported"
+ direction TB
+ subgraph V2["Pydantic v2 Model"]
+ V2Field["Pydantic v2 Model"]
+ end
+ subgraph V1["Pydantic v1 Model"]
+ V1Field["Pydantic v1 Model"]
+ end
+ end
+
+ style V2 fill:#f9fff3
+ style V1 fill:#fff6f0
+ style V1Field fill:#fff6f0
+ style V2Field fill:#f9fff3
+```
+
+En algunos casos, incluso es posible tener modelos de Pydantic v1 y v2 en la misma **path operation** de tu app de FastAPI:
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *}
+
+En el ejemplo anterior, el modelo de entrada es un modelo de Pydantic v1 y el modelo de salida (definido en `response_model=ItemV2`) es un modelo de Pydantic v2.
+
+### Parámetros de Pydantic v1 { #pydantic-v1-parameters }
+
+Si necesitas usar algunas de las herramientas específicas de FastAPI para parámetros como `Body`, `Query`, `Form`, etc. con modelos de Pydantic v1, puedes importarlas de `fastapi.temp_pydantic_v1_params` mientras terminas la migración a Pydantic v2:
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *}
+
+### Migra por pasos { #migrate-in-steps }
+
+/// tip | Consejo
+
+Primero prueba con `bump-pydantic`, si tus tests pasan y eso funciona, entonces terminaste con un solo comando. ✨
+
+///
+
+Si `bump-pydantic` no funciona para tu caso de uso, puedes usar la compatibilidad de modelos Pydantic v1 y v2 en la misma app para hacer la migración a Pydantic v2 de forma gradual.
+
+Podrías primero actualizar Pydantic para usar la última versión 2, y cambiar los imports para usar `pydantic.v1` para todos tus modelos.
+
+Luego puedes empezar a migrar tus modelos de Pydantic v1 a v2 por grupos, en pasos graduales. 🚶
diff --git a/docs/es/docs/how-to/separate-openapi-schemas.md b/docs/es/docs/how-to/separate-openapi-schemas.md
index b779158511..903313599d 100644
--- a/docs/es/docs/how-to/separate-openapi-schemas.md
+++ b/docs/es/docs/how-to/separate-openapi-schemas.md
@@ -1,18 +1,18 @@
-# Separación de Esquemas OpenAPI para Entrada y Salida o No
+# Separación de Esquemas OpenAPI para Entrada y Salida o No { #separate-openapi-schemas-for-input-and-output-or-not }
-Al usar **Pydantic v2**, el OpenAPI generado es un poco más exacto y **correcto** que antes. 😎
+Desde que se lanzó **Pydantic v2**, el OpenAPI generado es un poco más exacto y **correcto** que antes. 😎
De hecho, en algunos casos, incluso tendrá **dos JSON Schemas** en OpenAPI para el mismo modelo Pydantic, para entrada y salida, dependiendo de si tienen **valores por defecto**.
Veamos cómo funciona eso y cómo cambiarlo si necesitas hacerlo.
-## Modelos Pydantic para Entrada y Salida
+## Modelos Pydantic para Entrada y Salida { #pydantic-models-for-input-and-output }
Digamos que tienes un modelo Pydantic con valores por defecto, como este:
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *}
-### Modelo para Entrada
+### Modelo para Entrada { #model-for-input }
Si usas este modelo como entrada, como aquí:
@@ -20,7 +20,7 @@ Si usas este modelo como entrada, como aquí:
...entonces el campo `description` **no será requerido**. Porque tiene un valor por defecto de `None`.
-### Modelo de Entrada en la Documentación
+### Modelo de Entrada en la Documentación { #input-model-in-docs }
Puedes confirmar eso en la documentación, el campo `description` no tiene un **asterisco rojo**, no está marcado como requerido:
@@ -28,7 +28,7 @@ Puedes confirmar eso en la documentación, el campo `description` no tiene un **
-### Modelo para Salida
+### Modelo para Salida { #model-for-output }
Pero si usas el mismo modelo como salida, como aquí:
@@ -36,7 +36,7 @@ Pero si usas el mismo modelo como salida, como aquí:
...entonces, porque `description` tiene un valor por defecto, si **no devuelves nada** para ese campo, aún tendrá ese **valor por defecto**.
-### Modelo para Datos de Response de Salida
+### Modelo para Datos de Response de Salida { #model-for-output-response-data }
Si interactúas con la documentación y revisas el response, aunque el código no agregó nada en uno de los campos `description`, el response JSON contiene el valor por defecto (`null`):
@@ -46,7 +46,7 @@ Si interactúas con la documentación y revisas el response, aunque el código n
Esto significa que **siempre tendrá un valor**, solo que a veces el valor podría ser `None` (o `null` en JSON).
-Eso significa que, los clientes que usan tu API no tienen que comprobar si el valor existe o no, pueden **asumir que el campo siempre estará allí**, pero solo que en algunos casos tendrá el valor por defecto de `None`.
+Eso significa que, los clientes que usan tu API no tienen que revisar si el valor existe o no, pueden **asumir que el campo siempre estará allí**, pero solo que en algunos casos tendrá el valor por defecto de `None`.
La forma de describir esto en OpenAPI es marcar ese campo como **requerido**, porque siempre estará allí.
@@ -55,7 +55,7 @@ Debido a eso, el JSON Schema para un modelo puede ser diferente dependiendo de s
* para **entrada** el `description` **no será requerido**
* para **salida** será **requerido** (y posiblemente `None`, o en términos de JSON, `null`)
-### Modelo para Salida en la Documentación
+### Modelo para Salida en la Documentación { #model-for-output-in-docs }
También puedes revisar el modelo de salida en la documentación, **ambos** `name` y `description` están marcados como **requeridos** con un **asterisco rojo**:
@@ -63,7 +63,7 @@ También puedes revisar el modelo de salida en la documentación, **ambos** `nam
-### Modelo para Entrada y Salida en la Documentación
+### Modelo para Entrada y Salida en la Documentación { #model-for-input-and-output-in-docs }
Y si revisas todos los esquemas disponibles (JSON Schemas) en OpenAPI, verás que hay dos, uno `Item-Input` y uno `Item-Output`.
@@ -77,7 +77,7 @@ Pero para `Item-Output`, `description` **es requerido**, tiene un asterisco rojo
Con esta funcionalidad de **Pydantic v2**, la documentación de tu API es más **precisa**, y si tienes clientes y SDKs autogenerados, también serán más precisos, con una mejor **experiencia para desarrolladores** y consistencia. 🎉
-## No Separar Esquemas
+## No Separar Esquemas { #do-not-separate-schemas }
Ahora, hay algunos casos donde podrías querer tener el **mismo esquema para entrada y salida**.
@@ -85,7 +85,7 @@ Probablemente el caso principal para esto es si ya tienes algún código cliente
En ese caso, puedes desactivar esta funcionalidad en **FastAPI**, con el parámetro `separate_input_output_schemas=False`.
-/// info | Información
+/// info
El soporte para `separate_input_output_schemas` fue agregado en FastAPI `0.102.0`. 🤓
@@ -93,12 +93,10 @@ El soporte para `separate_input_output_schemas` fue agregado en FastAPI `0.102.0
{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *}
-### Mismo Esquema para Modelos de Entrada y Salida en la Documentación
+### Mismo Esquema para Modelos de Entrada y Salida en la Documentación { #same-schema-for-input-and-output-models-in-docs }
Y ahora habrá un único esquema para entrada y salida para el modelo, solo `Item`, y tendrá `description` como **no requerido**:
+
FastAPI framework, alto rendimiento, fácil de aprender, rápido de programar, listo para producción
@@ -27,7 +27,7 @@
---
-**Documentación**: https://fastapi.tiangolo.com
+**Documentación**: https://fastapi.tiangolo.com
**Código Fuente**: https://github.com/fastapi/fastapi
@@ -35,12 +35,12 @@
FastAPI es un framework web moderno, rápido (de alto rendimiento), para construir APIs con Python basado en las anotaciones de tipos estándar de Python.
-Las características clave son:
+Las funcionalidades clave son:
* **Rápido**: Muy alto rendimiento, a la par con **NodeJS** y **Go** (gracias a Starlette y Pydantic). [Uno de los frameworks Python más rápidos disponibles](#performance).
* **Rápido de programar**: Aumenta la velocidad para desarrollar funcionalidades en aproximadamente un 200% a 300%. *
* **Menos bugs**: Reduce en aproximadamente un 40% los errores inducidos por humanos (desarrolladores). *
-* **Intuitivo**: Gran soporte para editores. Autocompletado en todas partes. Menos tiempo depurando.
+* **Intuitivo**: Gran soporte para editores. Autocompletado en todas partes. Menos tiempo depurando.
* **Fácil**: Diseñado para ser fácil de usar y aprender. Menos tiempo leyendo documentación.
* **Corto**: Minimiza la duplicación de código. Múltiples funcionalidades desde cada declaración de parámetro. Menos bugs.
* **Robusto**: Obtén código listo para producción. Con documentación interactiva automática.
@@ -48,24 +48,30 @@ Las características clave son:
* estimación basada en pruebas con un equipo de desarrollo interno, construyendo aplicaciones de producción.
-## Sponsors
+## Sponsors { #sponsors }
-{% if sponsors %}
+### Sponsor Keystone { #keystone-sponsor }
+
+{% for sponsor in sponsors.keystone -%}
+
+{% endfor -%}
+
+### Sponsors Oro y Plata { #gold-and-silver-sponsors }
+
{% for sponsor in sponsors.gold -%}
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
{% endfor %}
-{% endif %}
-Otros sponsors
+Otros sponsors
-## Opiniones
+## Opiniones { #opinions }
"_[...] Estoy usando **FastAPI** un montón estos días. [...] De hecho, estoy planeando usarlo para todos los servicios de **ML de mi equipo en Microsoft**. Algunos de ellos se están integrando en el núcleo del producto **Windows** y algunos productos de **Office**._"
@@ -87,13 +93,13 @@ Las características clave son:
"_Estoy súper emocionado con **FastAPI**. ¡Es tan divertido!_"
-
+
+## **Typer**, el FastAPI de las CLIs { #typer-the-fastapi-of-clis }
httpx - Requerido si deseas usar el `TestClient`.
-* jinja2 - Requerido si deseas usar la configuración de plantilla predeterminada.
+* jinja2 - Requerido si deseas usar la configuración de plantilla por defecto.
* python-multipart - Requerido si deseas soportar "parsing" de forms, con `request.form()`.
-Usadas por FastAPI / Starlette:
+Usadas por FastAPI:
* uvicorn - para el servidor que carga y sirve tu aplicación. Esto incluye `uvicorn[standard]`, que incluye algunas dependencias (por ejemplo, `uvloop`) necesarias para servir con alto rendimiento.
-* `fastapi-cli` - para proporcionar el comando `fastapi`.
+* `fastapi-cli[standard]` - para proporcionar el comando `fastapi`.
+ * Esto incluye `fastapi-cloud-cli`, que te permite desplegar tu aplicación de FastAPI en FastAPI Cloud.
-### Sin Dependencias `standard`
+### Sin Dependencias `standard` { #without-standard-dependencies }
Si no deseas incluir las dependencias opcionales `standard`, puedes instalar con `pip install fastapi` en lugar de `pip install "fastapi[standard]"`.
-### Dependencias Opcionales Adicionales
+### Sin `fastapi-cloud-cli` { #without-fastapi-cloud-cli }
+
+Si quieres instalar FastAPI con las dependencias standard pero sin `fastapi-cloud-cli`, puedes instalar con `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
+
+### Dependencias Opcionales Adicionales { #additional-optional-dependencies }
Existen algunas dependencias adicionales que podrías querer instalar.
@@ -491,6 +560,6 @@ Dependencias opcionales adicionales de FastAPI:
* orjson - Requerido si deseas usar `ORJSONResponse`.
* ujson - Requerido si deseas usar `UJSONResponse`.
-## Licencia
+## Licencia { #license }
Este proyecto tiene licencia bajo los términos de la licencia MIT.
diff --git a/docs/es/docs/learn/index.md b/docs/es/docs/learn/index.md
index cc6c7cc3f0..4333bfcf6e 100644
--- a/docs/es/docs/learn/index.md
+++ b/docs/es/docs/learn/index.md
@@ -1,4 +1,4 @@
-# Aprende
+# Aprende { #learn }
Aquí están las secciones introductorias y los tutoriales para aprender **FastAPI**.
diff --git a/docs/es/docs/project-generation.md b/docs/es/docs/project-generation.md
index 5599951511..b4aa11d0dd 100644
--- a/docs/es/docs/project-generation.md
+++ b/docs/es/docs/project-generation.md
@@ -1,4 +1,4 @@
-# Plantilla Full Stack FastAPI
+# Plantilla Full Stack FastAPI { #full-stack-fastapi-template }
Las plantillas, aunque normalmente vienen con una configuración específica, están diseñadas para ser flexibles y personalizables. Esto te permite modificarlas y adaptarlas a los requisitos de tu proyecto, haciéndolas un excelente punto de partida. 🏁
@@ -6,23 +6,23 @@ Puedes usar esta plantilla para comenzar, ya que incluye gran parte de la config
Repositorio de GitHub: Plantilla Full Stack FastAPI
-## Plantilla Full Stack FastAPI - Tecnología y Funcionalidades
+## Plantilla Full Stack FastAPI - Tecnología y Funcionalidades { #full-stack-fastapi-template-technology-stack-and-features }
-- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) para la API del backend en Python.
+- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/es) para la API del backend en Python.
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) para las interacciones con bases de datos SQL en Python (ORM).
- 🔍 [Pydantic](https://docs.pydantic.dev), utilizado por FastAPI, para la validación de datos y gestión de configuraciones.
- 💾 [PostgreSQL](https://www.postgresql.org) como base de datos SQL.
- 🚀 [React](https://react.dev) para el frontend.
- - 💃 Usando TypeScript, hooks, [Vite](https://vitejs.dev), y otras partes de una stack moderna de frontend.
- - 🎨 [Chakra UI](https://chakra-ui.com) para los componentes del frontend.
+ - 💃 Usando TypeScript, hooks, Vite, y otras partes de una stack moderna de frontend.
+ - 🎨 [Tailwind CSS](https://tailwindcss.com) y [shadcn/ui](https://ui.shadcn.com) para los componentes del frontend.
- 🤖 Un cliente de frontend generado automáticamente.
- - 🧪 [Playwright](https://playwright.dev) para pruebas End-to-End.
+ - 🧪 [Playwright](https://playwright.dev) para escribir pruebas End-to-End.
- 🦇 Soporte para modo oscuro.
- 🐋 [Docker Compose](https://www.docker.com) para desarrollo y producción.
- 🔒 Hashing seguro de contraseñas por defecto.
- 🔑 Autenticación con tokens JWT.
- 📫 Recuperación de contraseñas basada en email.
- ✅ Pruebas con [Pytest](https://pytest.org).
-- 📞 [Traefik](https://traefik.io) como proxy inverso / balanceador de carga.
+- 📞 [Traefik](https://traefik.io) como proxy inverso / load balancer.
- 🚢 Instrucciones de despliegue usando Docker Compose, incluyendo cómo configurar un proxy Traefik frontend para manejar certificados HTTPS automáticos.
- 🏭 CI (integración continua) y CD (despliegue continuo) basados en GitHub Actions.
diff --git a/docs/es/docs/python-types.md b/docs/es/docs/python-types.md
index 769204f8fd..60b50a08ff 100644
--- a/docs/es/docs/python-types.md
+++ b/docs/es/docs/python-types.md
@@ -1,4 +1,4 @@
-# Introducción a Tipos en Python
+# Introducción a Tipos en Python { #python-types-intro }
Python tiene soporte para "anotaciones de tipos" opcionales (también llamadas "type hints").
@@ -18,11 +18,11 @@ Si eres un experto en Python, y ya sabes todo sobre las anotaciones de tipos, sa
///
-## Motivación
+## Motivación { #motivation }
Comencemos con un ejemplo simple:
-{* ../../docs_src/python_types/tutorial001.py *}
+{* ../../docs_src/python_types/tutorial001_py39.py *}
Llamar a este programa genera:
@@ -36,9 +36,9 @@ La función hace lo siguiente:
* Convierte la primera letra de cada uno a mayúsculas con `title()`.
* Concatena ambos con un espacio en el medio.
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
+{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
-### Edítalo
+### Edítalo { #edit-it }
Es un programa muy simple.
@@ -58,7 +58,7 @@ Pero, tristemente, no obtienes nada útil:
-### Añadir tipos
+### Añadir tipos { #add-types }
Modifiquemos una sola línea de la versión anterior.
@@ -78,7 +78,7 @@ Eso es todo.
Esas son las "anotaciones de tipos":
-{* ../../docs_src/python_types/tutorial002.py hl[1] *}
+{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
Eso no es lo mismo que declarar valores predeterminados como sería con:
@@ -102,11 +102,11 @@ Con eso, puedes desplazarte, viendo las opciones, hasta que encuentres la que "t
-## Más motivación
+## Más motivación { #more-motivation }
Revisa esta función, ya tiene anotaciones de tipos:
-{* ../../docs_src/python_types/tutorial003.py hl[1] *}
+{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}
Porque el editor conoce los tipos de las variables, no solo obtienes autocompletado, también obtienes chequeo de errores:
@@ -114,15 +114,15 @@ Porque el editor conoce los tipos de las variables, no solo obtienes autocomplet
Ahora sabes que debes corregirlo, convertir `age` a un string con `str(age)`:
-{* ../../docs_src/python_types/tutorial004.py hl[2] *}
+{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
-## Declaración de tipos
+## Declaración de tipos { #declaring-types }
Acabas de ver el lugar principal para declarar anotaciones de tipos. Como parámetros de función.
Este también es el lugar principal donde los utilizarías con **FastAPI**.
-### Tipos simples
+### Tipos simples { #simple-types }
Puedes declarar todos los tipos estándar de Python, no solo `str`.
@@ -133,9 +133,9 @@ Puedes usar, por ejemplo:
* `bool`
* `bytes`
-{* ../../docs_src/python_types/tutorial005.py hl[1] *}
+{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
-### Tipos genéricos con parámetros de tipo
+### Tipos genéricos con parámetros de tipo { #generic-types-with-type-parameters }
Hay algunas estructuras de datos que pueden contener otros valores, como `dict`, `list`, `set` y `tuple`. Y los valores internos también pueden tener su propio tipo.
@@ -143,7 +143,7 @@ Estos tipos que tienen tipos internos se denominan tipos "**genéricos**". Y es
Para declarar esos tipos y los tipos internos, puedes usar el módulo estándar de Python `typing`. Existe específicamente para soportar estas anotaciones de tipos.
-#### Versiones más recientes de Python
+#### Versiones más recientes de Python { #newer-versions-of-python }
La sintaxis que utiliza `typing` es **compatible** con todas las versiones, desde Python 3.6 hasta las versiones más recientes, incluyendo Python 3.9, Python 3.10, etc.
@@ -157,60 +157,28 @@ Por ejemplo, "**Python 3.6+**" significa que es compatible con Python 3.6 o supe
Si puedes usar las **últimas versiones de Python**, utiliza los ejemplos para la última versión, esos tendrán la **mejor y más simple sintaxis**, por ejemplo, "**Python 3.10+**".
-#### Lista
+#### Lista { #list }
Por ejemplo, vamos a definir una variable para ser una `list` de `str`.
-//// tab | Python 3.9+
-
Declara la variable, con la misma sintaxis de dos puntos (`:`).
Como tipo, pon `list`.
Como la lista es un tipo que contiene algunos tipos internos, los pones entre corchetes:
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-De `typing`, importa `List` (con una `L` mayúscula):
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-Declara la variable, con la misma sintaxis de dos puntos (`:`).
-
-Como tipo, pon el `List` que importaste de `typing`.
-
-Como la lista es un tipo que contiene algunos tipos internos, los pones entre corchetes:
-
-```Python hl_lines="4"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
/// info | Información
Esos tipos internos en los corchetes se denominan "parámetros de tipo".
-En este caso, `str` es el parámetro de tipo pasado a `List` (o `list` en Python 3.9 y superior).
+En este caso, `str` es el parámetro de tipo pasado a `list`.
///
Eso significa: "la variable `items` es una `list`, y cada uno de los ítems en esta lista es un `str`".
-/// tip | Consejo
-
-Si usas Python 3.9 o superior, no tienes que importar `List` de `typing`, puedes usar el mismo tipo `list` regular en su lugar.
-
-///
-
Al hacer eso, tu editor puede proporcionar soporte incluso mientras procesa elementos de la lista:
@@ -221,32 +189,18 @@ Nota que la variable `item` es uno de los elementos en la lista `items`.
Y aún así, el editor sabe que es un `str` y proporciona soporte para eso.
-#### Tuple y Set
+#### Tuple y Set { #tuple-and-set }
Harías lo mismo para declarar `tuple`s y `set`s:
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial007_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial007.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
Esto significa:
* La variable `items_t` es un `tuple` con 3 ítems, un `int`, otro `int`, y un `str`.
* La variable `items_s` es un `set`, y cada uno de sus ítems es del tipo `bytes`.
-#### Dict
+#### Dict { #dict }
Para definir un `dict`, pasas 2 parámetros de tipo, separados por comas.
@@ -254,21 +208,7 @@ El primer parámetro de tipo es para las claves del `dict`.
El segundo parámetro de tipo es para los valores del `dict`:
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
Esto significa:
@@ -276,7 +216,7 @@ Esto significa:
* Las claves de este `dict` son del tipo `str` (digamos, el nombre de cada ítem).
* Los valores de este `dict` son del tipo `float` (digamos, el precio de cada ítem).
-#### Union
+#### Union { #union }
Puedes declarar que una variable puede ser cualquier de **varios tipos**, por ejemplo, un `int` o un `str`.
@@ -292,24 +232,24 @@ En Python 3.10 también hay una **nueva sintaxis** donde puedes poner los posibl
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008b.py!}
+{!> ../../docs_src/python_types/tutorial008b_py39.py!}
```
////
En ambos casos, esto significa que `item` podría ser un `int` o un `str`.
-#### Posiblemente `None`
+#### Posiblemente `None` { #possibly-none }
Puedes declarar que un valor podría tener un tipo, como `str`, pero que también podría ser `None`.
En Python 3.6 y posteriores (incluyendo Python 3.10) puedes declararlo importando y usando `Optional` del módulo `typing`.
```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
+{!../../docs_src/python_types/tutorial009_py39.py!}
```
Usar `Optional[str]` en lugar de solo `str` te permitirá al editor ayudarte a detectar errores donde podrías estar asumiendo que un valor siempre es un `str`, cuando en realidad también podría ser `None`.
@@ -326,23 +266,23 @@ Esto también significa que en Python 3.10, puedes usar `Something | None`:
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009.py!}
+{!> ../../docs_src/python_types/tutorial009_py39.py!}
```
////
-//// tab | Python 3.8+ alternative
+//// tab | Python 3.9+ alternativa
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009b.py!}
+{!> ../../docs_src/python_types/tutorial009b_py39.py!}
```
////
-#### Uso de `Union` u `Optional`
+#### Uso de `Union` u `Optional` { #using-union-or-optional }
Si estás usando una versión de Python inferior a 3.10, aquí tienes un consejo desde mi punto de vista muy **subjetivo**:
@@ -357,7 +297,7 @@ Se trata solo de las palabras y nombres. Pero esas palabras pueden afectar cómo
Como ejemplo, tomemos esta función:
-{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *}
+{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
El parámetro `name` está definido como `Optional[str]`, pero **no es opcional**, no puedes llamar a la función sin el parámetro:
@@ -377,7 +317,7 @@ La buena noticia es que, una vez que estés en Python 3.10, no tendrás que preo
Y entonces no tendrás que preocuparte por nombres como `Optional` y `Union`. 😎
-#### Tipos genéricos
+#### Tipos genéricos { #generic-types }
Estos tipos que toman parámetros de tipo en corchetes se llaman **Tipos Genéricos** o **Genéricos**, por ejemplo:
@@ -390,10 +330,10 @@ Puedes usar los mismos tipos integrados como genéricos (con corchetes y tipos d
* `set`
* `dict`
-Y lo mismo que con Python 3.8, desde el módulo `typing`:
+Y, como con versiones anteriores de Python, desde el módulo `typing`:
* `Union`
-* `Optional` (lo mismo que con Python 3.8)
+* `Optional`
* ...y otros.
En Python 3.10, como alternativa a usar los genéricos `Union` y `Optional`, puedes usar la barra vertical (`|`) para declarar uniones de tipos, eso es mucho mejor y más simple.
@@ -409,7 +349,7 @@ Puedes usar los mismos tipos integrados como genéricos (con corchetes y tipos d
* `set`
* `dict`
-Y lo mismo que con Python 3.8, desde el módulo `typing`:
+Y generics desde el módulo `typing`:
* `Union`
* `Optional`
@@ -417,29 +357,17 @@ Y lo mismo que con Python 3.8, desde el módulo `typing`:
////
-//// tab | Python 3.8+
-
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Union`
-* `Optional`
-* ...y otros.
-
-////
-
-### Clases como tipos
+### Clases como tipos { #classes-as-types }
También puedes declarar una clase como el tipo de una variable.
Digamos que tienes una clase `Person`, con un nombre:
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
+{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
Luego puedes declarar una variable para que sea de tipo `Person`:
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
+{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
Y luego, nuevamente, obtienes todo el soporte del editor:
@@ -449,7 +377,7 @@ Nota que esto significa "`one_person` es una **instance** de la clase `Person`".
No significa "`one_person` es la **clase** llamada `Person`".
-## Modelos Pydantic
+## Modelos Pydantic { #pydantic-models }
Pydantic es un paquete de Python para realizar la validación de datos.
@@ -463,29 +391,7 @@ Y obtienes todo el soporte del editor con ese objeto resultante.
Un ejemplo de la documentación oficial de Pydantic:
-//// tab | Python 3.10+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py310.py!}
-```
-
-////
-
-//// tab | Python 3.9+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial011_py310.py *}
/// info | Información
@@ -503,31 +409,13 @@ Pydantic tiene un comportamiento especial cuando utilizas `Optional` o `Union[So
///
-## Anotaciones de tipos con metadata
+## Anotaciones de tipos con metadata { #type-hints-with-metadata-annotations }
-Python también tiene una funcionalidad que permite poner **metadata adicional** en estas anotaciones de tipos usando `Annotated`.
+Python también tiene una funcionalidad que permite poner **metadatos adicional** en estas anotaciones de tipos usando `Annotated`.
-//// tab | Python 3.9+
+Desde Python 3.9, `Annotated` es parte de la standard library, así que puedes importarlo desde `typing`.
-En Python 3.9, `Annotated` es parte de la librería estándar, así que puedes importarlo desde `typing`.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-En versiones por debajo de Python 3.9, importas `Annotated` de `typing_extensions`.
-
-Ya estará instalado con **FastAPI**.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
Python en sí no hace nada con este `Annotated`. Y para los editores y otras herramientas, el tipo sigue siendo `str`.
@@ -547,7 +435,7 @@ Y también que tu código será muy compatible con muchas otras herramientas y p
///
-## Anotaciones de tipos en **FastAPI**
+## Anotaciones de tipos en **FastAPI** { #type-hints-in-fastapi }
**FastAPI** aprovecha estas anotaciones de tipos para hacer varias cosas.
diff --git a/docs/es/docs/resources/index.md b/docs/es/docs/resources/index.md
index 92898d319e..3240095611 100644
--- a/docs/es/docs/resources/index.md
+++ b/docs/es/docs/resources/index.md
@@ -1,3 +1,3 @@
-# Recursos
+# Recursos { #resources }
-Recursos adicionales, enlaces externos, artículos y más. ✈️
+Recursos adicionales, enlaces externos y más. ✈️
diff --git a/docs/es/docs/tutorial/background-tasks.md b/docs/es/docs/tutorial/background-tasks.md
index 783db20a4b..cc8a2c9cbe 100644
--- a/docs/es/docs/tutorial/background-tasks.md
+++ b/docs/es/docs/tutorial/background-tasks.md
@@ -1,4 +1,4 @@
-# Tareas en Segundo Plano
+# Tareas en Segundo Plano { #background-tasks }
Puedes definir tareas en segundo plano para que se ejecuten *después* de devolver un response.
@@ -11,15 +11,15 @@ Esto incluye, por ejemplo:
* Procesamiento de datos:
* Por ejemplo, supongamos que recibes un archivo que debe pasar por un proceso lento, puedes devolver un response de "Accepted" (HTTP 202) y procesar el archivo en segundo plano.
-## Usando `BackgroundTasks`
+## Usando `BackgroundTasks` { #using-backgroundtasks }
Primero, importa `BackgroundTasks` y define un parámetro en tu *path operation function* con una declaración de tipo de `BackgroundTasks`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
**FastAPI** creará el objeto de tipo `BackgroundTasks` por ti y lo pasará como ese parámetro.
-## Crear una función de tarea
+## Crear una función de tarea { #create-a-task-function }
Crea una función para que se ejecute como la tarea en segundo plano.
@@ -31,13 +31,13 @@ En este caso, la función de tarea escribirá en un archivo (simulando el envío
Y como la operación de escritura no usa `async` y `await`, definimos la función con un `def` normal:
-{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
-## Agregar la tarea en segundo plano
+## Agregar la tarea en segundo plano { #add-the-background-task }
Dentro de tu *path operation function*, pasa tu función de tarea al objeto de *background tasks* con el método `.add_task()`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
`.add_task()` recibe como argumentos:
@@ -45,7 +45,7 @@ Dentro de tu *path operation function*, pasa tu función de tarea al objeto de *
* Cualquier secuencia de argumentos que deba pasarse a la función de tarea en orden (`email`).
* Cualquier argumento de palabras clave que deba pasarse a la función de tarea (`message="some notification"`).
-## Inyección de Dependencias
+## Inyección de Dependencias { #dependency-injection }
Usar `BackgroundTasks` también funciona con el sistema de inyección de dependencias, puedes declarar un parámetro de tipo `BackgroundTasks` en varios niveles: en una *path operation function*, en una dependencia (dependable), en una sub-dependencia, etc.
@@ -59,7 +59,7 @@ Si hay un query en el request, se escribirá en el log en una tarea en segundo p
Y luego otra tarea en segundo plano generada en la *path operation function* escribirá un mensaje usando el parámetro de path `email`.
-## Detalles Técnicos
+## Detalles Técnicos { #technical-details }
La clase `BackgroundTasks` proviene directamente de `starlette.background`.
@@ -71,7 +71,7 @@ Todavía es posible usar `BackgroundTask` solo en FastAPI, pero debes crear el o
Puedes ver más detalles en la documentación oficial de Starlette sobre Background Tasks.
-## Advertencia
+## Advertencia { #caveat }
Si necesitas realizar una computación intensa en segundo plano y no necesariamente necesitas que se ejecute por el mismo proceso (por ejemplo, no necesitas compartir memoria, variables, etc.), podrías beneficiarte del uso de otras herramientas más grandes como Celery.
@@ -79,6 +79,6 @@ Tienden a requerir configuraciones más complejas, un gestor de cola de mensajes
Pero si necesitas acceder a variables y objetos de la misma app de **FastAPI**, o necesitas realizar pequeñas tareas en segundo plano (como enviar una notificación por email), simplemente puedes usar `BackgroundTasks`.
-## Resumen
+## Resumen { #recap }
Importa y usa `BackgroundTasks` con parámetros en *path operation functions* y dependencias para agregar tareas en segundo plano.
diff --git a/docs/es/docs/tutorial/bigger-applications.md b/docs/es/docs/tutorial/bigger-applications.md
index c3d8f06860..7938a12158 100644
--- a/docs/es/docs/tutorial/bigger-applications.md
+++ b/docs/es/docs/tutorial/bigger-applications.md
@@ -1,4 +1,4 @@
-# Aplicaciones más grandes - Múltiples archivos
+# Aplicaciones más grandes - Múltiples archivos { #bigger-applications-multiple-files }
Si estás construyendo una aplicación o una API web, rara vez podrás poner todo en un solo archivo.
@@ -10,7 +10,7 @@ Si vienes de Flask, esto sería el equivalente a los Blueprints de Flask.
///
-## Un ejemplo de estructura de archivos
+## Un ejemplo de estructura de archivos { #an-example-file-structure }
Digamos que tienes una estructura de archivos como esta:
@@ -56,7 +56,7 @@ from app.routers import items
La misma estructura de archivos con comentarios:
-```
+```bash
.
├── app # "app" es un paquete de Python
│ ├── __init__.py # este archivo hace que "app" sea un "paquete de Python"
@@ -71,7 +71,7 @@ La misma estructura de archivos con comentarios:
│ └── admin.py # submódulo "admin", por ejemplo import app.internal.admin
```
-## `APIRouter`
+## `APIRouter` { #apirouter }
Digamos que el archivo dedicado solo a manejar usuarios es el submódulo en `/app/routers/users.py`.
@@ -81,23 +81,19 @@ Pero todavía es parte de la misma aplicación/web API de **FastAPI** (es parte
Puedes crear las *path operations* para ese módulo usando `APIRouter`.
-### Importar `APIRouter`
+### Importar `APIRouter` { #import-apirouter }
Lo importas y creas una "instance" de la misma manera que lo harías con la clase `FastAPI`:
-```Python hl_lines="1 3" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
-### *Path operations* con `APIRouter`
+### *Path operations* con `APIRouter` { #path-operations-with-apirouter }
Y luego lo usas para declarar tus *path operations*.
Úsalo de la misma manera que usarías la clase `FastAPI`:
-```Python hl_lines="6 11 16" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
Puedes pensar en `APIRouter` como una clase "mini `FastAPI`".
@@ -113,53 +109,25 @@ En este ejemplo, la variable se llama `router`, pero puedes nombrarla como quier
Vamos a incluir este `APIRouter` en la aplicación principal de `FastAPI`, pero primero, revisemos las dependencias y otro `APIRouter`.
-## Dependencias
+## Dependencias { #dependencies }
Vemos que vamos a necesitar algunas dependencias usadas en varios lugares de la aplicación.
Así que las ponemos en su propio módulo `dependencies` (`app/dependencies.py`).
-Ahora utilizaremos una dependencia simple para leer un encabezado `X-Token` personalizado:
+Ahora utilizaremos una dependencia simple para leer un header `X-Token` personalizado:
-//// tab | Python 3.9+
-
-```Python hl_lines="3 6-8" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 5-7" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app_an/dependencies.py!}
-```
-
-////
-
-//// tab | Python 3.8+ non-Annotated
+{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
/// tip | Consejo
-Preferiblemente usa la versión `Annotated` si es posible.
-
-///
-
-```Python hl_lines="1 4-6" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app/dependencies.py!}
-```
-
-////
-
-/// tip | Consejo
-
-Estamos usando un encabezado inventado para simplificar este ejemplo.
+Estamos usando un header inventado para simplificar este ejemplo.
Pero en casos reales obtendrás mejores resultados usando las [utilidades de Seguridad](security/index.md){.internal-link target=_blank} integradas.
///
-## Otro módulo con `APIRouter`
+## Otro módulo con `APIRouter` { #another-module-with-apirouter }
Digamos que también tienes los endpoints dedicados a manejar "items" de tu aplicación en el módulo `app/routers/items.py`.
@@ -181,9 +149,7 @@ Sabemos que todas las *path operations* en este módulo tienen el mismo:
Entonces, en lugar de agregar todo eso a cada *path operation*, podemos agregarlo al `APIRouter`.
-```Python hl_lines="5-10 16 21" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
Como el path de cada *path operation* tiene que empezar con `/`, como en:
@@ -219,7 +185,7 @@ El resultado final es que los paths de item son ahora:
* Todos incluirán las `responses` predefinidas.
* Todas estas *path operations* tendrán la lista de `dependencies` evaluadas/ejecutadas antes de ellas.
* Si también declaras dependencias en una *path operation* específica, **también se ejecutarán**.
- * Las dependencias del router se ejecutan primero, luego las [dependencias en el decorador](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, y luego las dependencias de parámetros normales.
+ * Las dependencias del router se ejecutan primero, luego las [`dependencies` en el decorador](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, y luego las dependencias de parámetros normales.
* También puedes agregar [dependencias de `Security` con `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
/// tip | Consejo
@@ -234,7 +200,7 @@ Los parámetros `prefix`, `tags`, `responses`, y `dependencies` son (como en muc
///
-### Importar las dependencias
+### Importar las dependencias { #import-the-dependencies }
Este código vive en el módulo `app.routers.items`, el archivo `app/routers/items.py`.
@@ -242,15 +208,13 @@ Y necesitamos obtener la función de dependencia del módulo `app.dependencies`,
Así que usamos un import relativo con `..` para las dependencias:
-```Python hl_lines="3" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
-#### Cómo funcionan los imports relativos
+#### Cómo funcionan los imports relativos { #how-relative-imports-work }
/// tip | Consejo
-Si sabes perfectamente cómo funcionan los imports, continúa a la siguiente sección.
+Si sabes perfectamente cómo funcionan los imports, continúa a la siguiente sección abajo.
///
@@ -307,27 +271,25 @@ eso significaría:
Eso se referiría a algún paquete arriba de `app/`, con su propio archivo `__init__.py`, etc. Pero no tenemos eso. Así que, eso lanzaría un error en nuestro ejemplo. 🚨
-Pero ahora sabes cómo funciona, para que puedas usar imports relativos en tus propias aplicaciones sin importar cuán complejas sean. 🤓
+Pero ahora sabes cómo funciona, para que puedas usar imports relativos en tus propias apps sin importar cuán complejas sean. 🤓
-### Agregar algunos `tags`, `responses`, y `dependencies` personalizados
+### Agregar algunos `tags`, `responses`, y `dependencies` personalizados { #add-some-custom-tags-responses-and-dependencies }
No estamos agregando el prefijo `/items` ni los `tags=["items"]` a cada *path operation* porque los hemos añadido al `APIRouter`.
Pero aún podemos agregar _más_ `tags` que se aplicarán a una *path operation* específica, y también algunas `responses` extra específicas para esa *path operation*:
-```Python hl_lines="30-31" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *}
/// tip | Consejo
-Esta última *path operation* tendrá la combinación de tags: `["items", "custom"]`.
+Esta última path operation tendrá la combinación de tags: `["items", "custom"]`.
Y también tendrá ambas responses en la documentación, una para `404` y otra para `403`.
///
-## El `FastAPI` principal
+## El `FastAPI` principal { #the-main-fastapi }
Ahora, veamos el módulo en `app/main.py`.
@@ -335,27 +297,25 @@ Aquí es donde importas y usas la clase `FastAPI`.
Este será el archivo principal en tu aplicación que conecta todo.
-### Importar `FastAPI`
+Y como la mayor parte de tu lógica ahora vivirá en su propio módulo específico, el archivo principal será bastante simple.
-Importas y creas una clase `FastAPI` como de costumbre.
+### Importar `FastAPI` { #import-fastapi }
+
+Importas y creas una clase `FastAPI` como normalmente.
Y podemos incluso declarar [dependencias globales](dependencies/global-dependencies.md){.internal-link target=_blank} que se combinarán con las dependencias para cada `APIRouter`:
-```Python hl_lines="1 3 7" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
-### Importar el `APIRouter`
+### Importar el `APIRouter` { #import-the-apirouter }
Ahora importamos los otros submódulos que tienen `APIRouter`s:
-```Python hl_lines="4-5" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
Como los archivos `app/routers/users.py` y `app/routers/items.py` son submódulos que son parte del mismo paquete de Python `app`, podemos usar un solo punto `.` para importarlos usando "imports relativos".
-### Cómo funciona la importación
+### Cómo funciona la importación { #how-the-importing-works }
La sección:
@@ -397,7 +357,7 @@ Para aprender más sobre Paquetes y Módulos de Python, lee
@@ -531,11 +481,11 @@ $ fastapi dev app/main.py
Y abre la documentación en http://127.0.0.1:8000/docs.
-Verás la documentación automática de la API, incluyendo los paths de todos los submódulos, usando los paths correctos (y prefijos) y las tags correctas:
+Verás la documentación automática de la API, incluyendo los paths de todos los submódulos, usando los paths correctos (y prefijos) y los tags correctos:
-## Incluir el mismo router múltiples veces con diferentes `prefix`
+## Incluir el mismo router múltiples veces con diferentes `prefix` { #include-the-same-router-multiple-times-with-different-prefix }
También puedes usar `.include_router()` múltiples veces con el *mismo* router usando diferentes prefijos.
@@ -543,7 +493,7 @@ Esto podría ser útil, por ejemplo, para exponer la misma API bajo diferentes p
Este es un uso avanzado que quizás no necesites realmente, pero está allí en caso de que lo necesites.
-## Incluir un `APIRouter` en otro
+## Incluir un `APIRouter` en otro { #include-an-apirouter-in-another }
De la misma manera que puedes incluir un `APIRouter` en una aplicación `FastAPI`, puedes incluir un `APIRouter` en otro `APIRouter` usando:
@@ -551,4 +501,4 @@ De la misma manera que puedes incluir un `APIRouter` en una aplicación `FastAPI
router.include_router(other_router)
```
-Asegúrate de hacerlo antes de incluir `router` en la aplicación de `FastAPI`, para que las *path operations* de `other_router` también se incluyan.
+Asegúrate de hacerlo antes de incluir `router` en la app de `FastAPI`, para que las *path operations* de `other_router` también se incluyan.
diff --git a/docs/es/docs/tutorial/body-fields.md b/docs/es/docs/tutorial/body-fields.md
index d07d214ec8..8902ce4e94 100644
--- a/docs/es/docs/tutorial/body-fields.md
+++ b/docs/es/docs/tutorial/body-fields.md
@@ -1,8 +1,8 @@
-# Body - Campos
+# Body - Campos { #body-fields }
De la misma manera que puedes declarar validaciones adicionales y metadatos en los parámetros de las *path operation function* con `Query`, `Path` y `Body`, puedes declarar validaciones y metadatos dentro de los modelos de Pydantic usando `Field` de Pydantic.
-## Importar `Field`
+## Importar `Field` { #import-field }
Primero, tienes que importarlo:
@@ -14,7 +14,7 @@ Fíjate que `Field` se importa directamente desde `pydantic`, no desde `fastapi`
///
-## Declarar atributos del modelo
+## Declarar atributos del modelo { #declare-model-attributes }
Después puedes utilizar `Field` con los atributos del modelo:
@@ -40,7 +40,7 @@ Observa cómo cada atributo del modelo con un tipo, un valor por defecto y `Fiel
///
-## Agregar información extra
+## Agregar información extra { #add-extra-information }
Puedes declarar información extra en `Field`, `Query`, `Body`, etc. Y será incluida en el JSON Schema generado.
@@ -53,7 +53,7 @@ Como estas claves no necesariamente tienen que ser parte de la especificación d
///
-## Resumen
+## Resumen { #recap }
Puedes utilizar `Field` de Pydantic para declarar validaciones adicionales y metadatos para los atributos del modelo.
diff --git a/docs/es/docs/tutorial/body-multiple-params.md b/docs/es/docs/tutorial/body-multiple-params.md
index df6560b620..57cec16743 100644
--- a/docs/es/docs/tutorial/body-multiple-params.md
+++ b/docs/es/docs/tutorial/body-multiple-params.md
@@ -1,24 +1,22 @@
-# Cuerpo - Múltiples Parámetros
+# Cuerpo - Múltiples Parámetros { #body-multiple-parameters }
Ahora que hemos visto cómo usar `Path` y `Query`, veamos usos más avanzados de las declaraciones del request body.
-## Mezclar `Path`, `Query` y parámetros del cuerpo
+## Mezclar `Path`, `Query` y parámetros del cuerpo { #mix-path-query-and-body-parameters }
Primero, por supuesto, puedes mezclar las declaraciones de parámetros de `Path`, `Query` y del request body libremente y **FastAPI** sabrá qué hacer.
-Y también puedes declarar parámetros del cuerpo como opcionales, estableciendo el valor predeterminado a `None`:
+Y también puedes declarar parámetros del cuerpo como opcionales, estableciendo el valor por defecto a `None`:
{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *}
-## Múltiples parámetros del cuerpo
-
/// note | Nota
Ten en cuenta que, en este caso, el `item` que se tomaría del cuerpo es opcional. Ya que tiene un valor por defecto de `None`.
///
-## Múltiples parámetros del cuerpo
+## Múltiples parámetros del cuerpo { #multiple-body-parameters }
En el ejemplo anterior, las *path operations* esperarían un cuerpo JSON con los atributos de un `Item`, como:
@@ -64,7 +62,7 @@ Ten en cuenta que aunque el `item` se declaró de la misma manera que antes, aho
Realizará la validación de los datos compuestos, y los documentará así para el esquema de OpenAPI y la documentación automática.
-## Valores singulares en el cuerpo
+## Valores singulares en el cuerpo { #singular-values-in-body }
De la misma manera que hay un `Query` y `Path` para definir datos extra para parámetros de query y path, **FastAPI** proporciona un equivalente `Body`.
@@ -96,7 +94,7 @@ En este caso, **FastAPI** esperará un cuerpo como:
Nuevamente, convertirá los tipos de datos, validará, documentará, etc.
-## Múltiples parámetros de cuerpo y query
+## Múltiples parámetros de cuerpo y query { #multiple-body-params-and-query }
Por supuesto, también puedes declarar parámetros adicionales de query siempre que lo necesites, además de cualquier parámetro del cuerpo.
@@ -122,7 +120,7 @@ Por ejemplo:
///
-## Embeber un solo parámetro de cuerpo
+## Embeber un solo parámetro de cuerpo { #embed-a-single-body-parameter }
Supongamos que solo tienes un único parámetro de cuerpo `item` de un modelo Pydantic `Item`.
@@ -162,7 +160,7 @@ en lugar de:
}
```
-## Resumen
+## Resumen { #recap }
Puedes añadir múltiples parámetros de cuerpo a tu *path operation function*, aunque un request solo puede tener un único cuerpo.
diff --git a/docs/es/docs/tutorial/body-nested-models.md b/docs/es/docs/tutorial/body-nested-models.md
index 5b4cfc14c2..0dfd6576f1 100644
--- a/docs/es/docs/tutorial/body-nested-models.md
+++ b/docs/es/docs/tutorial/body-nested-models.md
@@ -1,8 +1,8 @@
-# Cuerpo - Modelos Anidados
+# Cuerpo - Modelos Anidados { #body-nested-models }
Con **FastAPI**, puedes definir, validar, documentar y usar modelos anidados de manera arbitraria (gracias a Pydantic).
-## Campos de lista
+## Campos de lista { #list-fields }
Puedes definir un atributo como un subtipo. Por ejemplo, una `list` en Python:
@@ -10,39 +10,19 @@ Puedes definir un atributo como un subtipo. Por ejemplo, una `list` en Python:
Esto hará que `tags` sea una lista, aunque no declare el tipo de los elementos de la lista.
-## Campos de lista con parámetro de tipo
+## Campos de lista con parámetro de tipo { #list-fields-with-type-parameter }
Pero Python tiene una forma específica de declarar listas con tipos internos, o "parámetros de tipo":
-### Importar `List` de typing
+### Declarar una `list` con un parámetro de tipo { #declare-a-list-with-a-type-parameter }
-En Python 3.9 y superior, puedes usar el `list` estándar para declarar estas anotaciones de tipo como veremos a continuación. 💡
-
-Pero en versiones de Python anteriores a 3.9 (desde 3.6 en adelante), primero necesitas importar `List` del módulo `typing` estándar de Python:
-
-{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
-
-### Declarar una `list` con un parámetro de tipo
-
-Para declarar tipos que tienen parámetros de tipo (tipos internos), como `list`, `dict`, `tuple`:
-
-* Si estás en una versión de Python inferior a 3.9, importa su versión equivalente del módulo `typing`
-* Pasa el/los tipo(s) interno(s) como "parámetros de tipo" usando corchetes: `[` y `]`
-
-En Python 3.9 sería:
+Para declarar tipos que tienen parámetros de tipo (tipos internos), como `list`, `dict`, `tuple`,
+pasa el/los tipo(s) interno(s) como "parámetros de tipo" usando corchetes: `[` y `]`
```Python
my_list: list[str]
```
-En versiones de Python anteriores a 3.9, sería:
-
-```Python
-from typing import List
-
-my_list: List[str]
-```
-
Eso es toda la sintaxis estándar de Python para declaraciones de tipo.
Usa esa misma sintaxis estándar para atributos de modelos con tipos internos.
@@ -51,7 +31,7 @@ Así, en nuestro ejemplo, podemos hacer que `tags` sea específicamente una "lis
{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}
-## Tipos de conjunto
+## Tipos de conjunto { #set-types }
Pero luego pensamos en ello, y nos damos cuenta de que los tags no deberían repetirse, probablemente serían strings únicos.
@@ -67,7 +47,7 @@ Y siempre que emitas esos datos, incluso si la fuente tenía duplicados, se emit
Y también se anotará/documentará en consecuencia.
-## Modelos Anidados
+## Modelos Anidados { #nested-models }
Cada atributo de un modelo Pydantic tiene un tipo.
@@ -77,13 +57,13 @@ Así que, puedes declarar "objetos" JSON anidados profundamente con nombres de a
Todo eso, de manera arbitraria.
-### Definir un submodelo
+### Definir un submodelo { #define-a-submodel }
Por ejemplo, podemos definir un modelo `Image`:
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *}
-### Usar el submodelo como tipo
+### Usar el submodelo como tipo { #use-the-submodel-as-a-type }
Y luego podemos usarlo como el tipo de un atributo:
@@ -112,7 +92,7 @@ Nuevamente, haciendo solo esa declaración, con **FastAPI** obtienes:
* Validación de datos
* Documentación automática
-## Tipos especiales y validación
+## Tipos especiales y validación { #special-types-and-validation }
Además de tipos singulares normales como `str`, `int`, `float`, etc., puedes usar tipos singulares más complejos que heredan de `str`.
@@ -124,7 +104,7 @@ Por ejemplo, como en el modelo `Image` tenemos un campo `url`, podemos declararl
El string será verificado para ser una URL válida, y documentado en JSON Schema / OpenAPI como tal.
-## Atributos con listas de submodelos
+## Atributos con listas de submodelos { #attributes-with-lists-of-submodels }
También puedes usar modelos Pydantic como subtipos de `list`, `set`, etc.:
@@ -162,7 +142,7 @@ Nota cómo la clave `images` ahora tiene una lista de objetos de imagen.
///
-## Modelos anidados profundamente
+## Modelos anidados profundamente { #deeply-nested-models }
Puedes definir modelos anidados tan profundamente como desees:
@@ -174,16 +154,10 @@ Observa cómo `Offer` tiene una lista de `Item`s, que a su vez tienen una lista
///
-## Cuerpos de listas puras
+## Cuerpos de listas puras { #bodies-of-pure-lists }
Si el valor superior del cuerpo JSON que esperas es un `array` JSON (una `list` en Python), puedes declarar el tipo en el parámetro de la función, al igual que en los modelos Pydantic:
-```Python
-images: List[Image]
-```
-
-o en Python 3.9 y superior:
-
```Python
images: list[Image]
```
@@ -192,7 +166,7 @@ como en:
{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}
-## Soporte de editor en todas partes
+## Soporte de editor en todas partes { #editor-support-everywhere }
Y obtienes soporte de editor en todas partes.
@@ -204,7 +178,7 @@ No podrías obtener este tipo de soporte de editor si estuvieras trabajando dire
Pero tampoco tienes que preocuparte por ellos, los `dicts` entrantes se convierten automáticamente y tu salida se convierte automáticamente a JSON también.
-## Cuerpos de `dict`s arbitrarios
+## Cuerpos de `dict`s arbitrarios { #bodies-of-arbitrary-dicts }
También puedes declarar un cuerpo como un `dict` con claves de algún tipo y valores de algún otro tipo.
@@ -234,7 +208,7 @@ Y el `dict` que recibas como `weights` tendrá realmente claves `int` y valores
///
-## Resumen
+## Resumen { #recap }
Con **FastAPI** tienes la máxima flexibilidad proporcionada por los modelos Pydantic, manteniendo tu código simple, corto y elegante.
diff --git a/docs/es/docs/tutorial/body-updates.md b/docs/es/docs/tutorial/body-updates.md
index 26cd3345fd..e75e29b54b 100644
--- a/docs/es/docs/tutorial/body-updates.md
+++ b/docs/es/docs/tutorial/body-updates.md
@@ -1,6 +1,6 @@
-# Cuerpo - Actualizaciones
+# Body - Actualizaciones { #body-updates }
-## Actualización reemplazando con `PUT`
+## Actualización reemplazando con `PUT` { #update-replacing-with-put }
Para actualizar un ítem puedes utilizar la operación de HTTP `PUT`.
@@ -10,7 +10,7 @@ Puedes usar el `jsonable_encoder` para convertir los datos de entrada en datos q
`PUT` se usa para recibir datos que deben reemplazar los datos existentes.
-### Advertencia sobre el reemplazo
+### Advertencia sobre el reemplazo { #warning-about-replacing }
Esto significa que si quieres actualizar el ítem `bar` usando `PUT` con un body que contenga:
@@ -26,7 +26,7 @@ debido a que no incluye el atributo ya almacenado `"tax": 20.2`, el modelo de en
Y los datos se guardarían con ese "nuevo" `tax` de `10.5`.
-## Actualizaciones parciales con `PATCH`
+## Actualizaciones parciales con `PATCH` { #partial-updates-with-patch }
También puedes usar la operación de HTTP `PATCH` para actualizar *parcialmente* datos.
@@ -44,43 +44,27 @@ Pero esta guía te muestra, más o menos, cómo se pretende que se usen.
///
-### Uso del parámetro `exclude_unset` de Pydantic
+### Uso del parámetro `exclude_unset` de Pydantic { #using-pydantics-exclude-unset-parameter }
Si quieres recibir actualizaciones parciales, es muy útil usar el parámetro `exclude_unset` en el `.model_dump()` del modelo de Pydantic.
Como `item.model_dump(exclude_unset=True)`.
-/// info | Información
-
-En Pydantic v1 el método se llamaba `.dict()`, fue deprecado (pero aún soportado) en Pydantic v2, y renombrado a `.model_dump()`.
-
-Los ejemplos aquí usan `.dict()` para compatibilidad con Pydantic v1, pero deberías usar `.model_dump()` si puedes usar Pydantic v2.
-
-///
-
Eso generaría un `dict` solo con los datos que se establecieron al crear el modelo `item`, excluyendo los valores por defecto.
Luego puedes usar esto para generar un `dict` solo con los datos que se establecieron (enviados en el request), omitiendo los valores por defecto:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *}
-### Uso del parámetro `update` de Pydantic
+### Uso del parámetro `update` de Pydantic { #using-pydantics-update-parameter }
Ahora, puedes crear una copia del modelo existente usando `.model_copy()`, y pasar el parámetro `update` con un `dict` que contenga los datos a actualizar.
-/// info | Información
-
-En Pydantic v1 el método se llamaba `.copy()`, fue deprecado (pero aún soportado) en Pydantic v2, y renombrado a `.model_copy()`.
-
-Los ejemplos aquí usan `.copy()` para compatibilidad con Pydantic v1, pero deberías usar `.model_copy()` si puedes usar Pydantic v2.
-
-///
-
Como `stored_item_model.model_copy(update=update_data)`:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
-### Resumen de actualizaciones parciales
+### Resumen de actualizaciones parciales { #partial-updates-recap }
En resumen, para aplicar actualizaciones parciales deberías:
@@ -90,9 +74,9 @@ En resumen, para aplicar actualizaciones parciales deberías:
* Generar un `dict` sin valores por defecto del modelo de entrada (usando `exclude_unset`).
* De esta manera puedes actualizar solo los valores realmente establecidos por el usuario, en lugar de sobrescribir valores ya almacenados con valores por defecto en tu modelo.
* Crear una copia del modelo almacenado, actualizando sus atributos con las actualizaciones parciales recibidas (usando el parámetro `update`).
-* Convertir el modelo copiado en algo que pueda almacenarse en tu base de datos (por ejemplo, usando el `jsonable_encoder`).
+* Convertir el modelo copiado en algo que pueda almacenarse en tu DB (por ejemplo, usando el `jsonable_encoder`).
* Esto es comparable a usar el método `.model_dump()` del modelo de nuevo, pero asegura (y convierte) los valores a tipos de datos que pueden convertirse a JSON, por ejemplo, `datetime` a `str`.
-* Guardar los datos en tu base de datos.
+* Guardar los datos en tu DB.
* Devolver el modelo actualizado.
{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *}
diff --git a/docs/es/docs/tutorial/body.md b/docs/es/docs/tutorial/body.md
index 6d0aa7c60f..dde39f78c1 100644
--- a/docs/es/docs/tutorial/body.md
+++ b/docs/es/docs/tutorial/body.md
@@ -1,4 +1,4 @@
-# Request Body
+# Request Body { #request-body }
Cuando necesitas enviar datos desde un cliente (digamos, un navegador) a tu API, los envías como un **request body**.
@@ -18,13 +18,13 @@ Como no se recomienda, la documentación interactiva con Swagger UI no mostrará
///
-## Importar `BaseModel` de Pydantic
+## Importar `BaseModel` de Pydantic { #import-pydantics-basemodel }
Primero, necesitas importar `BaseModel` de `pydantic`:
{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
-## Crea tu modelo de datos
+## Crea tu modelo de datos { #create-your-data-model }
Luego, declaras tu modelo de datos como una clase que hereda de `BaseModel`.
@@ -32,7 +32,8 @@ Usa tipos estándar de Python para todos los atributos:
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
-Al igual que al declarar parámetros de query, cuando un atributo del modelo tiene un valor por defecto, no es obligatorio. De lo contrario, es obligatorio. Usa `None` para hacerlo opcional.
+
+Al igual que al declarar parámetros de query, cuando un atributo del modelo tiene un valor por defecto, no es obligatorio. De lo contrario, es obligatorio. Usa `None` para hacerlo solo opcional.
Por ejemplo, el modelo anterior declara un “`object`” JSON (o `dict` en Python) como:
@@ -54,7 +55,7 @@ Por ejemplo, el modelo anterior declara un “`object`” JSON (o `dict` en Pyth
}
```
-## Decláralo como un parámetro
+## Decláralo como un parámetro { #declare-it-as-a-parameter }
Para añadirlo a tu *path operation*, decláralo de la misma manera que declaraste parámetros de path y query:
@@ -62,7 +63,7 @@ Para añadirlo a tu *path operation*, decláralo de la misma manera que declaras
...y declara su tipo como el modelo que creaste, `Item`.
-## Resultados
+## Resultados { #results }
Con solo esa declaración de tipo en Python, **FastAPI** hará lo siguiente:
@@ -73,9 +74,9 @@ Con solo esa declaración de tipo en Python, **FastAPI** hará lo siguiente:
* Proporcionar los datos recibidos en el parámetro `item`.
* Como lo declaraste en la función como de tipo `Item`, también tendrás todo el soporte del editor (autocompletado, etc.) para todos los atributos y sus tipos.
* Generar definiciones de JSON Schema para tu modelo, que también puedes usar en cualquier otro lugar si tiene sentido para tu proyecto.
-* Esquemas que serán parte del esquema de OpenAPI generado y usados por la UIs de documentación automática.
+* Esos esquemas serán parte del esquema de OpenAPI generado y usados por las UIs de documentación automática.
-## Documentación automática
+## Documentación automática { #automatic-docs }
Los JSON Schemas de tus modelos serán parte del esquema OpenAPI generado y se mostrarán en la documentación API interactiva:
@@ -85,7 +86,7 @@ Y también se utilizarán en la documentación API dentro de cada *path operatio
-## Soporte del editor
+## Soporte del editor { #editor-support }
En tu editor, dentro de tu función, obtendrás anotaciones de tipos y autocompletado en todas partes (esto no sucedería si recibieras un `dict` en lugar de un modelo de Pydantic):
@@ -121,13 +122,13 @@ Mejora el soporte del editor para modelos de Pydantic, con:
///
-## Usa el modelo
+## Usa el modelo { #use-the-model }
Dentro de la función, puedes acceder a todos los atributos del objeto modelo directamente:
{* ../../docs_src/body/tutorial002_py310.py *}
-## Request body + parámetros de path
+## Request body + parámetros de path { #request-body-path-parameters }
Puedes declarar parámetros de path y request body al mismo tiempo.
@@ -135,7 +136,8 @@ Puedes declarar parámetros de path y request body al mismo tiempo.
{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
-## Request body + path + parámetros de query
+
+## Request body + path + parámetros de query { #request-body-path-query-parameters }
También puedes declarar parámetros de **body**, **path** y **query**, todos al mismo tiempo.
@@ -147,18 +149,18 @@ Los parámetros de la función se reconocerán de la siguiente manera:
* Si el parámetro también se declara en el **path**, se utilizará como un parámetro de path.
* Si el parámetro es de un **tipo singular** (como `int`, `float`, `str`, `bool`, etc.), se interpretará como un parámetro de **query**.
-* Si el parámetro se declara como del tipo de un **modelo de Pydantic**, se interpretará como un **request body**.
+* Si el parámetro se declara como del tipo de un **modelo de Pydantic**, se interpretará como un **body** de request.
/// note | Nota
FastAPI sabrá que el valor de `q` no es requerido debido al valor por defecto `= None`.
-El `str | None` (Python 3.10+) o `Union` en `Union[str, None]` (Python 3.8+) no es utilizado por FastAPI para determinar que el valor no es requerido, sabrá que no es requerido porque tiene un valor por defecto de `= None`.
+El `str | None` (Python 3.10+) o `Union` en `Union[str, None]` (Python 3.9+) no es utilizado por FastAPI para determinar que el valor no es requerido, sabrá que no es requerido porque tiene un valor por defecto de `= None`.
Pero agregar las anotaciones de tipos permitirá que tu editor te brinde un mejor soporte y detecte errores.
///
-## Sin Pydantic
+## Sin Pydantic { #without-pydantic }
-Si no quieres usar modelos de Pydantic, también puedes usar parámetros **Body**. Consulta la documentación para [Body - Multiples Parametros: Valores singulares en body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
+Si no quieres usar modelos de Pydantic, también puedes usar parámetros **Body**. Consulta la documentación para [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
diff --git a/docs/es/docs/tutorial/cookie-param-models.md b/docs/es/docs/tutorial/cookie-param-models.md
index ebdb592651..dab7d8c0a2 100644
--- a/docs/es/docs/tutorial/cookie-param-models.md
+++ b/docs/es/docs/tutorial/cookie-param-models.md
@@ -1,4 +1,4 @@
-# Modelos de Cookies
+# Modelos de Cookies { #cookie-parameter-models }
Si tienes un grupo de **cookies** que están relacionadas, puedes crear un **modelo de Pydantic** para declararlas. 🍪
@@ -16,7 +16,7 @@ Esta misma técnica se aplica a `Query`, `Cookie`, y `Header`. 😎
///
-## Cookies con un Modelo de Pydantic
+## Cookies con un Modelo de Pydantic { #cookies-with-a-pydantic-model }
Declara los parámetros de **cookie** que necesites en un **modelo de Pydantic**, y luego declara el parámetro como `Cookie`:
@@ -24,7 +24,7 @@ Declara los parámetros de **cookie** que necesites en un **modelo de Pydantic**
**FastAPI** **extraerá** los datos para **cada campo** de las **cookies** recibidas en el request y te entregará el modelo de Pydantic que definiste.
-## Revisa la Documentación
+## Revisa la Documentación { #check-the-docs }
Puedes ver las cookies definidas en la UI de la documentación en `/docs`:
@@ -42,7 +42,7 @@ Pero incluso si **rellenas los datos** y haces clic en "Execute", como la UI de
///
-## Prohibir Cookies Extra
+## Prohibir Cookies Extra { #forbid-extra-cookies }
En algunos casos de uso especiales (probablemente no muy comunes), podrías querer **restringir** las cookies que deseas recibir.
@@ -50,7 +50,7 @@ Tu API ahora tiene el poder de controlar su propio **cookies** en **FastAPI**. 😎
diff --git a/docs/es/docs/tutorial/cookie-params.md b/docs/es/docs/tutorial/cookie-params.md
index 45b113ff9c..598872c0a5 100644
--- a/docs/es/docs/tutorial/cookie-params.md
+++ b/docs/es/docs/tutorial/cookie-params.md
@@ -1,14 +1,14 @@
-# Parámetros de Cookie
+# Parámetros de Cookie { #cookie-parameters }
Puedes definir parámetros de Cookie de la misma manera que defines los parámetros `Query` y `Path`.
-## Importar `Cookie`
+## Importar `Cookie` { #import-cookie }
Primero importa `Cookie`:
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
-## Declarar parámetros de `Cookie`
+## Declarar parámetros de `Cookie` { #declare-cookie-parameters }
Luego declara los parámetros de cookie usando la misma estructura que con `Path` y `Query`.
@@ -30,6 +30,16 @@ Para declarar cookies, necesitas usar `Cookie`, porque de lo contrario los pará
///
-## Resumen
+/// info | Información
+
+Ten en cuenta que, como **los navegadores manejan las cookies** de formas especiales y por detrás, **no** permiten fácilmente que **JavaScript** las toque.
+
+Si vas a la **UI de la documentación de la API** en `/docs` podrás ver la **documentación** de cookies para tus *path operations*.
+
+Pero incluso si **rellenas los datos** y haces clic en "Execute", como la UI de la documentación funciona con **JavaScript**, las cookies no se enviarán y verás un mensaje de **error** como si no hubieras escrito ningún valor.
+
+///
+
+## Resumen { #recap }
Declara cookies con `Cookie`, usando el mismo patrón común que `Query` y `Path`.
diff --git a/docs/es/docs/tutorial/cors.md b/docs/es/docs/tutorial/cors.md
index e493d44312..c1a23295e1 100644
--- a/docs/es/docs/tutorial/cors.md
+++ b/docs/es/docs/tutorial/cors.md
@@ -1,8 +1,8 @@
-# CORS (Cross-Origin Resource Sharing)
+# CORS (Cross-Origin Resource Sharing) { #cors-cross-origin-resource-sharing }
CORS o "Cross-Origin Resource Sharing" se refiere a situaciones en las que un frontend que se ejecuta en un navegador tiene código JavaScript que se comunica con un backend, y el backend está en un "origen" diferente al frontend.
-## Origen
+## Origen { #origin }
Un origen es la combinación de protocolo (`http`, `https`), dominio (`myapp.com`, `localhost`, `localhost.tiangolo.com`) y puerto (`80`, `443`, `8080`).
@@ -14,7 +14,7 @@ Así que, todos estos son orígenes diferentes:
Aunque todos están en `localhost`, usan protocolos o puertos diferentes, por lo tanto, son "orígenes" diferentes.
-## Pasos
+## Pasos { #steps }
Entonces, digamos que tienes un frontend corriendo en tu navegador en `http://localhost:8080`, y su JavaScript está tratando de comunicarse con un backend corriendo en `http://localhost` (porque no especificamos un puerto, el navegador asumirá el puerto por defecto `80`).
@@ -24,7 +24,7 @@ Para lograr esto, el backend `:80` debe tener una lista de "orígenes permitidos
En este caso, la lista tendría que incluir `http://localhost:8080` para que el frontend `:8080` funcione correctamente.
-## Comodines
+## Comodines { #wildcards }
También es posible declarar la lista como `"*"` (un "comodín") para decir que todos están permitidos.
@@ -32,7 +32,7 @@ Pero eso solo permitirá ciertos tipos de comunicación, excluyendo todo lo que
Así que, para que todo funcione correctamente, es mejor especificar explícitamente los orígenes permitidos.
-## Usa `CORSMiddleware`
+## Usa `CORSMiddleware` { #use-corsmiddleware }
Puedes configurarlo en tu aplicación **FastAPI** usando el `CORSMiddleware`.
@@ -46,7 +46,7 @@ También puedes especificar si tu backend permite:
* Métodos HTTP específicos (`POST`, `PUT`) o todos ellos con el comodín `"*"`.
* Headers HTTP específicos o todos ellos con el comodín `"*"`.
-{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
+{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *}
Los parámetros predeterminados utilizados por la implementación de `CORSMiddleware` son restrictivos por defecto, por lo que necesitarás habilitar explícitamente orígenes, métodos o headers particulares para que los navegadores estén permitidos de usarlos en un contexto de Cross-Domain.
@@ -56,23 +56,26 @@ Se admiten los siguientes argumentos:
* `allow_origin_regex` - Una cadena regex para coincidir con orígenes que deberían estar permitidos para hacer requests cross-origin. por ejemplo, `'https://.*\.example\.org'`.
* `allow_methods` - Una lista de métodos HTTP que deberían estar permitidos para requests cross-origin. Por defecto es `['GET']`. Puedes usar `['*']` para permitir todos los métodos estándar.
* `allow_headers` - Una lista de headers de request HTTP que deberían estar soportados para requests cross-origin. Por defecto es `[]`. Puedes usar `['*']` para permitir todos los headers. Los headers `Accept`, `Accept-Language`, `Content-Language` y `Content-Type` siempre están permitidos para requests CORS simples.
-* `allow_credentials` - Indica que las cookies deberían estar soportadas para requests cross-origin. Por defecto es `False`. Además, `allow_origins` no puede ser configurado a `['*']` para que las credenciales estén permitidas, los orígenes deben ser especificados.
+* `allow_credentials` - Indica que las cookies deberían estar soportadas para requests cross-origin. Por defecto es `False`.
+
+ Ninguno de `allow_origins`, `allow_methods` y `allow_headers` puede establecerse a `['*']` si `allow_credentials` está configurado a `True`. Todos deben ser especificados explícitamente.
+
* `expose_headers` - Indica cualquier header de response que debería ser accesible para el navegador. Por defecto es `[]`.
* `max_age` - Establece un tiempo máximo en segundos para que los navegadores almacenen en caché los responses CORS. Por defecto es `600`.
El middleware responde a dos tipos particulares de request HTTP...
-### Requests de preflight CORS
+### Requests de preflight CORS { #cors-preflight-requests }
Estos son cualquier request `OPTIONS` con headers `Origin` y `Access-Control-Request-Method`.
En este caso, el middleware interceptará el request entrante y responderá con los headers CORS adecuados, y un response `200` o `400` con fines informativos.
-### Requests simples
+### Requests simples { #simple-requests }
Cualquier request con un header `Origin`. En este caso, el middleware pasará el request a través de lo normal, pero incluirá los headers CORS adecuados en el response.
-## Más info
+## Más info { #more-info }
Para más información sobre CORS, revisa la documentación de CORS de Mozilla.
diff --git a/docs/es/docs/tutorial/debugging.md b/docs/es/docs/tutorial/debugging.md
index 2a7544e830..c31daf40f4 100644
--- a/docs/es/docs/tutorial/debugging.md
+++ b/docs/es/docs/tutorial/debugging.md
@@ -1,14 +1,14 @@
-# Depuración
+# Depuración { #debugging }
Puedes conectar el depurador en tu editor, por ejemplo con Visual Studio Code o PyCharm.
-## Llama a `uvicorn`
+## Llama a `uvicorn` { #call-uvicorn }
En tu aplicación de FastAPI, importa y ejecuta `uvicorn` directamente:
-{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
+{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *}
-### Acerca de `__name__ == "__main__"`
+### Acerca de `__name__ == "__main__"` { #about-name-main }
El objetivo principal de `__name__ == "__main__"` es tener algo de código que se ejecute cuando tu archivo es llamado con:
@@ -26,7 +26,7 @@ pero no es llamado cuando otro archivo lo importa, como en:
from myapp import app
```
-#### Más detalles
+#### Más detalles { #more-details }
Supongamos que tu archivo se llama `myapp.py`.
@@ -78,7 +78,7 @@ Para más información, revisa
-## Atajo
+## Atajo { #shortcut }
Pero ves que estamos teniendo algo de repetición de código aquí, escribiendo `CommonQueryParams` dos veces:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ sin `Annotated`
+//// tab | Python 3.9+ sin `Annotated`
/// tip | Consejo
@@ -225,7 +225,7 @@ Para esos casos específicos, puedes hacer lo siguiente:
En lugar de escribir:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ sin `Annotated`
+//// tab | Python 3.9+ sin `Annotated`
/// tip | Consejo
@@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
...escribes:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends()]
@@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()]
////
-//// tab | Python 3.8 sin `Annotated`
+//// tab | Python 3.9+ sin `Annotated`
/// tip | Consejo
diff --git a/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
index fbe17c67a0..60baa93a90 100644
--- a/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
+++ b/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
@@ -1,4 +1,4 @@
-# Dependencias en decoradores de *path operation*
+# Dependencias en decoradores de *path operation* { #dependencies-in-path-operation-decorators }
En algunos casos realmente no necesitas el valor de retorno de una dependencia dentro de tu *path operation function*.
@@ -8,7 +8,7 @@ Pero aún necesitas que sea ejecutada/resuelta.
Para esos casos, en lugar de declarar un parámetro de *path operation function* con `Depends`, puedes añadir una `list` de `dependencies` al decorador de *path operation*.
-## Agregar `dependencies` al decorador de *path operation*
+## Agregar `dependencies` al decorador de *path operation* { #add-dependencies-to-the-path-operation-decorator }
El decorador de *path operation* recibe un argumento opcional `dependencies`.
@@ -36,23 +36,23 @@ Pero en casos reales, al implementar seguridad, obtendrías más beneficios usan
///
-## Errores de dependencias y valores de retorno
+## Errores de dependencias y valores de retorno { #dependencies-errors-and-return-values }
Puedes usar las mismas *funciones* de dependencia que usas normalmente.
-### Requisitos de dependencia
+### Requisitos de dependencia { #dependency-requirements }
Pueden declarar requisitos de request (como headers) u otras sub-dependencias:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *}
-### Lanzar excepciones
+### Lanzar excepciones { #raise-exceptions }
Estas dependencias pueden `raise` excepciones, igual que las dependencias normales:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *}
-### Valores de retorno
+### Valores de retorno { #return-values }
Y pueden devolver valores o no, los valores no serán usados.
@@ -60,10 +60,10 @@ Así que, puedes reutilizar una dependencia normal (que devuelve un valor) que y
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *}
-## Dependencias para un grupo de *path operations*
+## Dependencias para un grupo de *path operations* { #dependencies-for-a-group-of-path-operations }
Más adelante, cuando leas sobre cómo estructurar aplicaciones más grandes ([Aplicaciones Más Grandes - Múltiples Archivos](../../tutorial/bigger-applications.md){.internal-link target=_blank}), posiblemente con múltiples archivos, aprenderás cómo declarar un único parámetro `dependencies` para un grupo de *path operations*.
-## Dependencias Globales
+## Dependencias Globales { #global-dependencies }
A continuación veremos cómo añadir dependencias a toda la aplicación `FastAPI`, de modo que se apliquen a cada *path operation*.
diff --git a/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md
index 94290443aa..aa645daa47 100644
--- a/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md
+++ b/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md
@@ -1,8 +1,8 @@
-# Dependencias con yield
+# Dependencias con yield { #dependencies-with-yield }
-FastAPI admite dependencias que realizan algunos pasos adicionales después de finalizar.
+FastAPI admite dependencias que realizan algunos pasos adicionales después de finalizar.
-Para hacer esto, usa `yield` en lugar de `return` y escribe los pasos adicionales (código) después.
+Para hacer esto, usa `yield` en lugar de `return`, y escribe los pasos adicionales (código) después.
/// tip | Consejo
@@ -10,7 +10,7 @@ Asegúrate de usar `yield` una sola vez por dependencia.
///
-/// note | Nota técnica
+/// note | Detalles técnicos
Cualquier función que sea válida para usar con:
@@ -23,21 +23,21 @@ De hecho, FastAPI usa esos dos decoradores internamente.
///
-## Una dependencia de base de datos con `yield`
+## Una dependencia de base de datos con `yield` { #a-database-dependency-with-yield }
Por ejemplo, podrías usar esto para crear una sesión de base de datos y cerrarla después de finalizar.
Solo el código anterior e incluyendo la declaración `yield` se ejecuta antes de crear un response:
-{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *}
El valor generado es lo que se inyecta en *path operations* y otras dependencias:
-{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *}
-El código posterior a la declaración `yield` se ejecuta después de crear el response pero antes de enviarla:
+El código posterior a la declaración `yield` se ejecuta después del response:
-{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *}
/// tip | Consejo
@@ -47,7 +47,7 @@ Puedes usar funciones `async` o regulares.
///
-## Una dependencia con `yield` y `try`
+## Una dependencia con `yield` y `try` { #a-dependency-with-yield-and-try }
Si usas un bloque `try` en una dependencia con `yield`, recibirás cualquier excepción que se haya lanzado al usar la dependencia.
@@ -57,9 +57,9 @@ Por lo tanto, puedes buscar esa excepción específica dentro de la dependencia
Del mismo modo, puedes usar `finally` para asegurarte de que los pasos de salida se ejecuten, sin importar si hubo una excepción o no.
-{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *}
-## Sub-dependencias con `yield`
+## Sub-dependencias con `yield` { #sub-dependencies-with-yield }
Puedes tener sub-dependencias y "árboles" de sub-dependencias de cualquier tamaño y forma, y cualquiera o todas ellas pueden usar `yield`.
@@ -85,7 +85,7 @@ Puedes tener cualquier combinación de dependencias que quieras.
**FastAPI** se asegurará de que todo se ejecute en el orden correcto.
-/// note | Nota técnica
+/// note | Detalles técnicos
Esto funciona gracias a los Context Managers de Python.
@@ -93,15 +93,17 @@ Esto funciona gracias a los > dep_req: Start request
+ Note over dep_req: Run code up to yield
+ dep_req ->> dep_func: Pass dependency
+ Note over dep_func: Run code up to yield
+ dep_func ->> operation: Run path operation with dependency
+ operation ->> dep_func: Return from path operation
+ Note over dep_func: Run code after yield
+ Note over dep_func: ✅ Dependency closed
+ dep_func ->> client: Send response to client
+ Note over client: Response sent
+ Note over dep_req: Run code after yield
+ Note over dep_req: ✅ Dependency closed
+```
-De esta manera probablemente tendrás un código más limpio.
+## Dependencias con `yield`, `HTTPException`, `except` y Tareas en Background { #dependencies-with-yield-httpexception-except-and-background-tasks }
-///
+Las dependencias con `yield` han evolucionado con el tiempo para cubrir diferentes casos de uso y corregir algunos problemas.
-Si solías depender de este comportamiento, ahora deberías crear los recursos para tareas en background dentro de la propia tarea en background, y usar internamente solo datos que no dependan de los recursos de las dependencias con `yield`.
+Si quieres ver qué ha cambiado en diferentes versiones de FastAPI, puedes leer más al respecto en la guía avanzada, en [Dependencias avanzadas - Dependencias con `yield`, `HTTPException`, `except` y Tareas en Background](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}.
-Por ejemplo, en lugar de usar la misma sesión de base de datos, crearías una nueva sesión de base de datos dentro de la tarea en background, y obtendrías los objetos de la base de datos usando esta nueva sesión. Y luego, en lugar de pasar el objeto de la base de datos como parámetro a la función de tarea en background, pasarías el ID de ese objeto y luego obtendrías el objeto nuevamente dentro de la función de tarea en background.
+## Context Managers { #context-managers }
-## Context Managers
-
-### Qué son los "Context Managers"
+### Qué son los "Context Managers" { #what-are-context-managers }
Los "Context Managers" son aquellos objetos de Python que puedes usar en una declaración `with`.
@@ -240,7 +255,7 @@ Cuando el bloque `with` termina, se asegura de cerrar el archivo, incluso si hub
Cuando creas una dependencia con `yield`, **FastAPI** creará internamente un context manager para ella y lo combinará con algunas otras herramientas relacionadas.
-### Usando context managers en dependencias con `yield`
+### Usando context managers en dependencias con `yield` { #using-context-managers-in-dependencies-with-yield }
/// warning | Advertencia
@@ -255,7 +270,7 @@ En Python, puedes crear Context Managers Inyección de Dependencias** muy poderoso pero intuitivo.
Está diseñado para ser muy simple de usar, y para hacer que cualquier desarrollador integre otros componentes con **FastAPI** de forma muy sencilla.
-## Qué es la "Inyección de Dependencias"
+## Qué es la "Inyección de Dependencias" { #what-is-dependency-injection }
**"Inyección de Dependencias"** significa, en programación, que hay una manera para que tu código (en este caso, tus *path operation functions*) declare las cosas que necesita para funcionar y utilizar: "dependencias".
@@ -19,13 +19,13 @@ Esto es muy útil cuando necesitas:
Todo esto, mientras minimizas la repetición de código.
-## Primeros Pasos
+## Primeros Pasos { #first-steps }
Veamos un ejemplo muy simple. Será tan simple que no es muy útil, por ahora.
Pero de esta manera podemos enfocarnos en cómo funciona el sistema de **Inyección de Dependencias**.
-### Crear una dependencia, o "dependable"
+### Crear una dependencia, o "dependable" { #create-a-dependency-or-dependable }
Primero enfoquémonos en la dependencia.
@@ -61,11 +61,11 @@ Asegúrate de [Actualizar la versión de FastAPI](../../deployment/versions.md#u
///
-### Importar `Depends`
+### Importar `Depends` { #import-depends }
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *}
-### Declarar la dependencia, en el "dependant"
+### Declarar la dependencia, en el "dependant" { #declare-the-dependency-in-the-dependant }
De la misma forma en que usas `Body`, `Query`, etc. con los parámetros de tu *path operation function*, usa `Depends` con un nuevo parámetro:
@@ -114,7 +114,7 @@ Solo la pasas a `Depends` y **FastAPI** sabe cómo hacer el resto.
///
-## Compartir dependencias `Annotated`
+## Compartir dependencias `Annotated` { #share-annotated-dependencies }
En los ejemplos anteriores, ves que hay un poquito de **duplicación de código**.
@@ -138,9 +138,9 @@ Pero porque **FastAPI** está basado en los estándares de Python, incluido `Ann
Las dependencias seguirán funcionando como se esperaba, y la **mejor parte** es que la **información de tipo se preservará**, lo que significa que tu editor podrá seguir proporcionándote **autocompletado**, **errores en línea**, etc. Lo mismo para otras herramientas como `mypy`.
-Esto será especialmente útil cuando lo uses en una **gran base de código** donde uses **las mismas dependencias** una y otra vez en **muchas *path operations***.
+Esto será especialmente útil cuando lo uses en una **gran code base** donde uses **las mismas dependencias** una y otra vez en **muchas *path operations***.
-## Usar `async` o no usar `async`
+## Usar `async` o no usar `async` { #to-async-or-not-to-async }
Como las dependencias también serán llamadas por **FastAPI** (lo mismo que tus *path operation functions*), las mismas reglas aplican al definir tus funciones.
@@ -156,7 +156,7 @@ Si no lo sabes, revisa la sección [Async: *"¿Con prisa?"*](../../async.md#in-a
///
-## Integración con OpenAPI
+## Integración con OpenAPI { #integrated-with-openapi }
Todas las declaraciones de request, validaciones y requisitos de tus dependencias (y sub-dependencias) se integrarán en el mismo esquema de OpenAPI.
@@ -164,7 +164,7 @@ Así, la documentación interactiva tendrá toda la información de estas depend
-## Uso simple
+## Uso simple { #simple-usage }
Si lo ves, las *path operation functions* se declaran para ser usadas siempre que un *path* y una *operación* coincidan, y luego **FastAPI** se encarga de llamar la función con los parámetros correctos, extrayendo los datos del request.
@@ -182,7 +182,7 @@ Otros términos comunes para esta misma idea de "inyección de dependencias" son
* inyectables
* componentes
-## Plug-ins de **FastAPI**
+## Plug-ins de **FastAPI** { #fastapi-plug-ins }
Las integraciones y "plug-ins" pueden construirse usando el sistema de **Inyección de Dependencias**. Pero, de hecho, en realidad **no hay necesidad de crear "plug-ins"**, ya que al usar dependencias es posible declarar una cantidad infinita de integraciones e interacciones que se vuelven disponibles para tus *path operation functions*.
@@ -190,7 +190,7 @@ Y las dependencias se pueden crear de una manera muy simple e intuitiva que te p
Verás ejemplos de esto en los próximos capítulos, sobre bases de datos relacionales y NoSQL, seguridad, etc.
-## Compatibilidad de **FastAPI**
+## Compatibilidad de **FastAPI** { #fastapi-compatibility }
La simplicidad del sistema de inyección de dependencias hace que **FastAPI** sea compatible con:
@@ -203,7 +203,7 @@ La simplicidad del sistema de inyección de dependencias hace que **FastAPI** se
* sistemas de inyección de datos de response
* etc.
-## Simple y Poderoso
+## Simple y Poderoso { #simple-and-powerful }
Aunque el sistema de inyección de dependencias jerárquico es muy simple de definir y usar, sigue siendo muy poderoso.
@@ -243,7 +243,7 @@ admin_user --> activate_user
paying_user --> pro_items
```
-## Integrado con **OpenAPI**
+## Integrado con **OpenAPI** { #integrated-with-openapi_1 }
Todas estas dependencias, al declarar sus requisitos, también añaden parámetros, validaciones, etc. a tus *path operations*.
diff --git a/docs/es/docs/tutorial/dependencies/sub-dependencies.md b/docs/es/docs/tutorial/dependencies/sub-dependencies.md
index bba5322072..e74d65d7e9 100644
--- a/docs/es/docs/tutorial/dependencies/sub-dependencies.md
+++ b/docs/es/docs/tutorial/dependencies/sub-dependencies.md
@@ -1,4 +1,4 @@
-# Sub-dependencias
+# Sub-dependencias { #sub-dependencies }
Puedes crear dependencias que tengan **sub-dependencias**.
@@ -6,7 +6,7 @@ Pueden ser tan **profundas** como necesites.
**FastAPI** se encargará de resolverlas.
-## Primera dependencia "dependable"
+## Primera dependencia "dependable" { #first-dependency-dependable }
Podrías crear una primera dependencia ("dependable") así:
@@ -16,7 +16,7 @@ Declara un parámetro de query opcional `q` como un `str`, y luego simplemente l
Esto es bastante simple (no muy útil), pero nos ayudará a centrarnos en cómo funcionan las sub-dependencias.
-## Segunda dependencia, "dependable" y "dependant"
+## Segunda dependencia, "dependable" y "dependant" { #second-dependency-dependable-and-dependant }
Luego puedes crear otra función de dependencia (un "dependable") que al mismo tiempo declare una dependencia propia (por lo que también es un "dependant"):
@@ -29,7 +29,7 @@ Centrémonos en los parámetros declarados:
* También declara una `last_query` cookie opcional, como un `str`.
* Si el usuario no proporcionó ningún query `q`, usamos el último query utilizado, que guardamos previamente en una cookie.
-## Usa la dependencia
+## Usa la dependencia { #use-the-dependency }
Entonces podemos usar la dependencia con:
@@ -54,7 +54,7 @@ read_query["/items/"]
query_extractor --> query_or_cookie_extractor --> read_query
```
-## Usando la misma dependencia múltiples veces
+## Usando la misma dependencia múltiples veces { #using-the-same-dependency-multiple-times }
Si una de tus dependencias se declara varias veces para la misma *path operation*, por ejemplo, múltiples dependencias tienen una sub-dependencia común, **FastAPI** sabrá llamar a esa sub-dependencia solo una vez por request.
@@ -62,7 +62,7 @@ Y guardará el valor devuelto en un ISO.
+Entonces, un objeto `datetime` tendría que ser convertido a un `str` que contenga los datos en formato ISO.
De la misma manera, esta base de datos no recibiría un modelo de Pydantic (un objeto con atributos), solo un `dict`.
diff --git a/docs/es/docs/tutorial/extra-data-types.md b/docs/es/docs/tutorial/extra-data-types.md
index 28775780f0..e876921ba4 100644
--- a/docs/es/docs/tutorial/extra-data-types.md
+++ b/docs/es/docs/tutorial/extra-data-types.md
@@ -1,4 +1,4 @@
-# Tipos de Datos Extra
+# Tipos de Datos Extra { #extra-data-types }
Hasta ahora, has estado usando tipos de datos comunes, como:
@@ -17,7 +17,7 @@ Y seguirás teniendo las mismas funcionalidades como hasta ahora:
* Validación de datos.
* Anotación y documentación automática.
-## Otros tipos de datos
+## Otros tipos de datos { #other-data-types }
Aquí hay algunos de los tipos de datos adicionales que puedes usar:
@@ -51,7 +51,7 @@ Aquí hay algunos de los tipos de datos adicionales que puedes usar:
* En requests y responses, manejado igual que un `float`.
* Puedes revisar todos los tipos de datos válidos de Pydantic aquí: Tipos de datos de Pydantic.
-## Ejemplo
+## Ejemplo { #example }
Aquí tienes un ejemplo de una *path operation* con parámetros usando algunos de los tipos anteriores.
diff --git a/docs/es/docs/tutorial/extra-models.md b/docs/es/docs/tutorial/extra-models.md
index 0380b96904..d72c73e245 100644
--- a/docs/es/docs/tutorial/extra-models.md
+++ b/docs/es/docs/tutorial/extra-models.md
@@ -1,4 +1,4 @@
-# Modelos Extra
+# Modelos Extra { #extra-models }
Continuando con el ejemplo anterior, será común tener más de un modelo relacionado.
@@ -16,27 +16,19 @@ Si no lo sabes, aprenderás qué es un "hash de contraseña" en los [capítulos
///
-## Múltiples modelos
+## Múltiples modelos { #multiple-models }
Aquí tienes una idea general de cómo podrían ser los modelos con sus campos de contraseña y los lugares donde se utilizan:
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
-/// info | Información
+### Acerca de `**user_in.model_dump()` { #about-user-in-model-dump }
-En Pydantic v1 el método se llamaba `.dict()`, fue deprecado (pero aún soportado) en Pydantic v2, y renombrado a `.model_dump()`.
-
-Los ejemplos aquí usan `.dict()` para compatibilidad con Pydantic v1, pero deberías usar `.model_dump()` en su lugar si puedes usar Pydantic v2.
-
-///
-
-### Acerca de `**user_in.dict()`
-
-#### `.dict()` de Pydantic
+#### `.model_dump()` de Pydantic { #pydantics-model-dump }
`user_in` es un modelo Pydantic de la clase `UserIn`.
-Los modelos Pydantic tienen un método `.dict()` que devuelve un `dict` con los datos del modelo.
+Los modelos Pydantic tienen un método `.model_dump()` que devuelve un `dict` con los datos del modelo.
Así que, si creamos un objeto Pydantic `user_in` como:
@@ -47,7 +39,7 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com
y luego llamamos a:
```Python
-user_dict = user_in.dict()
+user_dict = user_in.model_dump()
```
ahora tenemos un `dict` con los datos en la variable `user_dict` (es un `dict` en lugar de un objeto modelo Pydantic).
@@ -58,7 +50,7 @@ Y si llamamos a:
print(user_dict)
```
-obtendremos un `dict` de Python con:
+obtendríamos un `dict` de Python con:
```Python
{
@@ -69,7 +61,7 @@ obtendremos un `dict` de Python con:
}
```
-#### Desempaquetando un `dict`
+#### Desempaquetando un `dict` { #unpacking-a-dict }
Si tomamos un `dict` como `user_dict` y lo pasamos a una función (o clase) con `**user_dict`, Python lo "desempaquetará". Pasará las claves y valores del `user_dict` directamente como argumentos clave-valor.
@@ -101,31 +93,31 @@ UserInDB(
)
```
-#### Un modelo Pydantic a partir del contenido de otro
+#### Un modelo Pydantic a partir del contenido de otro { #a-pydantic-model-from-the-contents-of-another }
-Como en el ejemplo anterior obtuvimos `user_dict` de `user_in.dict()`, este código:
+Como en el ejemplo anterior obtuvimos `user_dict` de `user_in.model_dump()`, este código:
```Python
-user_dict = user_in.dict()
+user_dict = user_in.model_dump()
UserInDB(**user_dict)
```
sería equivalente a:
```Python
-UserInDB(**user_in.dict())
+UserInDB(**user_in.model_dump())
```
-...porque `user_in.dict()` es un `dict`, y luego hacemos que Python lo "desempaquete" al pasarlo a `UserInDB` con el prefijo `**`.
+...porque `user_in.model_dump()` es un `dict`, y luego hacemos que Python lo "desempaquete" al pasarlo a `UserInDB` con el prefijo `**`.
Así, obtenemos un modelo Pydantic a partir de los datos en otro modelo Pydantic.
-#### Desempaquetando un `dict` y palabras clave adicionales
+#### Desempaquetando un `dict` y palabras clave adicionales { #unpacking-a-dict-and-extra-keywords }
Y luego agregando el argumento de palabra clave adicional `hashed_password=hashed_password`, como en:
```Python
-UserInDB(**user_in.dict(), hashed_password=hashed_password)
+UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
```
...termina siendo como:
@@ -146,7 +138,7 @@ Las funciones adicionales de soporte `fake_password_hasher` y `fake_save_user` s
///
-## Reducir duplicación
+## Reducir duplicación { #reduce-duplication }
Reducir la duplicación de código es una de las ideas centrales en **FastAPI**.
@@ -156,7 +148,7 @@ Y estos modelos están compartiendo muchos de los datos y duplicando nombres y t
Podríamos hacerlo mejor.
-Podemos declarar un modelo `UserBase` que sirva como base para nuestros otros modelos. Y luego podemos hacer subclases de ese modelo que heredan sus atributos (declaraciones de tipo, validación, etc).
+Podemos declarar un modelo `UserBase` que sirva como base para nuestros otros modelos. Y luego podemos hacer subclases de ese modelo que heredan sus atributos (declaraciones de tipos, validación, etc).
Toda la conversión de datos, validación, documentación, etc. seguirá funcionando normalmente.
@@ -164,13 +156,13 @@ De esa manera, podemos declarar solo las diferencias entre los modelos (con `pas
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
-## `Union` o `anyOf`
+## `Union` o `anyOf` { #union-or-anyof }
Puedes declarar un response que sea la `Union` de dos o más tipos, eso significa que el response sería cualquiera de ellos.
Se definirá en OpenAPI con `anyOf`.
-Para hacerlo, usa el type hint estándar de Python `typing.Union`:
+Para hacerlo, usa la anotación de tipos estándar de Python `typing.Union`:
/// note | Nota
@@ -180,22 +172,21 @@ Al definir una
```console
-$ fastapi dev main.py
-INFO Using path main.py
-INFO Resolved absolute path /home/user/code/awesomeapp/main.py
-INFO Searching for package file structure from directories with __init__.py files
-INFO Importing from /home/user/code/awesomeapp
+$ fastapi dev main.py
- ╭─ Python module file ─╮
- │ │
- │ 🐍 main.py │
- │ │
- ╰──────────────────────╯
+ FastAPI Starting development server 🚀
-INFO Importing module main
-INFO Found importable FastAPI app
+ Searching for package file structure from directories
+ with __init__.py files
+ Importing from /home/user/code/awesomeapp
- ╭─ Importable FastAPI app ─╮
- │ │
- │ from main import app │
- │ │
- ╰──────────────────────────╯
+ module 🐍 main.py
-INFO Using import string main:app
+ code Importing the FastAPI app object from the module with
+ the following code:
- ╭────────── FastAPI CLI - Development mode ───────────╮
- │ │
- │ Serving at: http://127.0.0.1:8000 │
- │ │
- │ API docs: http://127.0.0.1:8000/docs │
- │ │
- │ Running in development mode, for production use: │
- │ │
- │ fastapi run │
- │ │
- ╰─────────────────────────────────────────────────────╯
+ from main import app
-INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
-INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
-INFO: Started reloader process [2265862] using WatchFiles
-INFO: Started server process [2265873]
-INFO: Waiting for application startup.
-INFO: Application startup complete.
+ app Using import string: main:app
+
+ server Server started at http://127.0.0.1:8000
+ server Documentation at http://127.0.0.1:8000/docs
+
+ tip Running in development mode, for production use:
+ fastapi run
+
+ Logs:
+
+ INFO Will watch for changes in these directories:
+ ['/home/user/code/awesomeapp']
+ INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C
+ to quit)
+ INFO Started reloader process [383138] using WatchFiles
+ INFO Started server process [383153]
+ INFO Waiting for application startup.
+ INFO Application startup complete.
```
-## Prohibir Headers Extra
+## Prohibir Headers Extra { #forbid-extra-headers }
En algunos casos de uso especiales (probablemente no muy comunes), podrías querer **restringir** los headers que deseas recibir.
@@ -51,6 +51,22 @@ Por ejemplo, si el cliente intenta enviar un header `tool` con un valor de `plum
}
```
-## Resumen
+## Desactivar la conversión de guiones bajos { #disable-convert-underscores }
+
+De la misma forma que con los parámetros de header normales, cuando tienes caracteres de guion bajo en los nombres de los parámetros, se **convierten automáticamente en guiones**.
+
+Por ejemplo, si tienes un parámetro de header `save_data` en el código, el header HTTP esperado será `save-data`, y aparecerá así en la documentación.
+
+Si por alguna razón necesitas desactivar esta conversión automática, también puedes hacerlo para los modelos Pydantic de parámetros de header.
+
+{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *}
+
+/// warning | Advertencia
+
+Antes de establecer `convert_underscores` a `False`, ten en cuenta que algunos proxies y servidores HTTP no permiten el uso de headers con guiones bajos.
+
+///
+
+## Resumen { #summary }
Puedes usar **modelos Pydantic** para declarar **headers** en **FastAPI**. 😎
diff --git a/docs/es/docs/tutorial/header-params.md b/docs/es/docs/tutorial/header-params.md
index 9c0112bb2b..12b4d1002a 100644
--- a/docs/es/docs/tutorial/header-params.md
+++ b/docs/es/docs/tutorial/header-params.md
@@ -1,14 +1,14 @@
-# Parámetros de Header
+# Parámetros de Header { #header-parameters }
Puedes definir los parámetros de Header de la misma manera que defines los parámetros de `Query`, `Path` y `Cookie`.
-## Importar `Header`
+## Importar `Header` { #import-header }
Primero importa `Header`:
{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *}
-## Declarar parámetros de `Header`
+## Declarar parámetros de `Header` { #declare-header-parameters }
Luego declara los parámetros de header usando la misma estructura que con `Path`, `Query` y `Cookie`.
@@ -30,7 +30,7 @@ Para declarar headers, necesitas usar `Header`, porque de otra forma los paráme
///
-## Conversión automática
+## Conversión automática { #automatic-conversion }
`Header` tiene un poquito de funcionalidad extra además de lo que proporcionan `Path`, `Query` y `Cookie`.
@@ -54,7 +54,7 @@ Antes de establecer `convert_underscores` a `False`, ten en cuenta que algunos p
///
-## Headers duplicados
+## Headers duplicados { #duplicate-headers }
Es posible recibir headers duplicados. Eso significa, el mismo header con múltiples valores.
@@ -84,7 +84,7 @@ El response sería como:
}
```
-## Recapitulación
+## Recapitulación { #recap }
Declara headers con `Header`, usando el mismo patrón común que `Query`, `Path` y `Cookie`.
diff --git a/docs/es/docs/tutorial/index.md b/docs/es/docs/tutorial/index.md
index dcfc6cdfb7..7804b6854d 100644
--- a/docs/es/docs/tutorial/index.md
+++ b/docs/es/docs/tutorial/index.md
@@ -1,4 +1,4 @@
-# Tutorial - Guía del Usuario
+# Tutorial - Guía del Usuario { #tutorial-user-guide }
Este tutorial te muestra cómo usar **FastAPI** con la mayoría de sus funcionalidades, paso a paso.
@@ -6,7 +6,7 @@ Cada sección se basa gradualmente en las anteriores, pero está estructurada pa
También está diseñado para funcionar como una referencia futura para que puedas volver y ver exactamente lo que necesitas.
-## Ejecuta el código
+## Ejecuta el código { #run-the-code }
Todos los bloques de código pueden ser copiados y usados directamente (de hecho, son archivos Python probados).
@@ -15,48 +15,39 @@ Para ejecutar cualquiera de los ejemplos, copia el código a un archivo `main.py
-## Identificador de licencia
+## Identificador de licencia { #license-identifier }
Desde OpenAPI 3.1.0 y FastAPI 0.99.0, también puedes establecer la `license_info` con un `identifier` en lugar de una `url`.
Por ejemplo:
-{* ../../docs_src/metadata/tutorial001_1.py hl[31] *}
+{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *}
-## Metadata para etiquetas
+## Metadata para etiquetas { #metadata-for-tags }
También puedes agregar metadata adicional para las diferentes etiquetas usadas para agrupar tus path operations con el parámetro `openapi_tags`.
@@ -52,13 +52,13 @@ Cada diccionario puede contener:
* `description`: un `str` con una breve descripción para la documentación externa.
* `url` (**requerido**): un `str` con la URL para la documentación externa.
-### Crear metadata para etiquetas
+### Crear metadata para etiquetas { #create-metadata-for-tags }
Probemos eso en un ejemplo con etiquetas para `users` y `items`.
Crea metadata para tus etiquetas y pásala al parámetro `openapi_tags`:
-{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
+{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *}
Nota que puedes utilizar Markdown dentro de las descripciones, por ejemplo "login" se mostrará en negrita (**login**) y "fancy" se mostrará en cursiva (_fancy_).
@@ -68,11 +68,11 @@ No tienes que agregar metadata para todas las etiquetas que uses.
///
-### Usar tus etiquetas
+### Usar tus etiquetas { #use-your-tags }
Usa el parámetro `tags` con tus *path operations* (y `APIRouter`s) para asignarlas a diferentes etiquetas:
-{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
+{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *}
/// info | Información
@@ -80,19 +80,19 @@ Lee más sobre etiquetas en [Configuración de Path Operation](path-operation-co
///
-### Revisa la documentación
+### Revisa la documentación { #check-the-docs }
Ahora, si revisas la documentación, mostrará toda la metadata adicional:
-### Orden de las etiquetas
+### Orden de las etiquetas { #order-of-tags }
El orden de cada diccionario de metadata de etiqueta también define el orden mostrado en la interfaz de documentación.
Por ejemplo, aunque `users` iría después de `items` en orden alfabético, se muestra antes porque agregamos su metadata como el primer diccionario en la list.
-## URL de OpenAPI
+## URL de OpenAPI { #openapi-url }
Por defecto, el esquema OpenAPI se sirve en `/openapi.json`.
@@ -100,11 +100,11 @@ Pero puedes configurarlo con el parámetro `openapi_url`.
Por ejemplo, para configurarlo para que se sirva en `/api/v1/openapi.json`:
-{* ../../docs_src/metadata/tutorial002.py hl[3] *}
+{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *}
Si quieres deshabilitar el esquema OpenAPI completamente, puedes establecer `openapi_url=None`, eso también deshabilitará las interfaces de usuario de documentación que lo usan.
-## URLs de Docs
+## URLs de Docs { #docs-urls }
Puedes configurar las dos interfaces de usuario de documentación incluidas:
@@ -117,4 +117,4 @@ Puedes configurar las dos interfaces de usuario de documentación incluidas:
Por ejemplo, para configurar Swagger UI para que se sirva en `/documentation` y deshabilitar ReDoc:
-{* ../../docs_src/metadata/tutorial003.py hl[3] *}
+{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *}
diff --git a/docs/es/docs/tutorial/middleware.md b/docs/es/docs/tutorial/middleware.md
index c42e4eaa54..de636a4854 100644
--- a/docs/es/docs/tutorial/middleware.md
+++ b/docs/es/docs/tutorial/middleware.md
@@ -1,4 +1,4 @@
-# Middleware
+# Middleware { #middleware }
Puedes añadir middleware a las aplicaciones de **FastAPI**.
@@ -15,11 +15,11 @@ Un "middleware" es una función que trabaja con cada **request** antes de que se
Si tienes dependencias con `yield`, el código de salida se ejecutará *después* del middleware.
-Si hubiera alguna tarea en segundo plano (documentada más adelante), se ejecutará *después* de todo el middleware.
+Si hubiera tareas en segundo plano (cubiertas en la sección [Tareas en segundo plano](background-tasks.md){.internal-link target=_blank}, lo verás más adelante), se ejecutarán *después* de todo el middleware.
///
-## Crear un middleware
+## Crear un middleware { #create-a-middleware }
Para crear un middleware usas el decorador `@app.middleware("http")` encima de una función.
@@ -31,11 +31,11 @@ La función middleware recibe:
* Luego devuelve la `response` generada por la correspondiente *path operation*.
* Puedes entonces modificar aún más la `response` antes de devolverla.
-{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
+{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *}
/// tip | Consejo
-Ten en cuenta que los custom proprietary headers se pueden añadir usando el prefijo 'X-'.
+Ten en cuenta que los custom proprietary headers se pueden añadir usando el prefijo `X-`.
Pero si tienes custom headers que deseas que un cliente en un navegador pueda ver, necesitas añadirlos a tus configuraciones de CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) usando el parámetro `expose_headers` documentado en la documentación de CORS de Starlette.
@@ -49,7 +49,7 @@ También podrías usar `from starlette.requests import Request`.
///
-### Antes y después de la `response`
+### Antes y después de la `response` { #before-and-after-the-response }
Puedes añadir código que se ejecute con la `request`, antes de que cualquier *path operation* la reciba.
@@ -57,7 +57,7 @@ Y también después de que se genere la `response`, antes de devolverla.
Por ejemplo, podrías añadir un custom header `X-Process-Time` que contenga el tiempo en segundos que tomó procesar la request y generar una response:
-{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
+{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *}
/// tip | Consejo
@@ -65,7 +65,30 @@ Aquí usamos
-### Tags con Enums
+### Tags con Enums { #tags-with-enums }
Si tienes una gran aplicación, podrías terminar acumulando **varias tags**, y querrías asegurarte de que siempre uses la **misma tag** para *path operations* relacionadas.
@@ -46,15 +46,15 @@ En estos casos, podría tener sentido almacenar las tags en un `Enum`.
**FastAPI** soporta eso de la misma manera que con strings normales:
-{* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *}
+{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *}
-## Resumen y Descripción
+## Resumen y Descripción { #summary-and-description }
Puedes añadir un `summary` y `description`:
{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *}
-## Descripción desde docstring
+## Descripción desde docstring { #description-from-docstring }
Como las descripciones tienden a ser largas y cubrir múltiples líneas, puedes declarar la descripción de la *path operation* en la docstring de la función y **FastAPI** la leerá desde allí.
@@ -66,7 +66,7 @@ Será usado en la documentación interactiva:
-## Descripción del Response
+## Descripción del Response { #response-description }
Puedes especificar la descripción del response con el parámetro `response_description`:
@@ -88,11 +88,11 @@ Entonces, si no proporcionas una, **FastAPI** generará automáticamente una de
-## Deprecar una *path operation*
+## Deprecar una *path operation* { #deprecate-a-path-operation }
Si necesitas marcar una *path operation* como deprecated, pero sin eliminarla, pasa el parámetro `deprecated`:
-{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *}
+{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *}
Se marcará claramente como deprecado en la documentación interactiva:
@@ -102,6 +102,6 @@ Revisa cómo lucen las *path operations* deprecadas y no deprecadas:
-## Resumen
+## Resumen { #recap }
Puedes configurar y añadir metadatos a tus *path operations* fácilmente pasando parámetros a los *path operation decorators*.
diff --git a/docs/es/docs/tutorial/path-params-numeric-validations.md b/docs/es/docs/tutorial/path-params-numeric-validations.md
index 4ea01753b4..569dd03dd0 100644
--- a/docs/es/docs/tutorial/path-params-numeric-validations.md
+++ b/docs/es/docs/tutorial/path-params-numeric-validations.md
@@ -1,8 +1,8 @@
-# Parámetros de Path y Validaciones Numéricas
+# Parámetros de Path y Validaciones Numéricas { #path-parameters-and-numeric-validations }
De la misma manera que puedes declarar más validaciones y metadatos para los parámetros de query con `Query`, puedes declarar el mismo tipo de validaciones y metadatos para los parámetros de path con `Path`.
-## Importar Path
+## Importar Path { #import-path }
Primero, importa `Path` de `fastapi`, e importa `Annotated`:
@@ -18,7 +18,7 @@ Asegúrate de [Actualizar la versión de FastAPI](../deployment/versions.md#upgr
///
-## Declarar metadatos
+## Declarar metadatos { #declare-metadata }
Puedes declarar todos los mismos parámetros que para `Query`.
@@ -32,7 +32,7 @@ Un parámetro de path siempre es requerido ya que tiene que formar parte del pat
///
-## Ordena los parámetros como necesites
+## Ordena los parámetros como necesites { #order-the-parameters-as-you-need }
/// tip | Consejo
@@ -54,13 +54,13 @@ No importa para **FastAPI**. Detectará los parámetros por sus nombres, tipos y
Así que puedes declarar tu función como:
-{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *}
Pero ten en cuenta que si usas `Annotated`, no tendrás este problema, no importará ya que no estás usando los valores por defecto de los parámetros de la función para `Query()` o `Path()`.
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
-## Ordena los parámetros como necesites, trucos
+## Ordena los parámetros como necesites, trucos { #order-the-parameters-as-you-need-tricks }
/// tip | Consejo
@@ -83,15 +83,15 @@ Pasa `*`, como el primer parámetro de la función.
Python no hará nada con ese `*`, pero sabrá que todos los parámetros siguientes deben ser llamados como argumentos de palabras clave (parejas key-value), también conocidos como kwargs. Incluso si no tienen un valor por defecto.
-{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
-### Mejor con `Annotated`
+### Mejor con `Annotated` { #better-with-annotated }
Ten en cuenta que si usas `Annotated`, como no estás usando valores por defecto de los parámetros de la función, no tendrás este problema y probablemente no necesitarás usar `*`.
{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
-## Validaciones numéricas: mayor o igual
+## Validaciones numéricas: mayor o igual { #number-validations-greater-than-or-equal }
Con `Query` y `Path` (y otros que verás más adelante) puedes declarar restricciones numéricas.
@@ -99,7 +99,7 @@ Aquí, con `ge=1`, `item_id` necesitará ser un número entero "`g`reater than o
{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
-## Validaciones numéricas: mayor que y menor o igual
+## Validaciones numéricas: mayor que y menor o igual { #number-validations-greater-than-and-less-than-or-equal }
Lo mismo aplica para:
@@ -108,19 +108,19 @@ Lo mismo aplica para:
{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
-## Validaciones numéricas: flotantes, mayor y menor
+## Validaciones numéricas: flotantes, mayor y menor { #number-validations-floats-greater-than-and-less-than }
Las validaciones numéricas también funcionan para valores `float`.
-Aquí es donde se convierte en importante poder declarar gt y no solo ge. Ya que con esto puedes requerir, por ejemplo, que un valor sea mayor que `0`, incluso si es menor que `1`.
+Aquí es donde se convierte en importante poder declarar gt y no solo ge. Ya que con esto puedes requerir, por ejemplo, que un valor sea mayor que `0`, incluso si es menor que `1`.
Así, `0.5` sería un valor válido. Pero `0.0` o `0` no lo serían.
-Y lo mismo para lt.
+Y lo mismo para lt.
{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *}
-## Resumen
+## Resumen { #recap }
Con `Query`, `Path` (y otros que aún no has visto) puedes declarar metadatos y validaciones de string de las mismas maneras que con [Parámetros de Query y Validaciones de String](query-params-str-validations.md){.internal-link target=_blank}.
@@ -139,7 +139,7 @@ Todas ellas comparten los mismos parámetros para validación adicional y metada
///
-/// note | Nota técnica
+/// note | Detalles técnicos
Cuando importas `Query`, `Path` y otros de `fastapi`, en realidad son funciones.
diff --git a/docs/es/docs/tutorial/path-params.md b/docs/es/docs/tutorial/path-params.md
index 4262809026..7ba49f3b0b 100644
--- a/docs/es/docs/tutorial/path-params.md
+++ b/docs/es/docs/tutorial/path-params.md
@@ -1,8 +1,8 @@
-# Parámetros de Path
+# Parámetros de Path { #path-parameters }
Puedes declarar "parámetros" o "variables" de path con la misma sintaxis que se usa en los format strings de Python:
-{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
+{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
El valor del parámetro de path `item_id` se pasará a tu función como el argumento `item_id`.
@@ -12,11 +12,11 @@ Así que, si ejecutas este ejemplo y vas a Conversión de datos { #data-conversion }
Si ejecutas este ejemplo y abres tu navegador en http://127.0.0.1:8000/items/3, verás un response de:
@@ -38,11 +38,11 @@ Si ejecutas este ejemplo y abres tu navegador en "parsing" automático de requests.
+Entonces, con esa declaración de tipo, **FastAPI** te ofrece "parsing" automático de request.
///
-## Validación de datos
+## Validación de datos { #data-validation }
Pero si vas al navegador en http://127.0.0.1:8000/items/foo, verás un bonito error HTTP de:
@@ -76,7 +76,7 @@ Esto es increíblemente útil mientras desarrollas y depuras código que interac
///
-## Documentación
+## Documentación { #documentation }
Y cuando abras tu navegador en http://127.0.0.1:8000/docs, verás una documentación de API automática e interactiva como:
@@ -90,7 +90,7 @@ Nota que el parámetro de path está declarado como un entero.
///
-## Beneficios basados en estándares, documentación alternativa
+## Beneficios basados en estándares, documentación alternativa { #standards-based-benefits-alternative-documentation }
Y porque el esquema generado es del estándar OpenAPI, hay muchas herramientas compatibles.
@@ -100,7 +100,7 @@ Debido a esto, el propio **FastAPI** proporciona una documentación de API alter
De la misma manera, hay muchas herramientas compatibles. Incluyendo herramientas de generación de código para muchos lenguajes.
-## Pydantic
+## Pydantic { #pydantic }
Toda la validación de datos se realiza internamente con Pydantic, así que obtienes todos los beneficios de esta. Y sabes que estás en buenas manos.
@@ -108,7 +108,7 @@ Puedes usar las mismas declaraciones de tipo con `str`, `float`, `bool` y muchos
Varios de estos se exploran en los siguientes capítulos del tutorial.
-## El orden importa
+## El orden importa { #order-matters }
Al crear *path operations*, puedes encontrarte en situaciones donde tienes un path fijo.
@@ -118,21 +118,21 @@ Y luego también puedes tener un path `/users/{user_id}` para obtener datos sobr
Debido a que las *path operations* se evalúan en orden, necesitas asegurarte de que el path para `/users/me` se declara antes que el de `/users/{user_id}`:
-{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
+{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *}
De lo contrario, el path para `/users/{user_id}` también coincidiría para `/users/me`, "pensando" que está recibiendo un parámetro `user_id` con un valor de `"me"`.
De manera similar, no puedes redefinir una path operation:
-{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *}
+{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *}
La primera siempre será utilizada ya que el path coincide primero.
-## Valores predefinidos
+## Valores predefinidos { #predefined-values }
-Si tienes una *path operation* que recibe un *path parameter*, pero quieres que los valores posibles válidos del *path parameter* estén predefinidos, puedes usar un `Enum` estándar de Python.
+Si tienes una *path operation* que recibe un *path parameter*, pero quieres que los valores posibles válidos del *path parameter* estén predefinidos, puedes usar un `Enum` estándar de Python.
-### Crear una clase `Enum`
+### Crear una clase `Enum` { #create-an-enum-class }
Importa `Enum` y crea una subclase que herede de `str` y de `Enum`.
@@ -140,13 +140,7 @@ Al heredar de `str`, la documentación de la API podrá saber que los valores de
Luego crea atributos de clase con valores fijos, que serán los valores válidos disponibles:
-{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
-
-/// info | Información
-
-Las enumeraciones (o enums) están disponibles en Python desde la versión 3.4.
-
-///
+{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *}
/// tip | Consejo
@@ -154,33 +148,33 @@ Si te estás preguntando, "AlexNet", "ResNet" y "LeNet" son solo nombres de
-## Prohibir Parámetros Query Extras
+## Prohibir Parámetros Query Extras { #forbid-extra-query-parameters }
En algunos casos de uso especiales (probablemente no muy comunes), podrías querer **restringir** los parámetros query que deseas recibir.
@@ -57,7 +57,7 @@ Recibirán un response de **error** que les indica que el parámetro query `tool
}
```
-## Resumen
+## Resumen { #summary }
Puedes usar **modelos de Pydantic** para declarar **parámetros query** en **FastAPI**. 😎
diff --git a/docs/es/docs/tutorial/query-params-str-validations.md b/docs/es/docs/tutorial/query-params-str-validations.md
index 9cb76156f0..4af4782f83 100644
--- a/docs/es/docs/tutorial/query-params-str-validations.md
+++ b/docs/es/docs/tutorial/query-params-str-validations.md
@@ -1,4 +1,4 @@
-# Parámetros de Query y Validaciones de String
+# Parámetros de Query y Validaciones de String { #query-parameters-and-string-validations }
**FastAPI** te permite declarar información adicional y validación para tus parámetros.
@@ -6,48 +6,28 @@ Tomemos esta aplicación como ejemplo:
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
-El parámetro de query `q` es del tipo `Union[str, None]` (o `str | None` en Python 3.10), lo que significa que es de tipo `str` pero también podría ser `None`, y de hecho, el valor por defecto es `None`, así que FastAPI sabrá que no es requerido.
+El parámetro de query `q` es de tipo `str | None`, lo que significa que es de tipo `str` pero también podría ser `None`, y de hecho, el valor por defecto es `None`, así que FastAPI sabrá que no es requerido.
/// note | Nota
FastAPI sabrá que el valor de `q` no es requerido por el valor por defecto `= None`.
-El `Union` en `Union[str, None]` permitirá a tu editor darte un mejor soporte y detectar errores.
+Tener `str | None` permitirá que tu editor te dé un mejor soporte y detecte errores.
///
-## Validaciones adicionales
+## Validaciones adicionales { #additional-validation }
Vamos a hacer que, aunque `q` sea opcional, siempre que se proporcione, **su longitud no exceda los 50 caracteres**.
-### Importar `Query` y `Annotated`
+### Importar `Query` y `Annotated` { #import-query-and-annotated }
Para lograr eso, primero importa:
* `Query` desde `fastapi`
-* `Annotated` desde `typing` (o desde `typing_extensions` en Python por debajo de 3.9)
+* `Annotated` desde `typing`
-//// tab | Python 3.10+
-
-En Python 3.9 o superior, `Annotated` es parte de la biblioteca estándar, así que puedes importarlo desde `typing`.
-
-```Python hl_lines="1 3"
-{!> ../../docs_src/query_params_str_validations/tutorial002_an_py310.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-En versiones de Python por debajo de 3.9 importas `Annotated` desde `typing_extensions`.
-
-Ya estará instalado con FastAPI.
-
-```Python hl_lines="3-4"
-{!> ../../docs_src/query_params_str_validations/tutorial002_an.py!}
-```
-
-////
+{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *}
/// info | Información
@@ -59,9 +39,9 @@ Asegúrate de [Actualizar la versión de FastAPI](../deployment/versions.md#upgr
///
-## Usar `Annotated` en el tipo del parámetro `q`
+## Usar `Annotated` en el tipo del parámetro `q` { #use-annotated-in-the-type-for-the-q-parameter }
-¿Recuerdas que te dije antes que `Annotated` puede ser usado para agregar metadatos a tus parámetros en la [Introducción a Tipos de Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}?
+¿Recuerdas que te dije antes que `Annotated` puede usarse para agregar metadatos a tus parámetros en la [Introducción a Tipos de Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}?
Ahora es el momento de usarlo con FastAPI. 🚀
@@ -75,7 +55,7 @@ q: str | None = None
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
q: Union[str, None] = None
@@ -93,7 +73,7 @@ q: Annotated[str | None] = None
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
q: Annotated[Union[str, None]] = None
@@ -105,7 +85,7 @@ Ambas versiones significan lo mismo, `q` es un parámetro que puede ser un `str`
Ahora vamos a lo divertido. 🎉
-## Agregar `Query` a `Annotated` en el parámetro `q`
+## Agregar `Query` a `Annotated` en el parámetro `q` { #add-query-to-annotated-in-the-q-parameter }
Ahora que tenemos este `Annotated` donde podemos poner más información (en este caso algunas validaciones adicionales), agrega `Query` dentro de `Annotated`, y establece el parámetro `max_length` a `50`:
@@ -127,9 +107,9 @@ FastAPI ahora:
* Mostrará un **error claro** para el cliente cuando los datos no sean válidos
* **Documentará** el parámetro en el OpenAPI esquema *path operation* (así aparecerá en la **UI de documentación automática**)
-## Alternativa (antigua): `Query` como valor por defecto
+## Alternativa (antigua): `Query` como valor por defecto { #alternative-old-query-as-the-default-value }
-Versiones anteriores de FastAPI (antes de 0.95.0) requerían que usaras `Query` como el valor por defecto de tu parámetro, en lugar de ponerlo en `Annotated`. Hay una alta probabilidad de que veas código usándolo alrededor, así que te lo explicaré.
+Versiones anteriores de FastAPI (antes de 0.95.0) requerían que usaras `Query` como el valor por defecto de tu parámetro, en lugar de ponerlo en `Annotated`, hay una alta probabilidad de que veas código usándolo alrededor, así que te lo explicaré.
/// tip | Consejo
@@ -141,63 +121,32 @@ Así es como usarías `Query()` como el valor por defecto de tu parámetro de fu
{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *}
-Ya que en este caso (sin usar `Annotated`) debemos reemplazar el valor por defecto `None` en la función con `Query()`, ahora necesitamos establecer el valor por defecto con el parámetro `Query(default=None)`, esto sirve al mismo propósito de definir ese valor por defecto (al menos para FastAPI).
+Como en este caso (sin usar `Annotated`) debemos reemplazar el valor por defecto `None` en la función con `Query()`, ahora necesitamos establecer el valor por defecto con el parámetro `Query(default=None)`, esto sirve al mismo propósito de definir ese valor por defecto (al menos para FastAPI).
Entonces:
-```Python
-q: Union[str, None] = Query(default=None)
-```
-
-...hace que el parámetro sea opcional, con un valor por defecto de `None`, lo mismo que:
-
-```Python
-q: Union[str, None] = None
-```
-
-Y en Python 3.10 y superior:
-
```Python
q: str | None = Query(default=None)
```
...hace que el parámetro sea opcional, con un valor por defecto de `None`, lo mismo que:
+
```Python
q: str | None = None
```
-Pero las versiones de `Query` lo declaran explícitamente como un parámetro de query.
-
-/// info | Información
-
-Ten en cuenta que la parte más importante para hacer un parámetro opcional es la parte:
-
-```Python
-= None
-```
-
-o la parte:
-
-```Python
-= Query(default=None)
-```
-
-ya que usará ese `None` como el valor por defecto, y de esa manera hará el parámetro **no requerido**.
-
-La parte `Union[str, None]` permite que tu editor brinde un mejor soporte, pero no es lo que le dice a FastAPI que este parámetro no es requerido.
-
-///
+Pero la versión con `Query` lo declara explícitamente como un parámetro de query.
Luego, podemos pasar más parámetros a `Query`. En este caso, el parámetro `max_length` que se aplica a los strings:
```Python
-q: Union[str, None] = Query(default=None, max_length=50)
+q: str | None = Query(default=None, max_length=50)
```
-Esto validará los datos, mostrará un error claro cuando los datos no sean válidos, y documentará el parámetro en el esquema del *path operation* de OpenaPI.
+Esto validará los datos, mostrará un error claro cuando los datos no sean válidos, y documentará el parámetro en el esquema del *path operation* de OpenAPI.
-### `Query` como valor por defecto o en `Annotated`
+### `Query` como valor por defecto o en `Annotated` { #query-as-the-default-value-or-in-annotated }
Ten en cuenta que cuando uses `Query` dentro de `Annotated` no puedes usar el parámetro `default` para `Query`.
@@ -217,13 +166,13 @@ Así que utilizarías (preferentemente):
q: Annotated[str, Query()] = "rick"
```
-...o en code bases más antiguos encontrarás:
+...o en code bases más antiguas encontrarás:
```Python
q: str = Query(default="rick")
```
-### Ventajas de `Annotated`
+### Ventajas de `Annotated` { #advantages-of-annotated }
**Usar `Annotated` es recomendado** en lugar del valor por defecto en los parámetros de función, es **mejor** por múltiples razones. 🤓
@@ -235,13 +184,13 @@ Cuando no usas `Annotated` y en su lugar usas el estilo de valor por defecto **(
Dado que `Annotated` puede tener más de una anotación de metadato, ahora podrías incluso usar la misma función con otras herramientas, como Typer. 🚀
-## Agregar más validaciones
+## Agregar más validaciones { #add-more-validations }
También puedes agregar un parámetro `min_length`:
{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *}
-## Agregar expresiones regulares
+## Agregar expresiones regulares { #add-regular-expressions }
Puedes definir una expresión regular `pattern` que el parámetro debe coincidir:
@@ -249,29 +198,15 @@ Puedes definir una Campos requeridos.
-
-///
-
-/// tip | Consejo
-
-Recuerda que en la mayoría de los casos, cuando algo es requerido, puedes simplemente omitir el default, así que normalmente no tienes que usar `...`.
-
-///
-
-## Lista de parámetros de Query / múltiples valores
+## Lista de parámetros de Query / múltiples valores { #query-parameter-list-multiple-values }
Cuando defines un parámetro de query explícitamente con `Query` también puedes declararlo para recibir una lista de valores, o dicho de otra manera, para recibir múltiples valores.
@@ -355,7 +266,7 @@ Entonces, con una URL como:
http://localhost:8000/items/?q=foo&q=bar
```
-recibirías los múltiples valores del *query parameter* `q` (`foo` y `bar`) en una `list` de Python dentro de tu *path operation function*, en el *parámetro de función* `q`.
+recibirías los múltiples valores de los *query parameters* `q` (`foo` y `bar`) en una `list` de Python dentro de tu *path operation function*, en el *parámetro de función* `q`.
Entonces, el response a esa URL sería:
@@ -378,9 +289,9 @@ La documentación interactiva de API se actualizará en consecuencia, para permi
-### Lista de parámetros de Query / múltiples valores con valores por defecto
+### Lista de parámetros de Query / múltiples valores con valores por defecto { #query-parameter-list-multiple-values-with-defaults }
-Y también puedes definir un valor por defecto `list` de valores si no se proporcionan ninguno:
+También puedes definir un valor por defecto `list` de valores si no se proporciona ninguno:
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
@@ -401,9 +312,9 @@ el valor por defecto de `q` será: `["foo", "bar"]` y tu response será:
}
```
-#### Usando solo `list`
+#### Usando solo `list` { #using-just-list }
-También puedes usar `list` directamente en lugar de `List[str]` (o `list[str]` en Python 3.9+):
+También puedes usar `list` directamente en lugar de `list[str]`:
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
@@ -411,11 +322,11 @@ También puedes usar `list` directamente en lugar de `List[str]` (o `list[str]`
Ten en cuenta que en este caso, FastAPI no comprobará el contenido de la lista.
-Por ejemplo, `List[int]` comprobaría (y documentaría) que el contenido de la lista son enteros. Pero `list` sola no lo haría.
+Por ejemplo, `list[int]` comprobaría (y documentaría) que el contenido de la lista son enteros. Pero `list` sola no lo haría.
///
-## Declarar más metadatos
+## Declarar más metadatos { #declare-more-metadata }
Puedes agregar más información sobre el parámetro.
@@ -437,7 +348,7 @@ Y una `description`:
{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
-## Alias para parámetros
+## Alias para parámetros { #alias-parameters }
Imagina que quieres que el parámetro sea `item-query`.
@@ -457,11 +368,11 @@ Entonces puedes declarar un `alias`, y ese alias será usado para encontrar el v
{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
-## Declarar parámetros obsoletos
+## Declarar parámetros obsoletos { #deprecating-parameters }
Ahora digamos que ya no te gusta este parámetro.
-Tienes que dejarlo allí por un tiempo porque hay clientes usándolo, pero quieres que la documentación lo muestre claramente como deprecated.
+Tienes que dejarlo allí por un tiempo porque hay clientes usándolo, pero quieres que la documentación lo muestre claramente como deprecated.
Luego pasa el parámetro `deprecated=True` a `Query`:
@@ -471,13 +382,75 @@ La documentación lo mostrará así:
-## Excluir parámetros de OpenAPI
+## Excluir parámetros de OpenAPI { #exclude-parameters-from-openapi }
Para excluir un parámetro de query del esquema de OpenAPI generado (y por lo tanto, de los sistemas de documentación automática), establece el parámetro `include_in_schema` de `Query` a `False`:
{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *}
-## Recapitulación
+## Validación personalizada { #custom-validation }
+
+Podría haber casos donde necesites hacer alguna **validación personalizada** que no puede hacerse con los parámetros mostrados arriba.
+
+En esos casos, puedes usar una **función validadora personalizada** que se aplique después de la validación normal (por ejemplo, después de validar que el valor es un `str`).
+
+Puedes lograr eso usando `AfterValidator` de Pydantic dentro de `Annotated`.
+
+/// tip | Consejo
+
+Pydantic también tiene `BeforeValidator` y otros. 🤓
+
+///
+
+Por ejemplo, este validador personalizado comprueba que el ID del ítem empiece con `isbn-` para un número de libro ISBN o con `imdb-` para un ID de URL de película de IMDB:
+
+{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
+
+/// info | Información
+
+Esto está disponible con Pydantic versión 2 o superior. 😎
+
+///
+
+/// tip | Consejo
+
+Si necesitas hacer cualquier tipo de validación que requiera comunicarte con algún **componente externo**, como una base de datos u otra API, deberías usar **Dependencias de FastAPI**, las aprenderás más adelante.
+
+Estos validadores personalizados son para cosas que pueden comprobarse **solo** con los **mismos datos** provistos en el request.
+
+///
+
+### Entiende ese código { #understand-that-code }
+
+El punto importante es solo usar **`AfterValidator` con una función dentro de `Annotated`**. Si quieres, sáltate esta parte. 🤸
+
+---
+
+Pero si te da curiosidad este ejemplo de código específico y sigues entretenido, aquí tienes algunos detalles extra.
+
+#### String con `value.startswith()` { #string-with-value-startswith }
+
+¿Lo notaste? un string usando `value.startswith()` puede recibir una tupla, y comprobará cada valor en la tupla:
+
+{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *}
+
+#### Un ítem aleatorio { #a-random-item }
+
+Con `data.items()` obtenemos un objeto iterable con tuplas que contienen la clave y el valor para cada elemento del diccionario.
+
+Convertimos este objeto iterable en una `list` propiamente dicha con `list(data.items())`.
+
+Luego con `random.choice()` podemos obtener un **valor aleatorio** de la lista, así que obtenemos una tupla con `(id, name)`. Será algo como `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`.
+
+Luego **asignamos esos dos valores** de la tupla a las variables `id` y `name`.
+
+Así, si el usuario no proporcionó un ID de ítem, aún recibirá una sugerencia aleatoria.
+
+...hacemos todo esto en una **sola línea simple**. 🤯 ¿No te encanta Python? 🐍
+
+{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *}
+
+## Recapitulación { #recap }
Puedes declarar validaciones y metadatos adicionales para tus parámetros.
@@ -494,6 +467,8 @@ Validaciones específicas para strings:
* `max_length`
* `pattern`
+Validaciones personalizadas usando `AfterValidator`.
+
En estos ejemplos viste cómo declarar validaciones para valores de tipo `str`.
Mira los siguientes capítulos para aprender cómo declarar validaciones para otros tipos, como números.
diff --git a/docs/es/docs/tutorial/query-params.md b/docs/es/docs/tutorial/query-params.md
index 9bd47f8714..2b58a2b4b6 100644
--- a/docs/es/docs/tutorial/query-params.md
+++ b/docs/es/docs/tutorial/query-params.md
@@ -1,8 +1,8 @@
-# Parámetros de Query
+# Parámetros de Query { #query-parameters }
Cuando declaras otros parámetros de función que no son parte de los parámetros de path, son automáticamente interpretados como parámetros de "query".
-{* ../../docs_src/query_params/tutorial001.py hl[9] *}
+{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *}
La query es el conjunto de pares clave-valor que van después del `?` en una URL, separados por caracteres `&`.
@@ -24,11 +24,11 @@ Pero cuando los declaras con tipos de Python (en el ejemplo anterior, como `int`
Todo el mismo proceso que se aplica para los parámetros de path también se aplica para los parámetros de query:
* Soporte del editor (obviamente)
-* "Parsing" de datos
+* "parsing" de datos
* Validación de datos
* Documentación automática
-## Valores por defecto
+## Valores por defecto { #defaults }
Como los parámetros de query no son una parte fija de un path, pueden ser opcionales y pueden tener valores por defecto.
@@ -57,19 +57,21 @@ Los valores de los parámetros en tu función serán:
* `skip=20`: porque lo configuraste en la URL
* `limit=10`: porque ese era el valor por defecto
-## Parámetros opcionales
+## Parámetros opcionales { #optional-parameters }
De la misma manera, puedes declarar parámetros de query opcionales, estableciendo su valor por defecto en `None`:
{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *}
+En este caso, el parámetro de función `q` será opcional y será `None` por defecto.
+
/// check | Revisa
Además, nota que **FastAPI** es lo suficientemente inteligente para notar que el parámetro de path `item_id` es un parámetro de path y `q` no lo es, por lo tanto, es un parámetro de query.
///
-## Conversión de tipos en parámetros de query
+## Conversión de tipos en parámetros de query { #query-parameter-type-conversion }
También puedes declarar tipos `bool`, y serán convertidos:
@@ -107,7 +109,7 @@ http://127.0.0.1:8000/items/foo?short=yes
o cualquier otra variación (mayúsculas, primera letra en mayúscula, etc.), tu función verá el parámetro `short` con un valor `bool` de `True`. De lo contrario, será `False`.
-## Múltiples parámetros de path y de query
+## Múltiples parámetros de path y de query { #multiple-path-and-query-parameters }
Puedes declarar múltiples parámetros de path y de query al mismo tiempo, **FastAPI** sabe cuál es cuál.
@@ -117,7 +119,7 @@ Serán detectados por nombre:
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
-## Parámetros de query requeridos
+## Parámetros de query requeridos { #required-query-parameters }
Cuando declaras un valor por defecto para parámetros que no son de path (por ahora, solo hemos visto parámetros de query), entonces no es requerido.
@@ -125,7 +127,7 @@ Si no quieres agregar un valor específico pero solo hacer que sea opcional, est
Pero cuando quieres hacer un parámetro de query requerido, simplemente no declares ningún valor por defecto:
-{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
+{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
Aquí el parámetro de query `needy` es un parámetro de query requerido de tipo `str`.
diff --git a/docs/es/docs/tutorial/request-files.md b/docs/es/docs/tutorial/request-files.md
index 330523c7ef..cc99deb2e3 100644
--- a/docs/es/docs/tutorial/request-files.md
+++ b/docs/es/docs/tutorial/request-files.md
@@ -1,4 +1,4 @@
-# Archivos de Request
+# Archivos de Request { #request-files }
Puedes definir archivos que serán subidos por el cliente utilizando `File`.
@@ -16,13 +16,13 @@ Esto es porque los archivos subidos se envían como "form data".
///
-## Importar `File`
+## Importar `File` { #import-file }
Importa `File` y `UploadFile` desde `fastapi`:
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
-## Definir Parámetros `File`
+## Definir Parámetros `File` { #define-file-parameters }
Crea parámetros de archivo de la misma manera que lo harías para `Body` o `Form`:
@@ -50,7 +50,7 @@ Ten en cuenta que esto significa que todo el contenido se almacenará en memoria
Pero hay varios casos en los que podrías beneficiarte de usar `UploadFile`.
-## Parámetros de Archivo con `UploadFile`
+## Parámetros de Archivo con `UploadFile` { #file-parameters-with-uploadfile }
Define un parámetro de archivo con un tipo de `UploadFile`:
@@ -66,7 +66,7 @@ Usar `UploadFile` tiene varias ventajas sobre `bytes`:
* Tiene una interfaz `async` parecida a un archivo.
* Expone un objeto Python real `SpooledTemporaryFile` que puedes pasar directamente a otros paquetes que esperan un objeto parecido a un archivo.
-### `UploadFile`
+### `UploadFile` { #uploadfile }
`UploadFile` tiene los siguientes atributos:
@@ -109,7 +109,7 @@ El `UploadFile` de **FastAPI** hereda directamente del `UploadFile` de **Starlet
///
-## Qué es "Form Data"
+## Qué es "Form Data" { #what-is-form-data }
La manera en que los forms de HTML (``) envían los datos al servidor normalmente utiliza una codificación "especial" para esos datos, es diferente de JSON.
@@ -121,7 +121,7 @@ Los datos de los forms normalmente se codifican usando el "media type" `applicat
Pero cuando el formulario incluye archivos, se codifica como `multipart/form-data`. Si usas `File`, **FastAPI** sabrá que tiene que obtener los archivos de la parte correcta del cuerpo.
-Si deseas leer más sobre estas codificaciones y campos de formularios, dirígete a la MDN web docs para POST.
+Si deseas leer más sobre estas codificaciones y campos de formularios, dirígete a la MDN web docs para POST.
///
@@ -133,19 +133,19 @@ Esto no es una limitación de **FastAPI**, es parte del protocolo HTTP.
///
-## Subida de Archivos Opcional
+## Subida de Archivos Opcional { #optional-file-upload }
Puedes hacer un archivo opcional utilizando anotaciones de tipos estándar y estableciendo un valor por defecto de `None`:
{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
-## `UploadFile` con Metadatos Adicionales
+## `UploadFile` con Metadatos Adicionales { #uploadfile-with-additional-metadata }
También puedes usar `File()` con `UploadFile`, por ejemplo, para establecer metadatos adicionales:
{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
-## Subidas de Múltiples Archivos
+## Subidas de Múltiples Archivos { #multiple-file-uploads }
Es posible subir varios archivos al mismo tiempo.
@@ -165,12 +165,12 @@ También podrías usar `from starlette.responses import HTMLResponse`.
///
-### Subidas de Múltiples Archivos con Metadatos Adicionales
+### Subidas de Múltiples Archivos con Metadatos Adicionales { #multiple-file-uploads-with-additional-metadata }
Y de la misma manera que antes, puedes usar `File()` para establecer parámetros adicionales, incluso para `UploadFile`:
{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
-## Recapitulación
+## Recapitulación { #recap }
Usa `File`, `bytes` y `UploadFile` para declarar archivos que se subirán en el request, enviados como form data.
diff --git a/docs/es/docs/tutorial/request-form-models.md b/docs/es/docs/tutorial/request-form-models.md
index 9d5d7495a0..1f4668c840 100644
--- a/docs/es/docs/tutorial/request-form-models.md
+++ b/docs/es/docs/tutorial/request-form-models.md
@@ -1,4 +1,4 @@
-# Modelos de Formulario
+# Modelos de Formulario { #form-models }
Puedes usar **modelos de Pydantic** para declarar **campos de formulario** en FastAPI.
@@ -20,7 +20,7 @@ Esto es compatible desde la versión `0.113.0` de FastAPI. 🤓
///
-## Modelos de Pydantic para Formularios
+## Modelos de Pydantic para Formularios { #pydantic-models-for-forms }
Solo necesitas declarar un **modelo de Pydantic** con los campos que quieres recibir como **campos de formulario**, y luego declarar el parámetro como `Form`:
@@ -28,7 +28,7 @@ Solo necesitas declarar un **modelo de Pydantic** con los campos que quieres rec
**FastAPI** **extraerá** los datos de **cada campo** de los **form data** en el request y te dará el modelo de Pydantic que definiste.
-## Revisa la Documentación
+## Revisa la Documentación { #check-the-docs }
Puedes verificarlo en la interfaz de documentación en `/docs`:
@@ -36,7 +36,7 @@ Puedes verificarlo en la interfaz de documentación en `/docs`:
-## Prohibir Campos de Formulario Extra
+## Prohibir Campos de Formulario Extra { #forbid-extra-form-fields }
En algunos casos de uso especiales (probablemente no muy comunes), podrías querer **restringir** los campos de formulario a solo aquellos declarados en el modelo de Pydantic. Y **prohibir** cualquier campo **extra**.
@@ -73,6 +73,6 @@ Recibirá un response de error indicando que el campo `extra` no está permitido
}
```
-## Resumen
+## Resumen { #summary }
Puedes usar modelos de Pydantic para declarar campos de formulario en FastAPI. 😎
diff --git a/docs/es/docs/tutorial/request-forms-and-files.md b/docs/es/docs/tutorial/request-forms-and-files.md
index 51dfbb3578..363553e862 100644
--- a/docs/es/docs/tutorial/request-forms-and-files.md
+++ b/docs/es/docs/tutorial/request-forms-and-files.md
@@ -1,4 +1,4 @@
-# Request Forms and Files
+# Formularios y archivos del request { #request-forms-and-files }
Puedes definir archivos y campos de formulario al mismo tiempo usando `File` y `Form`.
@@ -14,11 +14,11 @@ $ pip install python-multipart
///
-## Importar `File` y `Form`
+## Importa `File` y `Form` { #import-file-and-form }
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *}
-## Definir parámetros `File` y `Form`
+## Define parámetros `File` y `Form` { #define-file-and-form-parameters }
Crea parámetros de archivo y formulario de la misma manera que lo harías para `Body` o `Query`:
@@ -36,6 +36,6 @@ Esto no es una limitación de **FastAPI**, es parte del protocolo HTTP.
///
-## Resumen
+## Resumen { #recap }
Usa `File` y `Form` juntos cuando necesites recibir datos y archivos en el mismo request.
diff --git a/docs/es/docs/tutorial/request-forms.md b/docs/es/docs/tutorial/request-forms.md
index a9d62e660b..33061e6a1b 100644
--- a/docs/es/docs/tutorial/request-forms.md
+++ b/docs/es/docs/tutorial/request-forms.md
@@ -1,10 +1,10 @@
-# Form Data
+# Datos de formulario { #form-data }
Cuando necesitas recibir campos de formulario en lugar de JSON, puedes usar `Form`.
/// info | Información
-Para usar forms, primero instala `python-multipart`.
+Para usar formularios, primero instala `python-multipart`.
Asegúrate de crear un [entorno virtual](../virtual-environments.md){.internal-link target=_blank}, activarlo, y luego instalarlo, por ejemplo:
@@ -14,13 +14,13 @@ $ pip install python-multipart
///
-## Importar `Form`
+## Importar `Form` { #import-form }
Importar `Form` desde `fastapi`:
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *}
-## Definir parámetros de `Form`
+## Definir parámetros de `Form` { #define-form-parameters }
Crea parámetros de formulario de la misma manera que lo harías para `Body` o `Query`:
@@ -28,7 +28,7 @@ Crea parámetros de formulario de la misma manera que lo harías para `Body` o `
Por ejemplo, en una de las formas en las que se puede usar la especificación OAuth2 (llamada "password flow") se requiere enviar un `username` y `password` como campos de formulario.
-La especificación requiere que los campos se llamen exactamente `username` y `password`, y que se envíen como campos de formulario, no JSON.
+La spec requiere que los campos se llamen exactamente `username` y `password`, y que se envíen como campos de formulario, no JSON.
Con `Form` puedes declarar las mismas configuraciones que con `Body` (y `Query`, `Path`, `Cookie`), incluyendo validación, ejemplos, un alias (por ejemplo, `user-name` en lugar de `username`), etc.
@@ -40,23 +40,23 @@ Con `Form` puedes declarar las mismas configuraciones que con `Body` (y `Query`,
/// tip | Consejo
-Para declarar bodies de forms, necesitas usar `Form` explícitamente, porque sin él, los parámetros se interpretarían como parámetros de query o como parámetros de body (JSON).
+Para declarar bodies de formularios, necesitas usar `Form` explícitamente, porque sin él, los parámetros se interpretarían como parámetros de query o como parámetros de body (JSON).
///
-## Sobre "Campos de Formulario"
+## Sobre "Campos de formulario" { #about-form-fields }
-La manera en que los forms HTML (``) envían los datos al servidor normalmente usa una codificación "especial" para esos datos, es diferente de JSON.
+La manera en que los formularios HTML (``) envían los datos al servidor normalmente usa una codificación "especial" para esos datos, es diferente de JSON.
**FastAPI** se encargará de leer esos datos del lugar correcto en lugar de JSON.
/// note | Detalles técnicos
-Los datos de forms normalmente se codifican usando el "media type" `application/x-www-form-urlencoded`.
+Los datos de formularios normalmente se codifican usando el "media type" `application/x-www-form-urlencoded`.
Pero cuando el formulario incluye archivos, se codifica como `multipart/form-data`. Leerás sobre la gestión de archivos en el próximo capítulo.
-Si quieres leer más sobre estas codificaciones y campos de formulario, dirígete a la MDN web docs para POST.
+Si quieres leer más sobre estas codificaciones y campos de formulario, dirígete a la MDN web docs para POST.
///
@@ -68,6 +68,6 @@ Esto no es una limitación de **FastAPI**, es parte del protocolo HTTP.
///
-## Recapitulación
+## Recapitulación { #recap }
Usa `Form` para declarar parámetros de entrada de datos de formulario.
diff --git a/docs/es/docs/tutorial/response-model.md b/docs/es/docs/tutorial/response-model.md
index 09682f51bc..8cfe69e782 100644
--- a/docs/es/docs/tutorial/response-model.md
+++ b/docs/es/docs/tutorial/response-model.md
@@ -1,8 +1,8 @@
-# Modelo de Response - Tipo de Retorno
+# Modelo de Response - Tipo de Retorno { #response-model-return-type }
Puedes declarar el tipo utilizado para el response anotando el **tipo de retorno** de la *path operation function*.
-Puedes utilizar **anotaciones de tipos** de la misma manera que lo harías para datos de entrada en **parámetros** de función, puedes utilizar modelos de Pydantic, listas, diccionarios, valores escalares como enteros, booleanos, etc.
+Puedes utilizar **anotaciones de tipos** de la misma manera que lo harías para datos de entrada en **parámetros** de función, puedes utilizar modelos de Pydantic, lists, diccionarios, valores escalares como enteros, booleanos, etc.
{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
@@ -19,7 +19,7 @@ Pero lo más importante:
* **Limitará y filtrará** los datos de salida a lo que se define en el tipo de retorno.
* Esto es particularmente importante para la **seguridad**, veremos más sobre eso a continuación.
-## Parámetro `response_model`
+## Parámetro `response_model` { #response-model-parameter }
Hay algunos casos en los que necesitas o quieres devolver algunos datos que no son exactamente lo que declara el tipo.
@@ -27,7 +27,7 @@ Por ejemplo, podrías querer **devolver un diccionario** u objeto de base de dat
Si añadiste la anotación del tipo de retorno, las herramientas y editores se quejarían con un error (correcto) diciéndote que tu función está devolviendo un tipo (por ejemplo, un dict) que es diferente de lo que declaraste (por ejemplo, un modelo de Pydantic).
-En esos casos, puedes usar el parámetro del decorador de path operation `response_model` en lugar del tipo de retorno.
+En esos casos, puedes usar el parámetro del *decorador de path operation* `response_model` en lugar del tipo de retorno.
Puedes usar el parámetro `response_model` en cualquiera de las *path operations*:
@@ -57,7 +57,7 @@ De esa manera le dices al editor que intencionalmente estás devolviendo cualqui
///
-### Prioridad del `response_model`
+### Prioridad del `response_model` { #response-model-priority }
Si declaras tanto un tipo de retorno como un `response_model`, el `response_model` tomará prioridad y será utilizado por FastAPI.
@@ -65,7 +65,7 @@ De esta manera puedes añadir anotaciones de tipos correctas a tus funciones inc
También puedes usar `response_model=None` para desactivar la creación de un modelo de response para esa *path operation*, podrías necesitar hacerlo si estás añadiendo anotaciones de tipos para cosas que no son campos válidos de Pydantic, verás un ejemplo de eso en una de las secciones a continuación.
-## Devolver los mismos datos de entrada
+## Devolver los mismos datos de entrada { #return-the-same-input-data }
Aquí estamos declarando un modelo `UserIn`, contendrá una contraseña en texto plano:
@@ -105,7 +105,7 @@ Nunca almacenes la contraseña en texto plano de un usuario ni la envíes en un
///
-## Añadir un modelo de salida
+## Añadir un modelo de salida { #add-an-output-model }
Podemos en cambio crear un modelo de entrada con la contraseña en texto plano y un modelo de salida sin ella:
@@ -121,7 +121,7 @@ Aquí, aunque nuestra *path operation function* está devolviendo el mismo usuar
Entonces, **FastAPI** se encargará de filtrar todos los datos que no estén declarados en el modelo de salida (usando Pydantic).
-### `response_model` o Tipo de Retorno
+### `response_model` o Tipo de Retorno { #response-model-or-return-type }
En este caso, como los dos modelos son diferentes, si anotáramos el tipo de retorno de la función como `UserOut`, el editor y las herramientas se quejarían de que estamos devolviendo un tipo inválido, ya que son clases diferentes.
@@ -129,7 +129,7 @@ Por eso en este ejemplo tenemos que declararlo en el parámetro `response_model`
...pero sigue leyendo abajo para ver cómo superar eso.
-## Tipo de Retorno y Filtrado de Datos
+## Tipo de Retorno y Filtrado de Datos { #return-type-and-data-filtering }
Continuemos con el ejemplo anterior. Queríamos **anotar la función con un tipo**, pero queríamos poder devolver desde la función algo que en realidad incluya **más datos**.
@@ -147,17 +147,17 @@ Con esto, obtenemos soporte de las herramientas, de los editores y mypy ya que e
¿Cómo funciona esto? Vamos a echarle un vistazo. 🤓
-### Anotaciones de Tipos y Herramientas
+### Anotaciones de Tipos y Herramientas { #type-annotations-and-tooling }
Primero vamos a ver cómo los editores, mypy y otras herramientas verían esto.
`BaseUser` tiene los campos base. Luego `UserIn` hereda de `BaseUser` y añade el campo `password`, por lo que incluirá todos los campos de ambos modelos.
-Anotamos el tipo de retorno de la función como `BaseUser`, pero en realidad estamos devolviendo una instancia de `UserIn`.
+Anotamos el tipo de retorno de la función como `BaseUser`, pero en realidad estamos devolviendo un `UserIn` instance.
El editor, mypy y otras herramientas no se quejarán de esto porque, en términos de tipificación, `UserIn` es una subclase de `BaseUser`, lo que significa que es un tipo *válido* cuando se espera algo que es un `BaseUser`.
-### Filtrado de Datos en FastAPI
+### Filtrado de Datos en FastAPI { #fastapi-data-filtering }
Ahora, para FastAPI, verá el tipo de retorno y se asegurará de que lo que devuelves incluya **solo** los campos que están declarados en el tipo.
@@ -165,7 +165,7 @@ FastAPI realiza varias cosas internamente con Pydantic para asegurarse de que es
De esta manera, puedes obtener lo mejor de ambos mundos: anotaciones de tipos con **soporte de herramientas** y **filtrado de datos**.
-## Verlo en la documentación
+## Verlo en la documentación { #see-it-in-the-docs }
Cuando veas la documentación automática, puedes verificar que el modelo de entrada y el modelo de salida tendrán cada uno su propio JSON Schema:
@@ -175,29 +175,29 @@ Y ambos modelos se utilizarán para la documentación interactiva de la API:
-## Otras Anotaciones de Tipos de Retorno
+## Otras Anotaciones de Tipos de Retorno { #other-return-type-annotations }
Podría haber casos en los que devuelvas algo que no es un campo válido de Pydantic y lo anotes en la función, solo para obtener el soporte proporcionado por las herramientas (el editor, mypy, etc).
-### Devolver un Response Directamente
+### Devolver un Response Directamente { #return-a-response-directly }
El caso más común sería [devolver un Response directamente como se explica más adelante en la documentación avanzada](../advanced/response-directly.md){.internal-link target=_blank}.
-{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
+{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
Este caso simple es manejado automáticamente por FastAPI porque la anotación del tipo de retorno es la clase (o una subclase de) `Response`.
Y las herramientas también estarán felices porque tanto `RedirectResponse` como `JSONResponse` son subclases de `Response`, por lo que la anotación del tipo es correcta.
-### Anotar una Subclase de Response
+### Anotar una Subclase de Response { #annotate-a-response-subclass }
También puedes usar una subclase de `Response` en la anotación del tipo:
-{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
+{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
Esto también funcionará porque `RedirectResponse` es una subclase de `Response`, y FastAPI manejará automáticamente este caso simple.
-### Anotaciones de Tipos de Retorno Inválidas
+### Anotaciones de Tipos de Retorno Inválidas { #invalid-return-type-annotations }
Pero cuando devuelves algún otro objeto arbitrario que no es un tipo válido de Pydantic (por ejemplo, un objeto de base de datos) y lo anotas así en la función, FastAPI intentará crear un modelo de response de Pydantic a partir de esa anotación de tipo, y fallará.
@@ -207,7 +207,7 @@ Lo mismo sucedería si tuvieras algo como un I/O اشاره داره که نسبتاً "آروم" هستن (نسبت به سرعت پردازنده و حافظه RAM)، مثل منتظر موندن برای:
-
-* دادههایی که از کلاینت از طریق شبکه فرستاده میشن
-* دادههایی که برنامهات فرستاده تا از طریق شبکه به کلاینت برسه
-* محتوای یه فایل توی دیسک که سیستم بخوندش و به برنامهات بده
-* محتوایی که برنامهات به سیستم داده تا توی دیسک بنویسه
-* یه عملیات API از راه دور
-* یه عملیات دیتابیس که تموم بشه
-* یه کوئری دیتابیس که نتایجش برگرده
-* و غیره.
-
-چون زمان اجرا بیشتر صرف انتظار برای عملیات I/O میشه، بهشون میگن عملیات "I/O bound".
-
-بهش "ناهمزمان" میگن چون کامپیوتر / برنامه لازم نیست با کار آروم "همزمان" باشه، منتظر لحظه دقیق تموم شدن کار بمونه، در حالی که هیچ کاری نمیکنه، تا نتیجه رو بگیره و کارش رو ادامه بده.
-
-به جاش، چون یه سیستم "ناهمزمان" هست، وقتی کار تموم شد، میتونه یه کم توی صف منتظر بمونه (چند میکروثانیه) تا کامپیوتر / برنامه هر کاری که رفته بکنه رو تموم کنه، و بعد برگرده نتیجه رو بگیره و باهاش کار کنه.
-
-برای "همزمان" (برخلاف "ناهمزمان") معمولاً از اصطلاح "ترتیبی" هم استفاده میکنن، چون کامپیوتر / برنامه همه مراحل رو به ترتیب دنبال میکنه قبل از اینکه بره سراغ یه کار دیگه، حتی اگه اون مراحل شامل انتظار باشن.
-
-### همزمانی و برگرها
-
-این ایده **ناهمزمان** که بالا توضیح دادم گاهی بهش **"همزمانی"** هم میگن. با **"موازیسازی"** فرق داره.
-
-**همزمانی** و **موازیسازی** هر دو به "اتفاق افتادن چیزای مختلف کموبیش همزمان" ربط دارن.
-
-ولی جزئیات بین *همزمانی* و *موازیسازی* خیلی متفاوته.
-
-برای دیدن فرقش، این داستان در مورد برگرها رو تصور کن:
-
-### برگرهای همزمان
-
-با عشقت میری فستفود بگیرین، توی صف وایمیستی در حالی که صندوقدار سفارش آدمای جلوی تو رو میگیره. 😍
-
-
-
-بعد نوبت تو میشه، سفارش دو تا برگر خیلی شیک برای خودت و عشقت میدی. 🍔🍔
-
-
-
-صندوقدار یه چیزی به آشپز توی آشپزخونه میگه تا بدونن باید برگرهای تو رو آماده کنن (گرچه الان دارن برگرهای مشتریای قبلی رو درست میکنن).
-
-
-
-پول رو میدی. 💸
-
-صندوقدار شماره نوبتت رو بهت میده.
-
-
-
-وقتی منتظری، با عشقت میری یه میز انتخاب میکنی، میشینی و کلی با عشقت حرف میزنی (چون برگرهات خیلی شیکن و آماده کردنشون یه کم طول میکشه).
-
-وقتی پشت میز با عشقت نشستی، در حالی که منتظر برگرهایی، میتونی اون زمان رو صرف تحسین این کنی که عشقت چقدر باحال، ناز و باهوشه ✨😍✨.
-
-
-
-وقتی منتظری و با عشقت حرف میزنی، هر از گاهی شمارهای که رو پیشخون نشون داده میشه رو چک میکنی که ببینی نوبتت شده یا نه.
-
-بعد یه جایی بالاخره نوبتت میشه. میری پیشخون، برگرهات رو میگیری و برمیگردی سر میز.
-
-
-
-تو و عشقت برگرها رو میخورین و یه وقت خوب باهم دارین. ✨
-
-
-
-/// info
-
-تصاویر قشنگ از کترینا تامپسون. 🎨
-
-///
-
----
-
-تصور کن تو توی این داستان کامپیوتر / برنامه 🤖 هستی.
-
-وقتی توی صف هستی، فقط بیکاری 😴، منتظر نوبتت هستی، کار خیلی "مفیدی" نمیکنی. ولی صف سریع پیش میره چون صندوقدار فقط سفارش میگیره (آمادشون نمیکنه)، پس این خوبه.
-
-بعد، وقتی نوبتت میشه، کار "مفید" واقعی میکنی، منو رو پردازش میکنی، تصمیم میگیری چی میخوای، انتخاب عشقت رو میگیری، پول میدی، چک میکنی اسکناس یا کارت درست رو دادی، چک میکنی درست حساب شده، چک میکنی سفارش آیتمای درست رو داره و غیره.
-
-ولی بعد، گرچه هنوز برگرهات رو نداری، کارت با صندوقدار "موقتاً متوقف" ⏸ میشه، چون باید منتظر بمونی 🕙 تا برگرهات آماده بشن.
-
-ولی وقتی از پیشخون دور میشی و با شماره نوبتت سر میز میشینی، میتونی توجهت رو 🔀 به عشقت بدی و "کار" ⏯ 🤓 رو اون بکنی. بعدش دوباره داری یه چیز خیلی "مفید" انجام میدی، مثل لاس زدن با عشقت 😍.
-
-بعد صندوقدار 💁 با گذاشتن شمارهات رو نمایشگر پیشخون میگه "من با درست کردن برگرها تموم کردم"، ولی تو مثل دیوونهها وقتی شمارهات رو نمایشگر میاد فوری نمیپری. میدونی کسی برگرهات رو نمیدزده چون شماره نوبتت رو داری، و اونا هم مال خودشون رو دارن.
-
-پس منتظر میمونی تا عشقت داستانش رو تموم کنه (کار فعلی ⏯ / وظیفهای که داره پردازش میشه 🤓)، آروم لبخند میزنی و میگی که میری برگرها رو بیاری ⏸.
-
-بعد میری پیشخون 🔀، به کار اولیه که حالا تموم شده ⏯، برگرها رو میگیری، تشکر میکنی و میبرشون سر میز. این مرحله / وظیفه تعامل با پیشخون رو تموم میکنه ⏹. این به نوبه خودش یه وظیفه جدید، "خوردن برگرها" 🔀 ⏯، میسازه، ولی اون قبلی که "گرفتن برگرها" بود تموم شده ⏹.
-
-### برگرهای موازی
-
-حالا فرض کن اینا "برگرهای همزمان" نیستن، بلکه "برگرهای موازی" هستن.
-
-با عشقت میری فستفود موازی بگیری.
-
-توی صف وایمیستی در حالی که چند تا (مثلاً 8 تا) صندوقدار که همزمان آشپز هم هستن سفارش آدمای جلوی تو رو میگیرن.
-
-همه قبل تو منتظرن برگرهاشون آماده بشه قبل از اینکه پیشخون رو ترک کنن، چون هر کدوم از 8 تا صندوقدار میره و برگر رو همون موقع درست میکنه قبل از اینکه سفارش بعدی رو بگیره.
-
-
-
-بالاخره نوبت تو میشه، سفارش دو تا برگر خیلی شیک برای خودت و عشقت میدی.
-
-پول رو میدی 💸.
-
-
-
-صندوقدار میره آشپزخونه.
-
-منتظر میمونی، جلوی پیشخون وایستادی 🕙، که کسی قبل از تو برگرهات رو نگیره، چون شماره نوبت نیست.
-
-
-
-چون تو و عشقت مشغول این هستین که نذارین کسی جلوتون بیاد و هر وقت برگرها رسیدن اونا رو بگیره، نمیتونی به عشقت توجه کنی. 😞
-
-این کار "همزمان" هست، تو با صندوقدار/آشپز 👨🍳 "همزمان" هستی. باید منتظر بمونی 🕙 و درست همون لحظه که صندوقدار/آشپز 👨🍳 برگرها رو تموم میکنه و بهت میده اونجا باشی، وگرنه ممکنه یکی دیگه اونا رو بگیره.
-
-
-
-بعد صندوقدار/آشپزت 👨🍳 بالاخره بعد از یه مدت طولانی انتظار 🕙 جلوی پیشخون با برگرهات برمیگرده.
-
-
-
-برگرهات رو میگیری و با عشقت میری سر میز.
-
-فقط میخورینشون، و تمومه. ⏹
-
-
-
-حرف زدن یا لاس زدن زیاد نبود چون بیشتر وقت صرف انتظار 🕙 جلوی پیشخون شد. 😞
-
-/// info
-
-تصاویر قشنگ از کترینا تامپسون. 🎨
-
-///
-
----
-
-توی این سناریوی برگرهای موازی، تو یه کامپیوتر / برنامه 🤖 با دو تا پردازنده (تو و عشقت) هستی، هر دو منتظر 🕙 و توجهشون ⏯ رو برای مدت طولانی "انتظار جلوی پیشخون" 🕙 گذاشتن.
-
-فستفود 8 تا پردازنده (صندوقدار/آشپز) داره. در حالی که فستفود برگرهای همزمان شاید فقط 2 تا داشته (یه صندوقدار و یه آشپز).
-
-ولی با این حال، تجربه نهایی بهترین نیست. 😞
-
----
-
-این معادل موازی داستان برگرها بود. 🍔
-
-برای یه مثال "واقعیتر" از زندگی، یه بانک رو تصور کن.
-
-تا همین چند وقت پیش، بیشتر بانکها چند تا صندوقدار 👨💼👨💼👨💼👨💼 داشتن و یه صف بزرگ 🕙🕙🕙🕙🕙🕙🕙🕙.
-
-همه صندوقدارها کار رو با یه مشتری بعد از اون یکی 👨💼⏯ انجام میدادن.
-
-و باید توی صف 🕙 مدت زیادی منتظر بمونی وگرنه نوبتت رو از دست میدی.
-
-احتمالاً نمیخوای عشقت 😍 رو با خودت ببری بانک 🏦 برای کارای روزمره.
-
-### نتیجهگیری برگرها
-
-توی این سناریوی "برگرهای فستفود با عشقت"، چون کلی انتظار 🕙 هست، خیلی منطقیتره که یه سیستم همزمان ⏸🔀⏯ داشته باشی.
-
-این برای بیشتر برنامههای وب هم صدق میکنه.
-
-خیلی خیلی کاربر، ولی سرورت منتظر 🕙 اتصال نهچندان خوبشون هست تا درخواستهاشون رو بفرستن.
-
-و بعد دوباره منتظر 🕙 که جوابها برگردن.
-
-این "انتظار" 🕙 توی میکروثانیهها اندازهگیری میشه، ولی با این حال، جمعش که بکنی آخرش کلی انتظار میشه.
-
-برای همین استفاده از کد ناهمزمان ⏸🔀⏯ برای APIهای وب خیلی منطقیه.
-
-این نوع ناهمزمانی چیزیه که NodeJS رو محبوب کرد (گرچه NodeJS موازی نیست) و نقطه قوت Go بهعنوان یه زبون برنامهنویسیه.
-
-و همون سطح عملکردی هست که با **FastAPI** میگیری.
-
-و چون میتونی همزمانی و موازیسازی رو همزمان داشته باشی، عملکرد بالاتری از بیشتر فریمورکهای تستشده NodeJS میگیری و همتراز با Go، که یه زبون کامپایلشده نزدیک به C هست (همه اینا به لطف Starlette).
-
-### آیا همزمانی از موازیسازی بهتره؟
-
-نه! این نتیجه داستان نیست.
-
-همزمانی با موازیسازی فرق داره. و توی **سناریوهای خاص** که کلی انتظار دارن بهتره. به همین خاطر، معمولاً برای توسعه برنامههای وب خیلی از موازیسازی بهتره. ولی نه برای همهچیز.
-
-برای اینکه یه تعادل بذاریم، این داستان کوتاه رو تصور کن:
-
-> باید یه خونه بزرگ و کثیف رو تمیز کنی.
-
-*آره، کل داستان همینه*.
-
----
-
-هیچ انتظاری 🕙 اونجا نیست، فقط کلی کار برای انجام دادن توی جاهای مختلف خونه.
-
-میتونی مثل مثال برگرها نوبت بذاری، اول پذیرایی، بعد آشپزخونه، ولی چون منتظر چیزی نیستی 🕙، فقط داری تمیز میکنی و تمیز میکنی، نوبتها هیچ تأثیری نداره.
-
-با نوبت یا بدون نوبت (همزمانی) همون قدر طول میکشه تا تمومش کنی و همون مقدار کار رو کردی.
-
-ولی توی این موقعیت، اگه بتونی اون 8 تا صندوقدار/آشپز/حالا-تمیزکار رو بیاری، و هر کدومشون (بهعلاوه خودت) یه قسمت از خونه رو تمیز کنن، میتونی همه کار رو **موازی** انجام بدی، با کمک اضافی، و خیلی زودتر تمومش کنی.
-
-توی این سناریو، هر کدوم از تمیزکارها (از جمله خودت) یه پردازندهست که کار خودش رو میکنه.
-
-و چون بیشتر زمان اجرا صرف کار واقعی میشه (به جای انتظار)، و کار توی کامپیوتر با CPU انجام میشه، به این مشکلات میگن "CPU bound".
-
----
-
-مثالهای رایج عملیات CPU bound چیزایی هستن که نیاز به پردازش ریاضی پیچیده دارن.
-
-مثلاً:
-
-* پردازش **صدا** یا **تصویر**.
-* **بینایی کامپیوتری**: یه تصویر از میلیونها پیکسل تشکیل شده، هر پیکسل 3 تا مقدار / رنگ داره، پردازشش معمولاً نیاز داره چیزی رو رو اون پیکسلها همزمان حساب کنی.
-* **یادگیری ماشین**: معمولاً کلی ضرب "ماتریس" و "بردار" لازم داره. یه جدول بزرگ پر از عدد رو تصور کن که همهشون رو همزمان ضرب میکنی.
-* **یادگیری عمیق**: این یه زیرشاخه از یادگیری ماشینه، پس همون قضیه صدق میکنه. فقط این که یه جدول عدد برای ضرب کردن نیست، بلکه یه مجموعه بزرگ از اونا هست، و توی خیلی موارد از یه پردازنده خاص برای ساخت و / یا استفاده از این مدلها استفاده میکنی.
-
-### همزمانی + موازیسازی: وب + یادگیری ماشین
-
-با **FastAPI** میتونی از همزمانی که برای توسعه وب خیلی رایجه (همون جذابیت اصلی NodeJS) استفاده کنی.
-
-ولی میتونی از فواید موازیسازی و چندپردازشی (اجرای چند پروسه بهصورت موازی) برای کارای **CPU bound** مثل سیستمهای یادگیری ماشین هم بهره ببری.
-
-این، بهعلاوه این واقعیت ساده که پایتون زبون اصلی برای **علم داده**، یادگیری ماشین و بهخصوص یادگیری عمیقه، باعث میشه FastAPI یه انتخاب خیلی خوب برای APIها و برنامههای وب علم داده / یادگیری ماشین باشه (بین خیلی چیزای دیگه).
-
-برای دیدن اینکه چطور توی محیط واقعی به این موازیسازی برسی، بخش [استقرار](deployment/index.md){.internal-link target=_blank} رو ببین.
-
-## `async` و `await`
-
-نسخههای مدرن پایتون یه راه خیلی ساده و قابلفهم برای تعریف کد ناهمزمان دارن. این باعث میشه مثل کد "ترتیبی" معمولی به نظر بیاد و توی لحظههای درست "انتظار" رو برات انجام بده.
-
-وقتی یه عملیاتی هست که قبل از دادن نتیجهها نیاز به انتظار داره و از این قابلیتهای جدید پایتون پشتیبانی میکنه، میتونی اینجوری کدنویسیش کنی:
-
-```Python
-burgers = await get_burgers(2)
-```
-
-نکته کلیدی اینجا `await` هست. به پایتون میگه که باید ⏸ منتظر بمونه تا `get_burgers(2)` کارش 🕙 تموم بشه قبل از اینکه نتیجهها رو توی `burgers` ذخیره کنه. با این، پایتون میدونه که میتونه بره یه کار دیگه 🔀 ⏯ توی این مدت بکنه (مثل گرفتن یه درخواست دیگه).
-
-برای اینکه `await` کار کنه، باید توی یه تابع باشه که از این ناهمزمانی پشتیبانی کنه. برای این کار، فقط با `async def` تعریفش میکنی:
-
-```Python hl_lines="1"
-async def get_burgers(number: int):
- # یه سری کار ناهمزمان برای ساختن برگرها انجام بده
- return burgers
-```
-
-...به جای `def`:
-
-```Python hl_lines="2"
-# این ناهمزمان نیست
-def get_sequential_burgers(number: int):
- # یه سری کار ترتیبی برای ساختن برگرها انجام بده
- return burgers
-```
-
-با `async def`، پایتون میدونه که توی اون تابع باید حواسش به عبارتهای `await` باشه، و میتونه اجرای اون تابع رو "موقتاً متوقف" ⏸ کنه و بره یه کار دیگه 🔀 قبل از برگشتن بکنه.
-
-وقتی میخوای یه تابع `async def` رو صدا کنی، باید "منتظرش" بمونی. پس این کار نمیکنه:
-
-```Python
-# این کار نمیکنه، چون get_burgers با async def تعریف شده
-burgers = get_burgers(2)
-```
-
----
-
-پس، اگه از یه کتابخونه استفاده میکنی که بهت میگه میتونی با `await` صداش کنی، باید *توابع عملیات مسیرت* که ازش استفاده میکنن رو با `async def` بسازی، مثل:
-
-```Python hl_lines="2-3"
-@app.get('/burgers')
-async def read_burgers():
- burgers = await get_burgers(2)
- return burgers
-```
-
-### جزئیات فنیتر
-
-شاید متوجه شده باشی که `await` فقط توی توابعی که با `async def` تعریف شدن میتونه استفاده بشه.
-
-ولی در عین حال، توابعی که با `async def` تعریف شدن باید "منتظر"شون بمونی. پس توابع با `async def` فقط توی توابعی که با `async def` تعریف شدن میتونن صدا زده بشن.
-
-حالا، قضیه مرغ و تخممرغ چیه، چطور اولین تابع `async` رو صدا میکنی؟
-
-اگه با **FastAPI** کار میکنی، لازم نیست نگران این باشی، چون اون "اولین" تابع، *تابع عملیات مسیرت* هست، و FastAPI میدونه چطور کار درست رو بکنه.
-
-ولی اگه بخوای بدون FastAPI از `async` / `await` استفاده کنی، اینم ممکنه.
-
-### کد ناهمزمان خودت رو بنویس
-
-Starlette (و **FastAPI**) بر پایه AnyIO هستن، که باعث میشه با کتابخونه استاندارد پایتون asyncio و Trio سازگار باشه.
-
-بهخصوص، میتونی مستقیماً از AnyIO برای موارد استفاده پیشرفته همزمانی که نیاز به الگوهای پیچیدهتر توی کد خودت دارن استفاده کنی.
-
-و حتی اگه از FastAPI استفاده نکنی، میتونی برنامههای ناهمزمان خودت رو با AnyIO بنویسی تا خیلی سازگار باشه و فوایدش رو بگیری (مثل *همزمانی ساختاریافته*).
-
-من یه کتابخونه دیگه روی AnyIO ساختم، یه لایه نازک روش، تا یه کم annotationهای نوع رو بهتر کنم و **تکمیل خودکار** بهتر، **خطاهای درونخطی** و غیره بگیرم. یه مقدمه و آموزش ساده هم داره که بهت کمک میکنه **بفهمی** و **کد ناهمزمان خودت رو بنویسی**: Asyncer. اگه بخوای **کد ناهمزمان رو با کد معمولی** (بلاککننده/همزمان) ترکیب کنی خیلی بهدردت میخوره.
-
-### شکلهای دیگه کد ناهمزمان
-
-این سبک استفاده از `async` و `await` توی زبون نسبتاً جدیده.
-
-ولی کار با کد ناهمزمان رو خیلی سادهتر میکنه.
-
-همین سینتکس (یا تقریباً یکسان) اخیراً توی نسخههای مدرن جاوااسکریپت (توی مرورگر و NodeJS) هم اضافه شده.
-
-ولی قبل از اون، مدیریت کد ناهمزمان خیلی پیچیدهتر و سختتر بود.
-
-توی نسخههای قبلی پایتون، میتونستی از نخها یا Gevent استفاده کنی. ولی کد خیلی پیچیدهتر میشه برای فهمیدن، دیباگ کردن و فکر کردن بهش.
-
-توی نسخههای قبلی NodeJS / جاوااسکریپت مرورگر، از "کالبکها" استفاده میکردی. که میرسید به "جهان کالبکها".
-
-## کروتینها
-
-**کروتین** فقط یه اصطلاح خیلی شیک برای چیزیه که یه تابع `async def` برمیگردونه. پایتون میدونه که این یه چیزی مثل تابع هست، میتونه شروع بشه و یه جایی تموم بشه، ولی ممکنه داخلش هم موقف ⏸ بشه، هر وقت یه `await` توش باشه.
-
-ولی همه این قابلیت استفاده از کد ناهمزمان با `async` و `await` خیلی وقتا خلاصه میشه به استفاده از "کروتینها". این قابل مقایسه با ویژگی اصلی Go، یعنی "Goroutineها" هست.
-
-## نتیجهگیری
-
-بیاید همون جمله از بالا رو ببینیم:
-
-> نسخههای مدرن پایتون از **"کد ناهمزمان"** با چیزی که بهش **"کروتین"** میگن پشتیبانی میکنن، با سینتکس **`async` و `await`**.
-
-حالا باید بیشتر برات معنی بده. ✨
-
-همه اینا چیزیه که به FastAPI (از طریق Starlette) قدرت میده و باعث میشه عملکرد چشمگیری داشته باشه.
-
-## جزئیات خیلی فنی
-
-/// warning
-
-احتمالاً میتونی اینو رد کنی.
-
-اینا جزئیات خیلی فنی از نحوه کار **FastAPI** زیر پوستهست.
-
-اگه یه کم دانش فنی (کروتینها، نخها، بلاک کردن و غیره) داری و کنجکاوی که FastAPI چطور `async def` رو در مقابل `def` معمولی مدیریت میکنه، ادامه بده.
-
-///
-
-### توابع عملیات مسیر
-
-وقتی یه *تابع عملیات مسیر* رو با `def` معمولی به جای `async def` تعریف میکنی، توی یه استخر نخ خارجی اجرا میشه که بعدش منتظرش میمونن، به جای اینکه مستقیم صداش کنن (چون سرور رو بلاک میکنه).
-
-اگه از یه فریمورک ناهمزمان دیگه میای که به روش بالا کار نمیکنه و عادت داری *توابع عملیات مسیر* ساده فقط محاسباتی رو با `def` معمولی برای یه سود کوچیک عملکرد (حدود 100 نانوثانیه) تعریف کنی، توجه کن که توی **FastAPI** اثرش کاملاً برعکسه. توی این موارد، بهتره از `async def` استفاده کنی مگه اینکه *توابع عملیات مسیرت* کدی داشته باشن که عملیات I/O بلاککننده انجام بده.
-
-با این حال، توی هر دو موقعیت، احتمالش زیاده که **FastAPI** هنوز [سریعتر](index.md#performance){.internal-link target=_blank} از فریمورک قبلیات باشه (یا حداقل قابل مقایسه باهاش).
-
-### وابستگیها
-
-همین برای [وابستگیها](tutorial/dependencies/index.md){.internal-link target=_blank} هم صدق میکنه. اگه یه وابستگی یه تابع `def` معمولی به جای `async def` باشه، توی استخر نخ خارجی اجرا میشه.
-
-### زیروابستگیها
-
-میتونی چند تا وابستگی و [زیروابستگی](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} داشته باشی که همدیگه رو نیاز دارن (بهعنوان پارامترهای تعریف تابع)، بعضیهاشون ممکنه با `async def` ساخته بشن و بعضیها با `def` معمولی. بازم کار میکنه، و اونایی که با `def` معمولی ساخته شدن توی یه نخ خارجی (از استخر نخ) صدا زده میشن به جای اینکه "منتظرشون" بمونن.
-
-### توابع کاربردی دیگه
-
-هر تابع کاربردی دیگهای که مستقیم خودت صداش میکنی میتونه با `def` معمولی یا `async def` ساخته بشه و FastAPI رو نحوه صدازدنش تأثیر نمیذاره.
-
-این برخلاف توابعی هست که FastAPI برات صداشون میکنه: *توابع عملیات مسیر* و وابستگیها.
-
-اگه تابع کاربردیت یه تابع معمولی با `def` باشه، مستقیم صداش میکنن (همونطور که توی کدت نوشتی)، نه توی استخر نخ، اگه تابع با `async def` ساخته شده باشه، باید وقتی توی کدت صداش میکنی `await`ش کنی.
-
----
-
-دوباره، اینا جزئیات خیلی فنی هستن که احتمالاً اگه دنبالشون اومده باشی برات مفید باشن.
-
-وگرنه، با راهنماییهای بخش بالا باید خوب باشی: عجله داری؟.
diff --git a/docs/fa/docs/environment-variables.md b/docs/fa/docs/environment-variables.md
deleted file mode 100644
index 75309ce1fe..0000000000
--- a/docs/fa/docs/environment-variables.md
+++ /dev/null
@@ -1,298 +0,0 @@
-# متغیرهای محیطی
-
-/// tip
-
-اگه از قبل میدونی متغیرهای محیطی چی هستن و چطور ازشون استفاده میشه، میتونی این بخش رو رد کنی.
-
-///
-
-یه متغیر محیطی (که بهش "**env var**" هم میگن) یه متغیریه که **خارج** از کد پایتون، توی **سیستمعامل** زندگی میکنه و میتونه توسط کد پایتونت (یا برنامههای دیگه) خونده بشه.
-
-متغیرهای محیطی میتونن برای مدیریت **تنظیمات** برنامه، بهعنوان بخشی از **نصب** پایتون و غیره مفید باشن.
-
-## ساخت و استفاده از متغیرهای محیطی
-
-میتونی متغیرهای محیطی رو توی **شل (ترمینال)** **بسازی** و ازشون استفاده کنی، بدون اینکه به پایتون نیاز داشته باشی:
-
-//// tab | لینوکس، مکاواس، ویندوز بش
-
-- فریمورک FastAPI، کارایی بالا، یادگیری آسان، کدنویسی سریع، آماده برای استفاده در محیط پروداکشن -
- - ---- - -**مستندات**: https://fastapi.tiangolo.com - -**کد منبع**: https://github.com/fastapi/fastapi - ---- -FastAPI یک وب فریمورک مدرن و سریع (با کارایی بالا) برای ایجاد APIهای متنوع (وب، وبسوکت و غبره) با زبان پایتون نسخه +۳.۶ است. این فریمورک با رعایت کامل راهنمای نوع داده (Type Hint) ایجاد شده است. - -ویژگیهای کلیدی این فریمورک عبارتند از: - -* **سرعت**: کارایی بسیار بالا و قابل مقایسه با **NodeJS** و **Go** (با تشکر از Starlette و Pydantic). [یکی از سریعترین فریمورکهای پایتونی موجود](#_10). - -* **کدنویسی سریع**: افزایش ۲۰۰ تا ۳۰۰ درصدی سرعت توسعه قابلیتهای جدید. * -* **باگ کمتر**: کاهش ۴۰ درصدی خطاهای انسانی (برنامهنویسی). * -* **هوشمندانه**: پشتیبانی فوقالعاده در محیطهای توسعه یکپارچه (IDE). تکمیل در همه بخشهای کد. کاهش زمان رفع باگ. -* **آسان**: طراحی شده برای یادگیری و استفاده آسان. کاهش زمان مورد نیاز برای مراجعه به مستندات. -* **کوچک**: کاهش تکرار در کد. چندین قابلیت برای هر پارامتر (منظور پارامترهای ورودی تابع هندلر میباشد، به بخش خلاصه در همین صفحه مراجعه شود). باگ کمتر. -* **استوار**: ایجاد کدی آماده برای استفاده در محیط پروداکشن و تولید خودکار مستندات تعاملی -* **مبتنی بر استانداردها**: مبتنی بر (و منطبق با) استانداردهای متن باز مربوط به API: OpenAPI (سوگر سابق) و JSON Schema. - -* تخمینها بر اساس تستهای انجام شده در یک تیم توسعه داخلی که مشغول ایجاد برنامههای کاربردی واقعی بودند صورت گرفته است. - -## اسپانسرهای طلایی - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} -async def... نیز استفاده کنیدuvicorn main:app --reload...email-validator - برای اعتبارسنجی آدرسهای ایمیل.
-
-استفاده شده توسط Starlette:
-
-* HTTPX - در صورتی که میخواهید از `TestClient` استفاده کنید.
-* aiofiles - در صورتی که میخواهید از `FileResponse` و `StaticFiles` استفاده کنید.
-* jinja2 - در صورتی که بخواهید از پیکربندی پیشفرض برای قالبها استفاده کنید.
-* python-multipart - در صورتی که بخواهید با استفاده از `request.form()` از قابلیت "تجزیه (parse)" فرم استفاده کنید.
-* itsdangerous - در صورتی که بخواید از `SessionMiddleware` پشتیبانی کنید.
-* pyyaml - برای پشتیبانی `SchemaGenerator` در Starlet (به احتمال زیاد برای کار کردن با FastAPI به آن نیازی پیدا نمیکنید).
-* graphene - در صورتی که از `GraphQLApp` پشتیبانی میکنید.
-
-استفاده شده توسط FastAPI / Starlette:
-
-* uvicorn - برای سرور اجرا کننده برنامه وب.
-* orjson - در صورتی که بخواهید از `ORJSONResponse` استفاده کنید.
-* ujson - در صورتی که بخواهید از `UJSONResponse` استفاده کنید.
-
-میتوان همه این موارد را با استفاده از دستور `pip install fastapi[all]`. به صورت یکجا نصب کرد.
-
-## لایسنس
-
-این پروژه مشمول قوانین و مقررات لایسنس MIT است.
diff --git a/docs/fa/docs/learn/index.md b/docs/fa/docs/learn/index.md
deleted file mode 100644
index 06aa7f00e7..0000000000
--- a/docs/fa/docs/learn/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# یادگیری
-
-اینجا بخشهای مقدماتی و آموزشهایی هستن که برای یادگیری **FastAPI** بهت کمک میکنن.
-
-میتونی اینو یه **کتاب**، یه **دوره آموزشی**، یا راه **رسمی** و پیشنهادی برای یادگیری FastAPI در نظر بگیری. 😎
diff --git a/docs/fa/docs/python-types.md b/docs/fa/docs/python-types.md
deleted file mode 100644
index c428acbf74..0000000000
--- a/docs/fa/docs/python-types.md
+++ /dev/null
@@ -1,578 +0,0 @@
-# مقدمهای بر انواع نوع در پایتون
-
-پایتون از "نوعنما"های اختیاری (که بهشون "type hints" یا "type annotations" هم میگن) پشتیبانی میکنه.
-
-این **"نوعنماها"** یا annotationها یه سینتکس خاص هستن که بهت اجازه میدن نوع یه متغیر رو مشخص کنی.
-
-با مشخص کردن نوع متغیرها، ویرایشگرها و ابزارها میتونن پشتیبانی بهتری بهت بدن.
-
-این فقط یه **آموزش سریع / یادآوری** در مورد نوعنماهای پایتونه. فقط حداقل چیزایی که برای استفاده ازشون با **FastAPI** لازمه رو پوشش میده... که در واقع خیلی کمه.
-
-**FastAPI** کاملاً بر پایه این نوعنماهاست و این بهش کلی مزیت و فایده میده.
-
-ولی حتی اگه هیچوقت از **FastAPI** استفاده نکنی، بازم یادگیری یه کم در موردشون به نفعته.
-
-/// note
-
-اگه حرفهای پایتونی و همهچیز رو در مورد نوعنماها میدونی، برو سراغ فصل بعدی.
-
-///
-
-## انگیزه
-
-بیاید با یه مثال ساده شروع کنیم:
-
-{* ../../docs_src/python_types/tutorial001.py *}
-
-وقتی این برنامه رو اجرا کنی، خروجی اینه:
-
-```
-John Doe
-```
-
-این تابع این کارا رو میکنه:
-
-* یه `first_name` و `last_name` میگیره.
-* حرف اول هر کدوم رو با `title()` بزرگ میکنه.
-* ترکیبشون میکنه با یه فاصله وسطشون.
-
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
-
-### ویرایشش کن
-
-این یه برنامه خیلی سادهست.
-
-ولی حالا تصور کن داری از صفر مینویسیش.
-
-یه جایی شروع کردی به تعریف تابع، پارامترهات آمادهست...
-
-ولی بعد باید "اون متدی که حرف اول رو بزرگ میکنه" رو صدا کنی.
-
-آیا اسمش `upper` بود؟ یا `uppercase`؟ شاید `first_uppercase`؟ یا `capitalize`؟
-
-بعد، با دوست قدیمی برنامهنویسا، تکمیل خودکار ویرایشگر، امتحان میکنی.
-
-پارامتر اول تابع، `first_name` رو تایپ میکنی، بعد یه نقطه (`.`) میذاری و `Ctrl+Space` رو میزنی تا تکمیل خودکار بیاد.
-
-ولی متأسفانه، چیز مفیدی نمیگیری:
-
-
-
-### نوع اضافه کن
-
-بیا فقط یه خط از نسخه قبلی رو تغییر بدیم.
-
-دقیقاً این بخش، پارامترهای تابع رو، از:
-
-```Python
- first_name, last_name
-```
-
-به:
-
-```Python
- first_name: str, last_name: str
-```
-
-عوض میکنیم.
-
-همینه.
-
-اینا همون "نوعنماها" هستن:
-
-{* ../../docs_src/python_types/tutorial002.py hl[1] *}
-
-این با تعریف مقدار پیشفرض فرق داره، مثل:
-
-```Python
- first_name="john", last_name="doe"
-```
-
-یه چیز متفاوته.
-
-ما از دونقطه (`:`) استفاده میکنیم، نه علامت مساوی (`=`).
-
-و اضافه کردن نوعنماها معمولاً چیزی که اتفاق میافته رو از چیزی که بدون اونا میافتاد تغییر نمیده.
-
-ولی حالا، دوباره تصور کن وسط ساختن اون تابع هستی، ولی این بار با نوعنماها.
-
-توی همون نقطه، سعی میکنی تکمیل خودکار رو با `Ctrl+Space` فعال کنی و اینو میبینی:
-
-
-
-با این، میتونی اسکرول کنی، گزینهها رو ببینی، تا وقتی که اون چیزی که "به نظرت آشنا میاد" رو پیدا کنی:
-
-
-
-## انگیزه بیشتر
-
-این تابع رو چک کن، الان نوعنما داره:
-
-{* ../../docs_src/python_types/tutorial003.py hl[1] *}
-
-چون ویرایشگر نوع متغیرها رو میدونه، فقط تکمیل خودکار نمیگیری، بلکه چک خطاها هم داری:
-
-
-
-حالا میدونی که باید درستش کنی، `age` رو با `str(age)` به یه رشته تبدیل کنی:
-
-{* ../../docs_src/python_types/tutorial004.py hl[2] *}
-
-## تعریف نوعها
-
-تازه اصلیترین جا برای تعریف نوعنماها رو دیدی. بهعنوان پارامترهای تابع.
-
-این هم اصلیترین جاییه که با **FastAPI** ازشون استفاده میکنی.
-
-### نوعهای ساده
-
-میتونی همه نوعهای استاندارد پایتون رو تعریف کنی، نه فقط `str`.
-
-مثلاً میتونی از اینا استفاده کنی:
-
-* `int`
-* `float`
-* `bool`
-* `bytes`
-
-{* ../../docs_src/python_types/tutorial005.py hl[1] *}
-
-### نوعهای عمومی با پارامترهای نوع
-
-یه سری ساختار داده هستن که میتونن مقدارهای دیگه رو نگه دارن، مثل `dict`، `list`، `set` و `tuple`. و مقدارهای داخلیشون هم میتونن نوع خودشون رو داشته باشن.
-
-به این نوعها که نوعهای داخلی دارن میگن "**عمومی**" یا "generic". و میشه اونا رو تعریف کرد، حتی با نوعهای داخلیشون.
-
-برای تعریف این نوعها و نوعهای داخلیشون، میتونی از ماژول استاندارد پایتون `typing` استفاده کنی. این ماژول مخصوص پشتیبانی از نوعنماهاست.
-
-#### نسخههای جدیدتر پایتون
-
-سینتکس با استفاده از `typing` با همه نسخهها، از پایتون 3.6 تا جدیدترینها، از جمله پایتون 3.9، 3.10 و غیره **سازگاره**.
-
-با پیشرفت پایتون، **نسخههای جدیدتر** پشتیبانی بهتری برای این نوعنماها دارن و توی خیلی موارد حتی لازم نیست ماژول `typing` رو وارد کنی و ازش برای تعریف نوعنماها استفاده کنی.
-
-اگه بتونی برای پروژهات از یه نسخه جدیدتر پایتون استفاده کنی، میتونی از این سادگی اضافه بهره ببری.
-
-توی همه مستندات، مثالهایی هستن که با هر نسخه پایتون سازگارن (وقتی تفاوتی هست).
-
-مثلاً "**Python 3.6+**" یعنی با پایتون 3.6 یا بالاتر (مثل 3.7، 3.8، 3.9، 3.10 و غیره) سازگاره. و "**Python 3.9+**" یعنی با پایتون 3.9 یا بالاتر (مثل 3.10 و غیره) سازگاره.
-
-اگه بتونی از **جدیدترین نسخههای پایتون** استفاده کنی، از مثالهای نسخه آخر استفاده کن، چون اونا **بهترین و سادهترین سینتکس** رو دارن، مثلاً "**Python 3.10+**".
-
-#### لیست
-
-مثلاً، بیایم یه متغیر تعریف کنیم که یه `list` از `str` باشه.
-
-//// tab | Python 3.9+
-
-متغیر رو با همون سینتکس دونقطه (`:`) تعریف کن.
-
-بهعنوان نوع، `list` رو بذار.
-
-چون لیست یه نوعه که نوعهای داخلی داره، اونا رو توی کروشهها میذاری:
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-از `typing`، `List` رو (با `L` بزرگ) وارد کن:
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-متغیر رو با همون سینتکس دونقطه (`:`) تعریف کن.
-
-بهعنوان نوع، `List` رو که از `typing` وارد کردی بذار.
-
-چون لیست یه نوعه که نوعهای داخلی داره، اونا رو توی کروشهها میذاری:
-
-```Python hl_lines="4"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-////
-
-/// info
-
-اون نوعهای داخلی توی کروشهها بهشون "پارامترهای نوع" میگن.
-
-توی این مورد، `str` پارامتر نوعیه که به `List` (یا `list` توی پایتون 3.9 و بالاتر) پاس داده شده.
-
-///
-
-یعنی: "متغیر `items` یه `list` هست، و هر کدوم از آیتمهای این لیست یه `str` هستن".
-
-/// tip
-
-اگه از پایتون 3.9 یا بالاتر استفاده میکنی، لازم نیست `List` رو از `typing` وارد کنی، میتونی همون نوع معمولی `list` رو به جاش استفاده کنی.
-
-///
-
-با این کار، ویرایشگرت حتی وقتی داری آیتمهای لیست رو پردازش میکنی بهت کمک میکنه:
-
-
-
-بدون نوعها، رسیدن به این تقریباً غیرممکنه.
-
-توجه کن که متغیر `item` یکی از عناصر توی لیست `items` هست.
-
-و با این حال، ویرایشگر میدونه که یه `str` هست و براش پشتیبانی میده.
-
-#### تاپل و ست
-
-برای تعریف `tuple`ها و `set`ها هم همین کار رو میکنی:
-
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial007_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial007.py!}
-```
-
-////
-
-یعنی:
-
-* متغیر `items_t` یه `tuple` با 3 تا آیتمه، یه `int`، یه `int` دیگه، و یه `str`.
-* متغیر `items_s` یه `set` هست، و هر کدوم از آیتمهاش از نوع `bytes` هستن.
-
-#### دیکشنری
-
-برای تعریف یه `dict`، 2 تا پارامتر نوع میدی، که با کاما از هم جدا شدن.
-
-پارامتر نوع اول برای کلیدهای `dict` هست.
-
-پارامتر نوع دوم برای مقدارهای `dict` هست:
-
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008.py!}
-```
-
-////
-
-یعنی:
-
-* متغیر `prices` یه `dict` هست:
- * کلیدهای این `dict` از نوع `str` هستن (مثلاً اسم هر آیتم).
- * مقدارهای این `dict` از نوع `float` هستن (مثلاً قیمت هر آیتم).
-
-#### اتحادیه
-
-میتونی تعریف کنی که یه متغیر میتونه هر کدوم از **چند تا نوع** باشه، مثلاً یه `int` یا یه `str`.
-
-توی پایتون 3.6 و بالاتر (از جمله پایتون 3.10) میتونی از نوع `Union` توی `typing` استفاده کنی و نوعهای ممکن رو توی کروشهها بذاری.
-
-توی پایتون 3.10 یه **سینتکس جدید** هم هست که میتونی نوعهای ممکن رو با یه خط عمودی (`|`) جدا کنی.
-
-//// tab | Python 3.10+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008b_py310.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008b.py!}
-```
-
-////
-
-توی هر دو حالت یعنی `item` میتونه یه `int` یا یه `str` باشه.
-
-#### شاید `None`
-
-میتونی تعریف کنی که یه مقدار میتونه یه نوع باشه، مثلاً `str`، ولی میتونه `None` هم باشه.
-
-توی پایتون 3.6 و بالاتر (از جمله پایتون 3.10) میتونی با وارد کردن و استفاده از `Optional` از ماژول `typing` اینو تعریف کنی.
-
-```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
-```
-
-استفاده از `Optional[str]` به جای فقط `str` به ویرایشگر کمک میکنه خطاهایی که ممکنه فکر کنی یه مقدار همیشه `str` هست رو پیدا کنه، در حالی که میتونه `None` هم باشه.
-
-`Optional[Something]` در واقع میانبر برای `Union[Something, None]` هست، این دو تا معادلن.
-
-یعنی توی پایتون 3.10، میتونی از `Something | None` استفاده کنی:
-
-//// tab | Python 3.10+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial009_py310.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009.py!}
-```
-
-////
-
-//// tab | Python 3.8+ جایگزین
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009b.py!}
-```
-
-////
-
-#### استفاده از `Union` یا `Optional`
-
-اگه از نسخه پایتون زیر 3.10 استفاده میکنی، یه نکته از دید خیلی **شخصی** خودم:
-
-* 🚨 از `Optional[SomeType]` استفاده نکن
-* به جاش ✨ **از `Union[SomeType, None]` استفاده کن** ✨.
-
-هر دو معادلن و زیر پوسته یکیان، ولی من `Union` رو به `Optional` ترجیح میدم چون کلمه "**اختیاری**" انگار暗示 میکنه که مقدار اختیاریه، در حالی که در واقع یعنی "میتونه `None` باشه"، حتی اگه اختیاری نباشه و هنوز لازم باشه.
-
-فکر میکنم `Union[SomeType, None]` واضحتر نشون میده چی معنی میده.
-
-فقط بحث کلمات و اسمهاست. ولی این کلمات میتونن رو طرز فکر تو و تیمت نسبت به کد تأثیر بذارن.
-
-بهعنوان مثال، این تابع رو ببین:
-
-{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *}
-
-پارامتر `name` بهعنوان `Optional[str]` تعریف شده، ولی **اختیاری نیست**، نمیتونی تابع رو بدون پارامتر صدا کنی:
-
-```Python
-say_hi() # اوه نه، این خطا میده! 😱
-```
-
-پارامتر `name` **هنوز لازمه** (نه *اختیاری*) چون مقدار پیشفرض نداره. با این حال، `name` مقدار `None` رو قبول میکنه:
-
-```Python
-say_hi(name=None) # این کار میکنه، None معتبره 🎉
-```
-
-خبر خوب اینه که وقتی رو پایتون 3.10 باشی، لازم نیست نگران این باشی، چون میتونی بهسادگی از `|` برای تعریف اتحادیه نوعها استفاده کنی:
-
-{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
-
-اون موقع دیگه لازم نیست نگران اسمهایی مثل `Optional` و `Union` باشی. 😎
-
-#### نوعهای عمومی
-
-این نوعهایی که پارامترهای نوع رو توی کروشهها میگیرن بهشون **نوعهای عمومی** یا **Generics** میگن، مثلاً:
-
-//// tab | Python 3.10+
-
-میتونی از همون نوعهای داخلی بهعنوان نوعهای عمومی استفاده کنی (با کروشهها و نوعها داخلشون):
-
-* `list`
-* `tuple`
-* `set`
-* `dict`
-
-و همونطور که توی پایتون 3.8 بود، از ماژول `typing`:
-
-* `Union`
-* `Optional` (همونطور که توی پایتون 3.8 بود)
-* ...و بقیه.
-
-توی پایتون 3.10، بهعنوان جایگزین برای استفاده از نوعهای عمومی `Union` و `Optional`، میتونی از خط عمودی (`|`) برای تعریف اتحادیه نوعها استفاده کنی، که خیلی بهتر و سادهتره.
-
-////
-
-//// tab | Python 3.9+
-
-میتونی از همون نوعهای داخلی بهعنوان نوعهای عمومی استفاده کنی (با کروشهها و نوعها داخلشون):
-
-* `list`
-* `tuple`
-* `set`
-* `dict`
-
-و همونطور که توی پایتون 3.8 بود، از ماژول `typing`:
-
-* `Union`
-* `Optional`
-* ...و بقیه.
-
-////
-
-//// tab | Python 3.8+
-
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Union`
-* `Optional`
-* ...و بقیه.
-
-////
-
-### کلاسها بهعنوان نوع
-
-میتونی یه کلاس رو هم بهعنوان نوع یه متغیر تعریف کنی.
-
-فرض کن یه کلاس `Person` داری، با یه نام:
-
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
-
-بعد میتونی یه متغیر رو از نوع `Person` تعریف کنی:
-
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
-
-و بعد، دوباره، همه پشتیبانی ویرایشگر رو داری:
-
-
-
-توجه کن که این یعنی "`one_person` یه **نمونه** از کلاس `Person` هست".
-
-یعنی "`one_person` خود **کلاس** به اسم `Person` نیست".
-
-## مدلهای Pydantic
-
-Pydantic یه کتابخونه پایتونه برای اعتبارسنجی دادهها.
-
-"شکل" دادهها رو بهعنوان کلاسهایی با ویژگیها تعریف میکنی.
-
-و هر ویژگی یه نوع داره.
-
-بعد یه نمونه از اون کلاس رو با یه سری مقدار میسازی و اون مقدارها رو اعتبارسنجی میکنه، به نوع مناسب تبدیلشون میکنه (اگه لازم باشه) و یه شیء با همه دادهها بهت میده.
-
-و با اون شیء نهایی همه پشتیبانی ویرایشگر رو میگیری.
-
-یه مثال از مستندات رسمی Pydantic:
-
-//// tab | Python 3.10+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py310.py!}
-```
-
-////
-
-//// tab | Python 3.9+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011.py!}
-```
-
-////
-
-/// info
-
-برای اطلاعات بیشتر در مورد Pydantic، مستنداتش رو چک کن.
-
-///
-
-**FastAPI** کاملاً بر پایه Pydantic هست.
-
-توی [آموزش - راهنمای کاربر](tutorial/index.md){.internal-link target=_blank} خیلی بیشتر از اینا رو توی عمل میبینی.
-
-/// tip
-
-Pydantic یه رفتار خاص داره وقتی از `Optional` یا `Union[Something, None]` بدون مقدار پیشفرض استفاده میکنی، میتونی توی مستندات Pydantic در مورد فیلدهای اختیاری لازم بیشتر بخونی.
-
-///
-
-## نوعنماها با Annotationهای متادیتا
-
-پایتون یه قابلیت هم داره که بهت اجازه میده **متادیتا اضافی** رو توی این نوعنماها بذاری با استفاده از `Annotated`.
-
-//// tab | Python 3.9+
-
-توی پایتون 3.9، `Annotated` بخشی از کتابخونه استاندارده، پس میتونی از `typing` واردش کنی.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-توی نسخههای زیر پایتون 3.9، `Annotated` رو از `typing_extensions` وارد میکنی.
-
-با **FastAPI** از قبل نصب شده.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013.py!}
-```
-
-////
-
-خود پایتون با این `Annotated` کاری نمیکنه. و برای ویرایشگرها و ابزارهای دیگه، نوع هنوز `str` هست.
-
-ولی میتونی از این فضا توی `Annotated` استفاده کنی تا به **FastAPI** متادیتای اضافی در مورد اینکه چطور میخوای برنامهات رفتار کنه بدی.
-
-نکته مهم اینه که **اولین *پارامتر نوع*** که به `Annotated` میدی، **نوع واقعی** هست. بقیش فقط متادیتا برای ابزارهای دیگهست.
-
-الان فقط باید بدونی که `Annotated` وجود داره، و اینکه پایتون استاندارده. 😎
-
-بعداً میبینی که چقدر **قوی** میتونه باشه.
-
-/// tip
-
-اینکه این **پایتون استاندارده** یعنی هنوز **بهترین تجربه توسعهدهنده** رو توی ویرایشگرت، با ابزارهایی که برای تحلیل و بازسازی کدت استفاده میکنی و غیره میگیری. ✨
-
-و همینطور کدت با خیلی از ابزارها و کتابخونههای دیگه پایتون خیلی سازگار میمونه. 🚀
-
-///
-
-## نوعنماها توی **FastAPI**
-
-**FastAPI** از این نوعنماها استفاده میکنه تا چند تا کار بکنه.
-
-با **FastAPI** پارامترها رو با نوعنماها تعریف میکنی و اینا رو میگیری:
-
-* **پشتیبانی ویرایشگر**.
-* **چک نوعها**.
-
-...و **FastAPI** از همون تعریفها برای اینا استفاده میکنه:
-
-* **تعریف نیازها**: از پارامترهای مسیر درخواست، پارامترهای کوئری، هدرها، بدنهها، وابستگیها و غیره.
-* **تبدیل داده**: از درخواست به نوع مورد نیاز.
-* **اعتبارسنجی داده**: که از هر درخواست میاد:
- * تولید **خطاهای خودکار** که به کلاینت برمیگرده وقتی داده نامعتبره.
-* **مستندسازی** API با استفاده از OpenAPI:
- * که بعدش توسط رابطهای کاربری مستندات تعاملی خودکار استفاده میشه.
-
-اینا شاید همهش انتزاعی به نظر بیاد. نگران نباش. همه اینا رو توی عمل توی [آموزش - راهنمای کاربر](tutorial/index.md){.internal-link target=_blank} میبینی.
-
-نکته مهم اینه که با استفاده از نوعهای استاندارد پایتون، توی یه جا (به جای اضافه کردن کلاسهای بیشتر، دکوراتورها و غیره)، **FastAPI** کلی از کار رو برات انجام میده.
-
-/// info
-
-اگه همه آموزش رو گذروندی و برگشتی که بیشتر در مورد نوعها ببینی، یه منبع خوب "تقلبنامه" از `mypy` هست.
-
-///
diff --git a/docs/fa/docs/tutorial/middleware.md b/docs/fa/docs/tutorial/middleware.md
deleted file mode 100644
index d68c25d82b..0000000000
--- a/docs/fa/docs/tutorial/middleware.md
+++ /dev/null
@@ -1,63 +0,0 @@
-# میانافزار - middleware
-
-شما میتوانید میانافزارها را در **FastAPI** اضافه کنید.
-
-"میانافزار" یک تابع است که با هر درخواست(request) قبل از پردازش توسط هر path operation (عملیات مسیر) خاص کار میکند. همچنین با هر پاسخ(response) قبل از بازگشت آن نیز کار میکند.
-
-* هر **درخواستی (request)** که به برنامه شما می آید را می گیرد.
-* سپس می تواند کاری برای آن **درخواست** انجام دهید یا هر کد مورد نیازتان را اجرا کنید.
-* سپس **درخواست** را به بخش دیگری از برنامه (توسط یک path operation مشخص) برای پردازش ارسال می کند.
-* سپس **پاسخ** تولید شده توسط برنامه را (توسط یک path operation مشخص) دریافت میکند.
-* می تواند کاری با **پاسخ** انجام دهید یا هر کد مورد نیازتان را اجرا کند.
-* سپس **پاسخ** را برمی گرداند.
-
-/// توجه | جزئیات فنی
-
-در صورت وجود وابستگی هایی با `yield`، کد خروجی **پس از** اجرای میانافزار اجرا خواهد شد.
-
-در صورت وجود هر گونه وظایف پس زمینه (که در ادامه توضیح داده میشوند)، تمام میانافزارها *پس از آن* اجرا خواهند شد.
-
-///
-
-## ساخت یک میان افزار
-
-برای ایجاد یک میانافزار، از دکوریتور `@app.middleware("http")` در بالای یک تابع استفاده میشود.
-
-تابع میان افزار دریافت می کند:
-* `درخواست`
-* تابع `call_next` که `درخواست` را به عنوان پارامتر دریافت می کند
- * این تابع `درخواست` را به *path operation* مربوطه ارسال می کند.
- * سپس `پاسخ` تولید شده توسط *path operation* مربوطه را برمیگرداند.
-* شما میتوانید سپس `پاسخ` را تغییر داده و پس از آن را برگردانید.
-
-{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
-
-/// نکته | به خاطر داشته باشید که هدرهای اختصاصی سفارشی را می توان با استفاده از پیشوند "X-" اضافه کرد.
-
-اما اگر هدرهای سفارشی دارید که میخواهید مرورگر کاربر بتواند آنها را ببیند، باید آنها را با استفاده از پارامتر `expose_headers` که در مستندات CORS از Starlette توضیح داده شده است، به پیکربندی CORS خود اضافه کنید.
-
-///
-
-/// توجه | جزئیات فنی
-
-شما همچنین میتوانید از `from starlette.requests import Request` استفاده کنید.
-
-**FastAPI** این را به عنوان یک سهولت برای شما به عنوان برنامهنویس فراهم میکند. اما این مستقیما از Starlette به دست میآید.
-
-///
-
-### قبل و بعد از `پاسخ`
-
-شما میتوانید کدی را برای اجرا با `درخواست`، قبل از اینکه هر *path operation* آن را دریافت کند، اضافه کنید.
-
-همچنین پس از تولید `پاسخ`، قبل از بازگشت آن، میتوانید کدی را اضافه کنید.
-
-به عنوان مثال، میتوانید یک هدر سفارشی به نام `X-Process-Time` که شامل زمان پردازش درخواست و تولید پاسخ به صورت ثانیه است، اضافه کنید.
-
-{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
-
- ## سایر میان افزار
-
-شما میتوانید بعداً در مورد میانافزارهای دیگر در [راهنمای کاربر پیشرفته: میانافزار پیشرفته](../advanced/middleware.md){.internal-link target=_blank} بیشتر بخوانید.
-
-شما در بخش بعدی در مورد این که چگونه با استفاده از یک میانافزار، CORS را مدیریت کنید، خواهید خواند.
diff --git a/docs/fa/docs/tutorial/security/index.md b/docs/fa/docs/tutorial/security/index.md
deleted file mode 100644
index c0827a8b33..0000000000
--- a/docs/fa/docs/tutorial/security/index.md
+++ /dev/null
@@ -1,106 +0,0 @@
-# امنیت
-
-روشهای مختلفی برای مدیریت امنیت، تأیید هویت و اعتبارسنجی وجود دارد.
-
-عموماً این یک موضوع پیچیده و "سخت" است.
-
-در بسیاری از فریم ورک ها و سیستمها، فقط مدیریت امنیت و تأیید هویت نیاز به تلاش و کد نویسی زیادی دارد (در بسیاری از موارد میتواند 50% یا بیشتر کل کد نوشته شده باشد).
-
-
-فریم ورک **FastAPI** ابزارهای متعددی را در اختیار شما قرار می دهد تا به راحتی، با سرعت، به صورت استاندارد و بدون نیاز به مطالعه و یادگیری همه جزئیات امنیت، در مدیریت **امنیت** به شما کمک کند.
-
-اما قبل از آن، بیایید برخی از مفاهیم کوچک را بررسی کنیم.
-
-## عجله دارید؟
-
-اگر به هیچ یک از این اصطلاحات اهمیت نمی دهید و فقط نیاز به افزودن امنیت با تأیید هویت بر اساس نام کاربری و رمز عبور دارید، *همین الان* به فصل های بعدی بروید.
-
-## پروتکل استاندارد OAuth2
-
-پروتکل استاندارد OAuth2 یک مشخصه است که چندین روش برای مدیریت تأیید هویت و اعتبار سنجی تعریف می کند.
-
-این مشخصه بسیار گسترده است و چندین حالت استفاده پیچیده را پوشش می دهد.
-
-در آن روش هایی برای تأیید هویت با استفاده از "برنامه های شخص ثالث" وجود دارد.
-
-این همان چیزی است که تمامی سیستم های با "ورود با فیسبوک، گوگل، توییتر، گیت هاب" در پایین آن را استفاده می کنند.
-
-### پروتکل استاندارد OAuth 1
-
-پروتکل استاندارد OAuth1 نیز وجود داشت که با OAuth2 خیلی متفاوت است و پیچیدگی بیشتری داشت، زیرا شامل مشخصات مستقیم در مورد رمزگذاری ارتباط بود.
-
-در حال حاضر OAuth1 بسیار محبوب یا استفاده شده نیست.
-
-پروتکل استاندارد OAuth2 روش رمزگذاری ارتباط را مشخص نمی کند، بلکه انتظار دارد که برنامه شما با HTTPS سرویس دهی شود.
-
-/// نکته
-
-در بخش در مورد **استقرار** ، شما یاد خواهید گرفت که چگونه با استفاده از Traefik و Let's Encrypt رایگان HTTPS را راه اندازی کنید.
-
-///
-
-## استاندارد OpenID Connect
-
-استاندارد OpenID Connect، مشخصهای دیگر است که بر پایه **OAuth2** ساخته شده است.
-
-این مشخصه، به گسترش OAuth2 میپردازد و برخی مواردی که در OAuth2 نسبتاً تردید برانگیز هستند را مشخص میکند تا سعی شود آن را با سایر سیستمها قابل ارتباط کند.
-
-به عنوان مثال، ورود به سیستم گوگل از OpenID Connect استفاده میکند (که در زیر از OAuth2 استفاده میکند).
-
-اما ورود به سیستم فیسبوک، از OpenID Connect پشتیبانی نمیکند. به جای آن، نسخه خودش از OAuth2 را دارد.
-
-### استاندارد OpenID (نه "OpenID Connect" )
-
-همچنین مشخصه "OpenID" نیز وجود داشت که سعی در حل مسائل مشابه OpenID Connect داشت، اما بر پایه OAuth2 ساخته نشده بود.
-
-بنابراین، یک سیستم جداگانه بود.
-
-اکنون این مشخصه کمتر استفاده میشود و محبوبیت زیادی ندارد.
-
-## استاندارد OpenAPI
-
-استاندارد OpenAPI (قبلاً با نام Swagger شناخته میشد) یک open specification برای ساخت APIs (که در حال حاضر جزئی از بنیاد لینوکس میباشد) است.
-
-فریم ورک **FastAPI** بر اساس **OpenAPI** است.
-
-این خاصیت، امکان دارد تا چندین رابط مستندات تعاملی خودکار(automatic interactive documentation interfaces)، تولید کد و غیره وجود داشته باشد.
-
-مشخصه OpenAPI روشی برای تعریف چندین "schemes" دارد.
-
-با استفاده از آنها، شما میتوانید از همه این ابزارهای مبتنی بر استاندارد استفاده کنید، از جمله این سیستمهای مستندات تعاملی(interactive documentation systems).
-
-استاندارد OpenAPI شیوههای امنیتی زیر را تعریف میکند:
-
-* شیوه `apiKey`: یک کلید اختصاصی برای برنامه که میتواند از موارد زیر استفاده شود:
- * پارامتر جستجو.
- * هدر.
- * کوکی.
-* شیوه `http`: سیستمهای استاندارد احراز هویت HTTP، از جمله:
- * مقدار `bearer`: یک هدر `Authorization` با مقدار `Bearer` به همراه یک توکن. این از OAuth2 به ارث برده شده است.
- * احراز هویت پایه HTTP.
- * ویژگی HTTP Digest و غیره.
-* شیوه `oauth2`: تمام روشهای OAuth2 برای مدیریت امنیت (به نام "flows").
- * چندین از این flows برای ساخت یک ارائهدهنده احراز هویت OAuth 2.0 مناسب هستند (مانند گوگل، فیسبوک، توییتر، گیتهاب و غیره):
- * ویژگی `implicit`
- * ویژگی `clientCredentials`
- * ویژگی `authorizationCode`
- * اما یک "flow" خاص وجود دارد که میتواند به طور کامل برای مدیریت احراز هویت در همان برنامه به کار رود:
- * بررسی `password`: چند فصل بعدی به مثالهای این مورد خواهیم پرداخت.
-* شیوه `openIdConnect`: یک روش برای تعریف نحوه کشف دادههای احراز هویت OAuth2 به صورت خودکار.
- * کشف خودکار این موضوع را که در مشخصه OpenID Connect تعریف شده است، مشخص میکند.
-
-/// نکته
-
-ادغام سایر ارائهدهندگان احراز هویت/اجازهدهی مانند گوگل، فیسبوک، توییتر، گیتهاب و غیره نیز امکانپذیر و نسبتاً آسان است.
-
-مشکل پیچیدهترین مسئله، ساخت یک ارائهدهنده احراز هویت/اجازهدهی مانند آنها است، اما **FastAPI** ابزارهای لازم برای انجام این کار را با سهولت به شما میدهد و همه کارهای سنگین را برای شما انجام میدهد.
-
-///
-
-## ابزارهای **FastAPI**
-
-فریم ورک FastAPI ابزارهایی برای هر یک از این شیوههای امنیتی در ماژول`fastapi.security` فراهم میکند که استفاده از این مکانیزمهای امنیتی را سادهتر میکند.
-
-در فصلهای بعدی، شما یاد خواهید گرفت که چگونه با استفاده از این ابزارهای ارائه شده توسط **FastAPI**، امنیت را به API خود اضافه کنید.
-
-همچنین، خواهید دید که چگونه به صورت خودکار در سیستم مستندات تعاملی ادغام میشود.
diff --git a/docs/fa/mkdocs.yml b/docs/fa/mkdocs.yml
deleted file mode 100644
index de18856f44..0000000000
--- a/docs/fa/mkdocs.yml
+++ /dev/null
@@ -1 +0,0 @@
-INHERIT: ../en/mkdocs.yml
diff --git a/docs/fr/llm-prompt.md b/docs/fr/llm-prompt.md
new file mode 100644
index 0000000000..5ff18c4e8b
--- /dev/null
+++ b/docs/fr/llm-prompt.md
@@ -0,0 +1,128 @@
+### Target language
+
+Translate to French (français).
+
+Language code: fr.
+
+### Grammar to use when talking to the reader
+
+Use the formal grammar (use «vous» instead of «tu»).
+
+### Quotes
+
+1) Convert neutral double quotes («"») and English double typographic quotes («“» and «”») to French guillemets (««» and «»»).
+
+2) In the French docs, guillemets are written without extra spaces: use «texte», not « texte ».
+
+3) Do not convert quotes inside code blocks, inline code, paths, URLs, or anything wrapped in backticks.
+
+Examples:
+
+ Source (English):
+
+ «««
+ "Hello world"
+ “Hello Universe”
+ "He said: 'Hello'"
+ "The module is `__main__`"
+ »»»
+
+ Result (French):
+
+ «««
+ «Hello world»
+ «Hello Universe»
+ «He said: 'Hello'»
+ «The module is `__main__`»
+ »»»
+
+### Ellipsis
+
+1) Make sure there is a space between an ellipsis and a word following or preceding the ellipsis.
+
+Examples:
+
+ Source (English):
+
+ «««
+ ...as we intended.
+ ...this would work:
+ ...etc.
+ others...
+ More to come...
+ »»»
+
+ Result (French):
+
+ «««
+ ... comme prévu.
+ ... cela fonctionnerait :
+ ... etc.
+ D'autres ...
+ La suite ...
+ »»»
+
+2) This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there.
+
+### Headings
+
+1) Prefer translating headings using the infinitive form (as is common in the existing French docs): «Créer…», «Utiliser…», «Ajouter…».
+
+2) For headings that are instructions written in imperative in English (e.g. “Go check …”), keep them in imperative in French, using the formal grammar (e.g. «Allez voir …»).
+
+3) Keep heading punctuation as in the source. In particular, keep occurrences of literal « - » (space-hyphen-space) as « - » (the existing French docs use a hyphen here).
+
+### French instructions about technical terms
+
+Do not try to translate everything. In particular, keep common programming terms when that is the established usage in the French docs (e.g. «framework», «endpoint», «plug-in», «payload»). Use French where the existing docs already consistently use French (e.g. «requête», «réponse»).
+
+Keep class names, function names, modules, file names, and CLI commands unchanged.
+
+### List of English terms and their preferred French translations
+
+Below is a list of English terms and their preferred French translations, separated by a colon («:»). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them.
+
+* «/// note | Technical Details»: «/// note | Détails techniques»
+* «/// note»: «/// note | Remarque»
+* «/// tip»: «/// tip | Astuce»
+* «/// warning»: «/// warning | Attention»
+* «/// check»: «/// check | vérifier»
+* «/// info»: «/// info»
+
+* «the docs»: «les documents»
+* «the documentation»: «la documentation»
+
+* «framework»: «framework» (do not translate to «cadre»)
+* «performance»: «performance»
+
+* «type hints»: «annotations de type»
+* «type annotations»: «annotations de type»
+
+* «autocomplete»: «autocomplétion»
+* «autocompletion»: «autocomplétion»
+
+* «the request» (what the client sends to the server): «la requête»
+* «the response» (what the server sends back to the client): «la réponse»
+
+* «the request body»: «le corps de la requête»
+* «the response body»: «le corps de la réponse»
+
+* «path operation»: «opération de chemin»
+* «path operations» (plural): «opérations de chemin»
+* «path operation function»: «fonction de chemin»
+* «path operation decorator»: «décorateur d'opération de chemin»
+
+* «path parameter»: «paramètre de chemin»
+* «query parameter»: «paramètre de requête»
+
+* «the `Request`»: «`Request`» (keep as code identifier)
+* «the `Response`»: «`Response`» (keep as code identifier)
+
+* «deployment»: «déploiement»
+* «to upgrade»: «mettre à niveau»
+
+* «deprecated»: «déprécié»
+* «to deprecate»: «déprécier»
+
+* «cheat sheet»: «aide-mémoire»
+* «plug-in»: «plug-in»
diff --git a/docs/ja/llm-prompt.md b/docs/ja/llm-prompt.md
new file mode 100644
index 0000000000..18909cd595
--- /dev/null
+++ b/docs/ja/llm-prompt.md
@@ -0,0 +1,47 @@
+### Target language
+
+Translate to Japanese (日本語).
+
+Language code: ja.
+
+### Grammar and tone
+
+- Use polite, instructional Japanese (です/ます調).
+- Keep the tone concise and technical (match existing Japanese FastAPI docs).
+
+### Headings
+
+- Follow the existing Japanese style: short, descriptive headings (often noun phrases), e.g. 「チェック」.
+- Do not add a trailing period at the end of headings.
+
+### Quotes
+
+- Prefer Japanese corner brackets 「」 in normal prose when quoting a term.
+- Do not change quotes inside inline code, code blocks, URLs, or file paths.
+
+### Ellipsis
+
+- Keep ellipsis style consistent with existing Japanese docs (commonly `...`).
+- Never change `...` in code, URLs, or CLI examples.
+
+### Preferred translations / glossary
+
+Use the following preferred translations when they apply in documentation prose:
+
+- request (HTTP): リクエスト
+- response (HTTP): レスポンス
+- path operation: パスオペレーション
+- path operation function: パスオペレーション関数
+
+### `///` admonitions
+
+1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.).
+2) If a title is present, prefer these canonical titles:
+
+- `/// note | 備考`
+- `/// note | 技術詳細`
+- `/// tip | 豆知識`
+- `/// warning | 注意`
+- `/// info | 情報`
+- `/// check | 確認`
+- `/// danger | 警告`
diff --git a/docs/ko/docs/about/index.md b/docs/ko/docs/about/index.md
index ee7804d323..dc2c728740 100644
--- a/docs/ko/docs/about/index.md
+++ b/docs/ko/docs/about/index.md
@@ -1,3 +1,3 @@
-# 소개
+# 소개 { #about }
-FastAPI에 대한 디자인, 영감 등에 대해 🤓
+FastAPI, 그 디자인, 영감 등에 대해 🤓
diff --git a/docs/ko/docs/advanced/additional-status-codes.md b/docs/ko/docs/advanced/additional-status-codes.md
index da06cb778a..64a7eabd5e 100644
--- a/docs/ko/docs/advanced/additional-status-codes.md
+++ b/docs/ko/docs/advanced/additional-status-codes.md
@@ -1,16 +1,16 @@
-# 추가 상태 코드
+# 추가 상태 코드 { #additional-status-codes }
기본적으로 **FastAPI**는 응답을 `JSONResponse`를 사용하여 반환하며, *경로 작업(path operation)*에서 반환한 내용을 해당 `JSONResponse` 안에 넣어 반환합니다.
기본 상태 코드 또는 *경로 작업*에서 설정한 상태 코드를 사용합니다.
-## 추가 상태 코드
+## 추가 상태 코드 { #additional-status-codes_1 }
기본 상태 코드와 별도로 추가 상태 코드를 반환하려면 `JSONResponse`와 같이 `Response`를 직접 반환하고 추가 상태 코드를 직접 설정할 수 있습니다.
예를 들어 항목을 업데이트할 수 있는 *경로 작업*이 있고 성공 시 200 “OK”의 HTTP 상태 코드를 반환한다고 가정해 보겠습니다.
-하지만 새로운 항목을 허용하기를 원할 것입니다. 항목이 이전에 존재하지 않았다면 이를 생성하고 HTTP 상태 코드 201 "Created"를 반환합니다.
+하지만 새로운 항목을 허용하기를 원할 것입니다. 그리고 항목이 이전에 존재하지 않았다면 이를 생성하고 HTTP 상태 코드 201 "Created"를 반환합니다.
이를 위해서는 `JSONResponse`를 가져와서 원하는 `status_code`를 설정하여 콘텐츠를 직접 반환합니다:
@@ -26,7 +26,7 @@
///
-/// note | 기술적 세부 정보
+/// note | 기술 세부사항
`from starlette.responses import JSONResponse`를 사용할 수도 있습니다.
@@ -34,7 +34,7 @@
///
-## OpenAPI 및 API 문서
+## OpenAPI 및 API 문서 { #openapi-and-api-docs }
추가 상태 코드와 응답을 직접 반환하는 경우, FastAPI는 반환할 내용을 미리 알 수 있는 방법이 없기 때문에 OpenAPI 스키마(API 문서)에 포함되지 않습니다.
diff --git a/docs/ko/docs/advanced/advanced-dependencies.md b/docs/ko/docs/advanced/advanced-dependencies.md
index 7fa043fa3a..04e557d15b 100644
--- a/docs/ko/docs/advanced/advanced-dependencies.md
+++ b/docs/ko/docs/advanced/advanced-dependencies.md
@@ -1,6 +1,6 @@
-# 고급 의존성
+# 고급 의존성 { #advanced-dependencies }
-## 매개변수화된 의존성
+## 매개변수화된 의존성 { #parameterized-dependencies }
지금까지 본 모든 의존성은 고정된 함수 또는 클래스입니다.
@@ -10,7 +10,7 @@
이때 해당 고정된 내용을 매개변수화할 수 있길 바랍니다.
-## "호출 가능한" 인스턴스
+## "호출 가능한" 인스턴스 { #a-callable-instance }
Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법이 있습니다.
@@ -21,9 +21,9 @@ Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *}
이 경우, **FastAPI**는 추가 매개변수와 하위 의존성을 확인하기 위해 `__call__`을 사용하게 되며,
-나중에 *경로 연산 함수*에서 매개변수에 값을 전달할 때 이를 호출하게 됩니다.
+나중에 *경로 처리 함수*에서 매개변수에 값을 전달할 때 이를 호출하게 됩니다.
-## 인스턴스 매개변수화하기
+## 인스턴스 매개변수화하기 { #parameterize-the-instance }
이제 `__init__`을 사용하여 의존성을 "매개변수화"할 수 있는 인스턴스의 매개변수를 선언할 수 있습니다:
@@ -31,7 +31,7 @@ Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법
이 경우, **FastAPI**는 `__init__`에 전혀 관여하지 않으며, 우리는 이 메서드를 코드에서 직접 사용하게 됩니다.
-## 인스턴스 생성하기
+## 인스턴스 생성하기 { #create-an-instance }
다음과 같이 이 클래스의 인스턴스를 생성할 수 있습니다:
@@ -39,10 +39,9 @@ Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법
이렇게 하면 `checker.fixed_content` 속성에 `"bar"`라는 값을 담아 의존성을 "매개변수화"할 수 있습니다.
-## 인스턴스를 의존성으로 사용하기
+## 인스턴스를 의존성으로 사용하기 { #use-the-instance-as-a-dependency }
-그런 다음, `Depends(FixedContentQueryChecker)` 대신 `Depends(checker)`에서 이 `checker` 인스턴스를 사용할 수 있으며,
-클래스 자체가 아닌 인스턴스 `checker`가 의존성이 됩니다.
+그런 다음, 클래스 자체가 아닌 인스턴스 `checker`가 의존성이 되므로, `Depends(FixedContentQueryChecker)` 대신 `Depends(checker)`에서 이 `checker` 인스턴스를 사용할 수 있습니다.
의존성을 해결할 때 **FastAPI**는 이 `checker`를 다음과 같이 호출합니다:
@@ -50,18 +49,116 @@ Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법
checker(q="somequery")
```
-...그리고 이때 반환되는 값을 *경로 연산 함수*의 `fixed_content_included` 매개변수로 전달합니다:
+...그리고 이때 반환되는 값을 *경로 처리 함수*의 의존성 값으로, `fixed_content_included` 매개변수에 전달합니다:
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *}
-/// tip | 참고
+/// tip | 팁
이 모든 과정이 복잡하게 느껴질 수 있습니다. 그리고 지금은 이 방법이 얼마나 유용한지 명확하지 않을 수도 있습니다.
이 예시는 의도적으로 간단하게 만들었지만, 전체 구조가 어떻게 작동하는지 보여줍니다.
-보안 관련 장에서는 이와 같은 방식으로 구현된 편의 함수들이 있습니다.
+보안 관련 장에서는 이와 같은 방식으로 구현된 유틸리티 함수들이 있습니다.
-이 모든 과정을 이해했다면, 이러한 보안 도구들이 내부적으로 어떻게 작동하는지 이미 파악한 것입니다.
+이 모든 과정을 이해했다면, 이러한 보안용 유틸리티 도구들이 내부적으로 어떻게 작동하는지 이미 파악한 것입니다.
///
+
+## `yield`, `HTTPException`, `except`, 백그라운드 태스크가 있는 의존성 { #dependencies-with-yield-httpexception-except-and-background-tasks }
+
+/// warning | 경고
+
+대부분의 경우 이러한 기술 세부사항이 필요하지 않을 것입니다.
+
+이 세부사항은 주로 0.121.0 이전의 FastAPI 애플리케이션이 있고 `yield`가 있는 의존성에서 문제가 발생하는 경우에 유용합니다.
+
+///
+
+`yield`가 있는 의존성은 여러 사용 사례를 수용하고 일부 문제를 해결하기 위해 시간이 지나며 발전해 왔습니다. 다음은 변경된 내용의 요약입니다.
+
+### `yield`와 `scope`가 있는 의존성 { #dependencies-with-yield-and-scope }
+
+0.121.0 버전에서 FastAPI는 `yield`가 있는 의존성에 대해 `Depends(scope="function")` 지원을 추가했습니다.
+
+`Depends(scope="function")`를 사용하면, `yield` 이후의 종료 코드는 *경로 처리 함수*가 끝난 직후(클라이언트에 응답이 반환되기 전)에 실행됩니다.
+
+그리고 `Depends(scope="request")`(기본값)를 사용하면, `yield` 이후의 종료 코드는 응답이 전송된 후에 실행됩니다.
+
+자세한 내용은 [Dependencies with `yield` - Early exit and `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope) 문서를 참고하세요.
+
+### `yield`가 있는 의존성과 `StreamingResponse`, 기술 세부사항 { #dependencies-with-yield-and-streamingresponse-technical-details }
+
+FastAPI 0.118.0 이전에는 `yield`가 있는 의존성을 사용하면, *경로 처리 함수*가 반환된 뒤 응답을 보내기 직전에 `yield` 이후의 종료 코드가 실행되었습니다.
+
+의도는 응답이 네트워크를 통해 전달되기를 기다리면서 필요한 것보다 더 오래 리소스를 점유하지 않도록 하는 것이었습니다.
+
+이 변경은 `StreamingResponse`를 반환하는 경우에도 `yield`가 있는 의존성의 종료 코드가 이미 실행된다는 의미이기도 했습니다.
+
+예를 들어, `yield`가 있는 의존성에 데이터베이스 세션이 있다면, `StreamingResponse`는 데이터를 스트리밍하는 동안 해당 세션을 사용할 수 없게 됩니다. `yield` 이후의 종료 코드에서 세션이 이미 닫혔기 때문입니다.
+
+이 동작은 0.118.0에서 되돌려져, `yield` 이후의 종료 코드가 응답이 전송된 뒤 실행되도록 변경되었습니다.
+
+/// info | 정보
+
+아래에서 보시겠지만, 이는 0.106.0 버전 이전의 동작과 매우 비슷하지만, 여러 개선 사항과 코너 케이스에 대한 버그 수정이 포함되어 있습니다.
+
+///
+
+#### 종료 코드를 조기에 실행하는 사용 사례 { #use-cases-with-early-exit-code }
+
+특정 조건의 일부 사용 사례에서는 응답을 보내기 전에 `yield`가 있는 의존성의 종료 코드를 실행하던 예전 동작이 도움이 될 수 있습니다.
+
+예를 들어, `yield`가 있는 의존성에서 데이터베이스 세션을 사용해 사용자를 검증만 하고, *경로 처리 함수*에서는 그 데이터베이스 세션을 다시는 사용하지 않으며(의존성에서만 사용), **그리고** 응답을 전송하는 데 오랜 시간이 걸리는 경우를 생각해 봅시다. 예를 들어 데이터를 천천히 보내는 `StreamingResponse`인데, 어떤 이유로든 데이터베이스를 사용하지는 않는 경우입니다.
+
+이 경우 데이터베이스 세션은 응답 전송이 끝날 때까지 유지되지만, 사용하지 않는다면 굳이 유지할 필요가 없습니다.
+
+다음과 같이 보일 수 있습니다:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py *}
+
+다음에서 `Session`을 자동으로 닫는 종료 코드는:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+...응답이 느린 데이터 전송을 마친 뒤에 실행됩니다:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
+
+하지만 `generate_stream()`는 데이터베이스 세션을 사용하지 않으므로, 응답을 전송하는 동안 세션을 열린 채로 유지할 필요는 없습니다.
+
+SQLModel(또는 SQLAlchemy)을 사용하면서 이런 특정 사용 사례가 있다면, 더 이상 필요하지 않을 때 세션을 명시적으로 닫을 수 있습니다:
+
+{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *}
+
+그러면 세션이 데이터베이스 연결을 해제하여, 다른 요청들이 이를 사용할 수 있게 됩니다.
+
+`yield`가 있는 의존성에서 조기 종료가 필요한 다른 사용 사례가 있다면, 여러분의 구체적인 사용 사례와 `yield`가 있는 의존성에 대한 조기 종료가 어떤 점에서 이득이 되는지를 포함해 GitHub Discussion Question을 생성해 주세요.
+
+`yield`가 있는 의존성에서 조기 종료에 대한 설득력 있는 사용 사례가 있다면, 조기 종료를 선택적으로 활성화할 수 있는 새로운 방법을 추가하는 것을 고려하겠습니다.
+
+### `yield`가 있는 의존성과 `except`, 기술 세부사항 { #dependencies-with-yield-and-except-technical-details }
+
+FastAPI 0.110.0 이전에는 `yield`가 있는 의존성을 사용한 다음 그 의존성에서 `except`로 예외를 잡고, 예외를 다시 발생시키지 않으면, 예외가 자동으로 어떤 예외 핸들러 또는 내부 서버 오류 핸들러로 raise/forward 되었습니다.
+
+이는 핸들러 없이 전달된 예외(내부 서버 오류)로 인해 처리되지 않은 메모리 사용이 발생하는 문제를 수정하고, 일반적인 Python 코드의 동작과 일관되게 하기 위해 0.110.0 버전에서 변경되었습니다.
+
+### 백그라운드 태스크와 `yield`가 있는 의존성, 기술 세부사항 { #background-tasks-and-dependencies-with-yield-technical-details }
+
+FastAPI 0.106.0 이전에는 `yield` 이후에 예외를 발생시키는 것이 불가능했습니다. `yield`가 있는 의존성의 종료 코드는 응답이 전송된 *후에* 실행되었기 때문에, [Exception Handlers](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}가 이미 실행된 뒤였습니다.
+
+이는 주로 백그라운드 태스크 안에서 의존성이 "yield"한 동일한 객체들을 사용할 수 있게 하기 위한 설계였습니다. 백그라운드 태스크가 끝난 뒤에 종료 코드가 실행되었기 때문입니다.
+
+이는 응답이 네트워크를 통해 전달되기를 기다리는 동안 리소스를 점유하지 않기 위한 의도로 FastAPI 0.106.0에서 변경되었습니다.
+
+/// tip | 팁
+
+추가로, 백그라운드 태스크는 보통 별도의 리소스(예: 자체 데이터베이스 연결)를 가지고 따로 처리되어야 하는 독립적인 로직 집합입니다.
+
+따라서 이 방식이 코드를 더 깔끔하게 만들어줄 가능성이 큽니다.
+
+///
+
+이 동작에 의존하던 경우라면, 이제는 백그라운드 태스크를 위한 리소스를 백그라운드 태스크 내부에서 생성하고, 내부적으로는 `yield`가 있는 의존성의 리소스에 의존하지 않는 데이터만 사용해야 합니다.
+
+예를 들어, 동일한 데이터베이스 세션을 사용하는 대신, 백그라운드 태스크 내부에서 새 데이터베이스 세션을 생성하고, 이 새 세션을 사용해 데이터베이스에서 객체를 가져오면 됩니다. 그리고 데이터베이스에서 가져온 객체를 백그라운드 태스크 함수의 매개변수로 전달하는 대신, 해당 객체의 ID를 전달한 다음 백그라운드 태스크 함수 내부에서 객체를 다시 가져오면 됩니다.
diff --git a/docs/ko/docs/advanced/async-tests.md b/docs/ko/docs/advanced/async-tests.md
index 37dfe29792..6c85936812 100644
--- a/docs/ko/docs/advanced/async-tests.md
+++ b/docs/ko/docs/advanced/async-tests.md
@@ -1,31 +1,26 @@
-# 비동기 테스트 코드 작성
+# 비동기 테스트 { #async-tests }
-이전 장에서 `TestClient` 를 이용해 **FastAPI** 어플리케이션 테스트를 작성하는 법을 배우셨을텐데요.
-지금까지는 `async` 키워드 사용없이 동기 함수의 테스트 코드를 작성하는 법만 익혔습니다.
+제공된 `TestClient`를 사용하여 **FastAPI** 애플리케이션을 테스트하는 방법을 이미 살펴보았습니다. 지금까지는 `async` 함수를 사용하지 않고, 동기 테스트를 작성하는 방법만 보았습니다.
-하지만 비동기 함수를 사용하여 테스트 코드를 작성하는 것은 매우 유용할 수 있습니다.
-예를 들면 데이터베이스에 비동기로 쿼리하는 경우를 생각해봅시다.
-FastAPI 애플리케이션에 요청을 보내고, 비동기 데이터베이스 라이브러리를 사용하여 백엔드가 데이터베이스에 올바르게 데이터를 기록했는지 확인하고 싶을 때가 있을 겁니다.
+테스트에서 비동기 함수를 사용할 수 있으면 유용할 수 있습니다. 예를 들어 데이터베이스를 비동기로 쿼리하는 경우를 생각해 보세요. FastAPI 애플리케이션에 요청을 보낸 다음, async 데이터베이스 라이브러리를 사용하면서 백엔드가 데이터베이스에 올바른 데이터를 성공적으로 기록했는지 검증하고 싶을 수 있습니다.
-이런 경우의 테스트 코드를 어떻게 비동기로 작성하는지 알아봅시다.
+어떻게 동작하게 만들 수 있는지 살펴보겠습니다.
-## pytest.mark.anyio
+## pytest.mark.anyio { #pytest-mark-anyio }
-앞에서 작성한 테스트 함수에서 비동기 함수를 호출하고 싶다면, 테스트 코드도 비동기 함수여야합니다.
-AnyIO는 특정 테스트 함수를 비동기 함수로 호출 할 수 있는 깔끔한 플러그인을 제공합니다.
+테스트에서 비동기 함수를 호출하려면, 테스트 함수도 비동기여야 합니다. AnyIO는 이를 위한 깔끔한 플러그인을 제공하며, 일부 테스트 함수를 비동기로 호출하도록 지정할 수 있습니다.
+## HTTPX { #httpx }
-## HTTPX
+**FastAPI** 애플리케이션이 `async def` 대신 일반 `def` 함수를 사용하더라도, 내부적으로는 여전히 `async` 애플리케이션입니다.
-**FastAPI** 애플리케이션이 `async def` 대신 `def` 키워드로 선언된 함수를 사용하더라도, 내부적으로는 여전히 `비동기` 애플리케이션입니다.
+`TestClient`는 표준 pytest를 사용하여, 일반 `def` 테스트 함수 안에서 비동기 FastAPI 애플리케이션을 호출하도록 내부에서 마법 같은 처리를 합니다. 하지만 비동기 함수 안에서 이를 사용하면 그 마법은 더 이상 동작하지 않습니다. 테스트를 비동기로 실행하면, 테스트 함수 안에서 `TestClient`를 더 이상 사용할 수 없습니다.
-`TestClient`는 pytest 표준을 사용하여 비동기 FastAPI 애플리케이션을 일반적인 `def` 테스트 함수 내에서 호출할 수 있도록 내부에서 마술을 부립니다. 하지만 이 마술은 비동기 함수 내부에서 사용할 때는 더 이상 작동하지 않습니다. 테스트를 비동기로 실행하면, 더 이상 테스트 함수 내부에서 `TestClient`를 사용할 수 없습니다.
+`TestClient`는 HTTPX를 기반으로 하며, 다행히 HTTPX를 직접 사용해 API를 테스트할 수 있습니다.
-`TestClient`는 HTTPX를 기반으로 하고 있으며, 다행히 이를 직접 사용하여 API를 테스트할 수 있습니다.
+## 예시 { #example }
-## 예시
-
-간단한 예시를 위해 [더 큰 어플리케이션 만들기](../ko/tutorial/bigger-applications.md){.internal-link target=_blank} 와 [테스트](../ko/tutorial/testing.md){.internal-link target=_blank}:에서 다룬 파일 구조와 비슷한 형태를 확인해봅시다:
+간단한 예시로, [더 큰 애플리케이션](../tutorial/bigger-applications.md){.internal-link target=_blank}과 [테스트](../tutorial/testing.md){.internal-link target=_blank}에서 설명한 것과 비슷한 파일 구조를 살펴보겠습니다:
```
.
@@ -35,17 +30,17 @@ AnyIO는 특정 테스트 함수를 비동기 함수로 호출 할 수 있는
│ └── test_main.py
```
- `main.py`는 아래와 같아야 합니다:
+`main.py` 파일은 다음과 같습니다:
-{* ../../docs_src/async_tests/main.py *}
+{* ../../docs_src/async_tests/app_a_py39/main.py *}
-`test_main.py` 파일은 `main.py`에 대한 테스트가 있을 텐데, 다음과 같을 수 있습니다:
+`test_main.py` 파일에는 `main.py`에 대한 테스트가 있으며, 이제 다음과 같이 보일 수 있습니다:
-{* ../../docs_src/async_tests/test_main.py *}
+{* ../../docs_src/async_tests/app_a_py39/test_main.py *}
-## 실행하기
+## 실행하기 { #run-it }
-아래의 명령어로 테스트 코드를 실행합니다:
+다음과 같이 평소처럼 테스트를 실행할 수 있습니다:
-## 사용 가능한 응답들
+## 사용 가능한 응답들 { #available-responses }
다음은 사용할 수 있는 몇가지 응답들 입니다.
@@ -121,7 +121,7 @@
///
-### `Response`
+### `Response` { #response }
기본 `Response` 클래스는 다른 모든 응답 클래스의 부모 클래스 입니다.
@@ -134,27 +134,27 @@
* `headers` - 문자열로 이루어진 `dict`.
* `media_type` - 미디어 타입을 나타내는 `str` 예: `"text/html"`.
-FastAPI (실제로는 Starlette)가 자동으로 `Content-Length` 헤더를 포함시킵니다. 또한 `media_type`에 기반하여 `Content-Type` 헤더를 포함하며, 텍스트 타입의 경우 문자 집합을 추가 합니다.
+FastAPI (실제로는 Starlette)가 자동으로 Content-Length 헤더를 포함시킵니다. 또한 `media_type`에 기반하여 Content-Type 헤더를 포함하며, 텍스트 타입의 경우 문자 집합을 추가 합니다.
-{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
+{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
-### `HTMLResponse`
+### `HTMLResponse` { #htmlresponse }
텍스트 또는 바이트를 받아 HTML 응답을 반환합니다. 위에서 설명한 내용과 같습니다.
-### `PlainTextResponse`
+### `PlainTextResponse` { #plaintextresponse }
텍스트 또는 바이트를 받아 일반 텍스트 응답을 반환합니다.
-{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *}
-### `JSONResponse`
+### `JSONResponse` { #jsonresponse }
데이터를 받아 `application/json`으로 인코딩된 응답을 반환합니다.
이는 위에서 설명했듯이 **FastAPI**에서 기본적으로 사용되는 응답 형식입니다.
-### `ORJSONResponse`
+### `ORJSONResponse` { #orjsonresponse }
`orjson`을 사용하여 빠른 JSON 응답을 제공하는 대안입니다. 위에서 설명한 내용과 같습니다.
@@ -164,13 +164,13 @@ FastAPI (실제로는 Starlette)가 자동으로 `Content-Length` 헤더를 포
///
-### `UJSONResponse`
+### `UJSONResponse` { #ujsonresponse }
`ujson`을 사용한 또 다른 JSON 응답 형식입니다.
/// info | 정보
-이 응답을 사용하려면 `ujson`을 설치해야합니다. 예: 'pip install ujson`.
+이 응답을 사용하려면 `ujson`을 설치해야합니다. 예: `pip install ujson`.
///
@@ -180,7 +180,7 @@ FastAPI (실제로는 Starlette)가 자동으로 `Content-Length` 헤더를 포
///
-{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
+{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *}
/// tip | 팁
@@ -188,22 +188,22 @@ FastAPI (실제로는 Starlette)가 자동으로 `Content-Length` 헤더를 포
///
-### `RedirectResponse`
+### `RedirectResponse` { #redirectresponse }
HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 307(임시 리디렉션)으로 설정됩니다.
`RedirectResponse`를 직접 반환할 수 있습니다.
-{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
+{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *}
---
또는 `response_class` 매개변수에서 사용할 수도 있습니다:
-{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *}
-이 경우, *경로 작업* 함수에서 URL을 직접 반환할 수 있습니다.
+이 경우, *경로 처리* 함수에서 URL을 직접 반환할 수 있습니다.
이 경우, 사용되는 `status_code`는 `RedirectResponse`의 기본값인 `307` 입니다.
@@ -211,23 +211,23 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30
`status_code` 매개변수를 `response_class` 매개변수와 함께 사용할 수도 있습니다:
-{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *}
-### `StreamingResponse`
+### `StreamingResponse` { #streamingresponse }
비동기 제너레이터 또는 일반 제너레이터/이터레이터를 받아 응답 본문을 스트리밍 합니다.
-{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
+{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *}
-#### 파일과 같은 객체를 사용한 `StreamingResponse`
+#### 파일과 같은 객체를 사용한 `StreamingResponse` { #using-streamingresponse-with-file-like-objects }
-파일과 같은 객체(예: `open()`으로 반환된 객체)가 있는 경우, 해당 파일과 같은 객체를 반복(iterate)하는 제너레이터 함수를 만들 수 있습니다.
+file-like 객체(예: `open()`으로 반환된 객체)가 있는 경우, 해당 file-like 객체를 반복(iterate)하는 제너레이터 함수를 만들 수 있습니다.
이 방식으로, 파일 전체를 메모리에 먼저 읽어들일 필요 없이, 제너레이터 함수를 `StreamingResponse`에 전달하여 반환할 수 있습니다.
이 방식은 클라우드 스토리지, 비디오 처리 등의 다양한 라이브러리와 함께 사용할 수 있습니다.
-{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *}
+{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *}
1. 이것이 제너레이터 함수입니다. `yield` 문을 포함하고 있으므로 "제너레이터 함수"입니다.
2. `with` 블록을 사용함으로써, 제너레이터 함수가 완료된 후 파일과 같은 객체가 닫히도록 합니다. 즉, 응답 전송이 끝난 후 닫힙니다.
@@ -235,15 +235,15 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30
이렇게 하면 "생성(generating)" 작업을 내부적으로 다른 무언가에 위임하는 제너레이터 함수가 됩니다.
- 이 방식을 사용하면 `with` 블록 안에서 파일을 열 수 있어, 작업이 완료된 후 파일과 같은 객체가 닫히는 것을 보장할 수 있습니다.
+ 이 방식을 사용하면 `with` 블록 안에서 파일을 열 수 있어, 작업이 완료된 후 파일과 같은 객체가 닫히는 것을 보장할 수 있습니다.
/// tip | 팁
-여기서 표준 `open()`을 사용하고 있기 때문에 `async`와 `await`를 지원하지 않습니다. 따라서 경로 작업은 일반 `def`로 선언합니다.
+여기서 표준 `open()`을 사용하고 있기 때문에 `async`와 `await`를 지원하지 않습니다. 따라서 경로 처리는 일반 `def`로 선언합니다.
///
-### `FileResponse`
+### `FileResponse` { #fileresponse }
파일을 비동기로 스트리밍하여 응답합니다.
@@ -256,25 +256,25 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30
파일 응답에는 적절한 `Content-Length`, `Last-Modified`, 및 `ETag` 헤더가 포함됩니다.
-{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
+{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *}
또한 `response_class` 매개변수를 사용할 수도 있습니다:
-{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *}
+{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *}
-이 경우, 경로 작업 함수에서 파일 경로를 직접 반환할 수 있습니다.
+이 경우, 경로 처리 함수에서 파일 경로를 직접 반환할 수 있습니다.
-## 사용자 정의 응답 클래스
+## 사용자 정의 응답 클래스 { #custom-response-class }
`Response`를 상속받아 사용자 정의 응답 클래스를 생성하고 사용할 수 있습니다.
-예를 들어, 포함된 `ORJSONResponse` 클래스에서 사용되지 않는 설정으로 orjson을 사용하고 싶다고 가정해봅시다.
+예를 들어, 포함된 `ORJSONResponse` 클래스에서 사용되지 않는 설정으로 `orjson`을 사용하고 싶다고 가정해봅시다.
만약 들여쓰기 및 포맷된 JSON을 반환하고 싶다면, `orjson.OPT_INDENT_2` 옵션을 사용할 수 있습니다.
`CustomORJSONResponse`를 생성할 수 있습니다. 여기서 핵심은 `Response.render(content)` 메서드를 생성하여 내용을 `bytes`로 반환하는 것입니다:
-{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *}
+{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *}
이제 다음 대신:
@@ -282,7 +282,7 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30
{"message": "Hello World"}
```
-이 응답은 이렇게 반환됩니다:
+...이 응답은 이렇게 반환됩니다:
```json
{
@@ -292,22 +292,22 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30
물론 JSON 포맷팅보다 더 유용하게 활용할 방법을 찾을 수 있을 것입니다. 😉
-## 기본 응답 클래스
+## 기본 응답 클래스 { #default-response-class }
**FastAPI** 클래스 객체 또는 `APIRouter`를 생성할 때 기본적으로 사용할 응답 클래스를 지정할 수 있습니다.
이를 정의하는 매개변수는 `default_response_class`입니다.
-아래 예제에서 **FastAPI**는 모든 경로 작업에서 기본적으로 `JSONResponse` 대신 `ORJSONResponse`를 사용합니다.
+아래 예제에서 **FastAPI**는 모든 *경로 처리*에서 기본적으로 `JSONResponse` 대신 `ORJSONResponse`를 사용합니다.
-{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *}
+{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *}
/// tip | 팁
-여전히 이전처럼 *경로 작업*에서 `response_class`를 재정의할 수 있습니다.
+여전히 이전처럼 *경로 처리*에서 `response_class`를 재정의할 수 있습니다.
///
-## 추가 문서화
+## 추가 문서화 { #additional-documentation }
OpenAPI에서 `responses`를 사용하여 미디어 타입 및 기타 세부 정보를 선언할 수도 있습니다: [OpenAPI에서 추가 응답](additional-responses.md){.internal-link target=_blank}.
diff --git a/docs/ko/docs/advanced/events.md b/docs/ko/docs/advanced/events.md
index 4318ada54e..35223eaf39 100644
--- a/docs/ko/docs/advanced/events.md
+++ b/docs/ko/docs/advanced/events.md
@@ -1,67 +1,66 @@
-# Lifespan 이벤트
+# Lifespan 이벤트 { #lifespan-events }
-애플리케이션 **시작 전**에 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이는 이 코드가 **한 번**만 실행되며, **애플리케이션이 요청을 받기 시작하기 전**에 실행된다는 의미입니다.
+애플리케이션이 **시작**하기 전에 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이는 이 코드가 **한 번**만 실행되며, 애플리케이션이 **요청을 받기 시작하기 전**에 실행된다는 의미입니다.
-마찬가지로, 애플리케이션이 **종료될 때** 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이 경우, 이 코드는 **한 번**만 실행되며, **여러 요청을 처리한 후**에 실행됩니다.
+마찬가지로, 애플리케이션이 **종료**될 때 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이 경우, 이 코드는 **한 번**만 실행되며, **여러 요청을 처리한 후**에 실행됩니다.
-이 코드가 애플리케이션이 **요청을 받기 시작하기 전에** 실행되고, 요청 처리가 끝난 후 **종료 직전에** 실행되기 때문에 전체 애플리케이션의 **수명(Lifespan)**을 다룹니다. (잠시 후 "수명"이라는 단어가 중요해집니다 😉)
+이 코드는 애플리케이션이 요청을 받기 **시작**하기 전에 실행되고, 요청 처리를 **끝낸 직후**에 실행되기 때문에 전체 애플리케이션의 **수명(lifespan)**을 다룹니다(잠시 후 "lifespan"이라는 단어가 중요해집니다 😉).
-이 방법은 전체 애플리케이션에서 사용해야 하는 **자원**을 설정하거나 요청 간에 **공유되는** 자원을 설정하고, 또는 그 후에 **정리**하는 데 매우 유용할 수 있습니다. 예를 들어, 데이터베이스 연결 풀 또는 공유되는 머신러닝 모델을 로드하는 경우입니다.
+이는 전체 앱에서 사용해야 하는 **자원**을 설정하고, 요청 간에 **공유되는** 자원을 설정하고, 그리고/또는 이후에 **정리**하는 데 매우 유용할 수 있습니다. 예를 들어, 데이터베이스 연결 풀 또는 공유 머신러닝 모델을 로드하는 경우입니다.
+## 사용 사례 { #use-case }
-## 사용 사례
+먼저 **사용 사례** 예시로 시작한 다음, 이를 어떻게 해결할지 살펴보겠습니다.
-먼저 **사용 사례**를 예로 들어보고, 이를 어떻게 해결할 수 있는지 살펴보겠습니다.
+요청을 처리하는 데 사용하고 싶은 **머신러닝 모델**이 있다고 상상해 봅시다. 🤖
-우리가 요청을 처리하기 위해 사용하고 싶은 **머신러닝 모델**이 있다고 상상해 봅시다. 🤖
+동일한 모델이 요청 간에 공유되므로, 요청마다 모델이 하나씩 있거나 사용자마다 하나씩 있는 등의 방식이 아닙니다.
-이 모델들은 요청 간에 공유되므로, 요청마다 모델이 하나씩 있는 것이 아니라, 여러 요청에서 동일한 모델을 사용합니다.
+모델을 로드하는 데 **상당한 시간이 걸린다고 상상해 봅시다**, 왜냐하면 모델이 **디스크에서 많은 데이터를 읽어야** 하기 때문입니다. 그래서 모든 요청마다 이를 수행하고 싶지는 않습니다.
-모델을 로드하는 데 **상당한 시간이 걸린다고 상상해 봅시다**, 왜냐하면 모델이 **디스크에서 많은 데이터를 읽어야** 하기 때문입니다. 따라서 모든 요청에 대해 모델을 매번 로드하고 싶지 않습니다.
+모듈/파일의 최상위에서 로드할 수도 있지만, 그러면 단순한 자동화된 테스트를 실행하는 경우에도 **모델을 로드**하게 되고, 테스트가 코드의 독립적인 부분을 실행하기 전에 모델이 로드될 때까지 기다려야 하므로 **느려집니다**.
-모듈/파일의 최상위에서 모델을 로드할 수도 있지만, 그러면 **모델을 로드하는데** 시간이 걸리기 때문에, 단순한 자동화된 테스트를 실행할 때도 모델이 로드될 때까지 기다려야 해서 **테스트 속도가 느려집니다**.
+이것이 우리가 해결할 문제입니다. 요청을 처리하기 전에 모델을 로드하되, 코드가 로드되는 동안이 아니라 애플리케이션이 요청을 받기 시작하기 직전에만 로드하겠습니다.
-이 문제를 해결하려고 하는 것입니다. 요청을 처리하기 전에 모델을 로드하되, 애플리케이션이 요청을 받기 시작하기 직전에만 로드하고, 코드가 로드되는 동안은 로드하지 않도록 하겠습니다.
+## Lifespan { #lifespan }
-## Lifespan
+`FastAPI` 앱의 `lifespan` 매개변수와 "컨텍스트 매니저"를 사용하여 *시작*과 *종료* 로직을 정의할 수 있습니다(컨텍스트 매니저가 무엇인지 잠시 후에 보여드리겠습니다).
-`FastAPI` 애플리케이션의 `lifespan` 매개변수와 "컨텍스트 매니저"를 사용하여 *시작*과 *종료* 로직을 정의할 수 있습니다. (컨텍스트 매니저가 무엇인지 잠시 후에 설명드리겠습니다.)
+예제로 시작한 다음 자세히 살펴보겠습니다.
-예제를 통해 시작하고, 그 후에 자세히 살펴보겠습니다.
+`yield`를 사용해 비동기 함수 `lifespan()`을 다음과 같이 생성합니다:
-우리는 `yield`를 사용하여 비동기 함수 `lifespan()`을 다음과 같이 생성합니다:
+{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *}
-{* ../../docs_src/events/tutorial003.py hl[16,19] *}
+여기서는 `yield` 이전에 (가짜) 모델 함수를 머신러닝 모델이 들어 있는 딕셔너리에 넣어 모델을 로드하는 비용이 큰 *시작* 작업을 시뮬레이션합니다. 이 코드는 애플리케이션이 **요청을 받기 시작하기 전**, *시작* 동안에 실행됩니다.
-여기서 우리는 모델을 로드하는 비싼 *시작* 작업을 시뮬레이션하고 있습니다. `yield` 앞에서 (가짜) 모델 함수를 머신러닝 모델이 담긴 딕셔너리에 넣습니다. 이 코드는 **애플리케이션이 요청을 받기 시작하기 전**, *시작* 동안에 실행됩니다.
-
-그리고 `yield` 직후에는 모델을 언로드합니다. 이 코드는 **애플리케이션이 요청 처리 완료 후**, *종료* 직전에 실행됩니다. 예를 들어, 메모리나 GPU와 같은 자원을 해제하는 작업을 할 수 있습니다.
+그리고 `yield` 직후에는 모델을 언로드합니다. 이 코드는 애플리케이션이 **요청 처리를 마친 후**, *종료* 직전에 실행됩니다. 예를 들어 메모리나 GPU 같은 자원을 해제할 수 있습니다.
/// tip | 팁
-`shutdown`은 애플리케이션을 **종료**할 때 발생합니다.
+`shutdown`은 애플리케이션을 **중지**할 때 발생합니다.
-새로운 버전을 시작해야 하거나, 그냥 실행을 멈추고 싶을 수도 있습니다. 🤷
+새 버전을 시작해야 할 수도 있고, 그냥 실행하는 게 지겨워졌을 수도 있습니다. 🤷
///
-### Lifespan 함수
+### Lifespan 함수 { #lifespan-function }
-먼저 주목할 점은, `yield`를 사용하여 비동기 함수(async function)를 정의하고 있다는 것입니다. 이는 `yield`를 사용한 의존성과 매우 유사합니다.
+먼저 주목할 점은 `yield`를 사용하여 비동기 함수를 정의하고 있다는 것입니다. 이는 `yield`를 사용하는 의존성과 매우 유사합니다.
-{* ../../docs_src/events/tutorial003.py hl[14:19] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *}
함수의 첫 번째 부분, 즉 `yield` 이전의 코드는 애플리케이션이 시작되기 **전에** 실행됩니다.
-그리고 `yield` 이후의 부분은 애플리케이션이 완료된 후 **나중에** 실행됩니다.
+그리고 `yield` 이후의 부분은 애플리케이션이 종료된 **후에** 실행됩니다.
-### 비동기 컨텍스트 매니저
+### 비동기 컨텍스트 매니저 { #async-context-manager }
-함수를 확인해보면, `@asynccontextmanager`로 장식되어 있습니다.
+확인해 보면, 함수는 `@asynccontextmanager`로 데코레이션되어 있습니다.
-이것은 함수를 "**비동기 컨텍스트 매니저**"라고 불리는 것으로 변환시킵니다.
+이는 함수를 "**비동기 컨텍스트 매니저**"라고 불리는 것으로 변환합니다.
-{* ../../docs_src/events/tutorial003.py hl[1,13] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *}
파이썬에서 **컨텍스트 매니저**는 `with` 문에서 사용할 수 있는 것입니다. 예를 들어, `open()`은 컨텍스트 매니저로 사용할 수 있습니다:
@@ -69,97 +68,98 @@
with open("file.txt") as file:
file.read()
```
-최근 버전의 파이썬에서는 **비동기 컨텍스트 매니저**도 있습니다. 이를 `async with`와 함께 사용합니다:
+
+최근 버전의 파이썬에는 **비동기 컨텍스트 매니저**도 있습니다. 이를 `async with`와 함께 사용합니다:
```Python
async with lifespan(app):
await do_stuff()
```
-컨텍스트 매니저나 위와 같은 비동기 컨텍스트 매니저를 만들면, `with` 블록에 들어가기 전에 `yield` 이전의 코드가 실행되고, `with` 블록을 벗어난 후에는 `yield` 이후의 코드가 실행됩니다.
+위와 같은 컨텍스트 매니저 또는 비동기 컨텍스트 매니저를 만들면, `with` 블록에 들어가기 전에 `yield` 이전의 코드를 실행하고, `with` 블록을 벗어난 후에는 `yield` 이후의 코드를 실행합니다.
-위의 코드 예제에서는 직접 사용하지 않고, FastAPI에 전달하여 사용하도록 합니다.
+위의 코드 예제에서는 직접 사용하지 않고, FastAPI에 전달하여 FastAPI가 이를 사용하도록 합니다.
-`FastAPI` 애플리케이션의 `lifespan` 매개변수는 **비동기 컨텍스트 매니저**를 받기 때문에, 새로운 `lifespan` 비동기 컨텍스트 매니저를 FastAPI에 전달할 수 있습니다.
+`FastAPI` 앱의 `lifespan` 매개변수는 **비동기 컨텍스트 매니저**를 받으므로, 새 `lifespan` 비동기 컨텍스트 매니저를 전달할 수 있습니다.
-{* ../../docs_src/events/tutorial003.py hl[22] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[22] *}
-## 대체 이벤트 (사용 중단)
+## 대체 이벤트(사용 중단) { #alternative-events-deprecated }
/// warning | 경고
-*시작*과 *종료*를 처리하는 권장 방법은 위에서 설명한 대로 `FastAPI` 애플리케이션의 `lifespan` 매개변수를 사용하는 것입니다. `lifespan` 매개변수를 제공하면 `startup`과 `shutdown` 이벤트 핸들러는 더 이상 호출되지 않습니다. `lifespan`을 사용할지, 모든 이벤트를 사용할지 선택해야 하며 둘 다 사용할 수는 없습니다.
+*시작*과 *종료*를 처리하는 권장 방법은 위에서 설명한 대로 `FastAPI` 앱의 `lifespan` 매개변수를 사용하는 것입니다. `lifespan` 매개변수를 제공하면 `startup`과 `shutdown` 이벤트 핸들러는 더 이상 호출되지 않습니다. `lifespan`만 쓰거나 이벤트만 쓰거나 둘 중 하나이지, 둘 다는 아닙니다.
-이 부분은 건너뛰셔도 좋습니다.
+이 부분은 아마 건너뛰셔도 됩니다.
///
*시작*과 *종료* 동안 실행될 이 로직을 정의하는 대체 방법이 있습니다.
-애플리케이션이 시작되기 전에 또는 종료될 때 실행해야 하는 이벤트 핸들러(함수)를 정의할 수 있습니다.
+애플리케이션이 시작되기 전에 또는 애플리케이션이 종료될 때 실행되어야 하는 이벤트 핸들러(함수)를 정의할 수 있습니다.
이 함수들은 `async def` 또는 일반 `def`로 선언할 수 있습니다.
-### `startup` 이벤트
+### `startup` 이벤트 { #startup-event }
애플리케이션이 시작되기 전에 실행되어야 하는 함수를 추가하려면, `"startup"` 이벤트로 선언합니다:
-{* ../../docs_src/events/tutorial001.py hl[8] *}
+{* ../../docs_src/events/tutorial001_py39.py hl[8] *}
-이 경우, `startup` 이벤트 핸들러 함수는 "database"라는 항목(단지 `dict`)을 일부 값으로 초기화합니다.
+이 경우, `startup` 이벤트 핸들러 함수는 "database"(그냥 `dict`) 항목을 일부 값으로 초기화합니다.
여러 개의 이벤트 핸들러 함수를 추가할 수 있습니다.
-애플리케이션은 모든 `startup` 이벤트 핸들러가 완료될 때까지 요청을 받기 시작하지 않습니다.
+그리고 모든 `startup` 이벤트 핸들러가 완료될 때까지 애플리케이션은 요청을 받기 시작하지 않습니다.
-### `shutdown` 이벤트
+### `shutdown` 이벤트 { #shutdown-event }
애플리케이션이 종료될 때 실행되어야 하는 함수를 추가하려면, `"shutdown"` 이벤트로 선언합니다:
-{* ../../docs_src/events/tutorial002.py hl[6] *}
+{* ../../docs_src/events/tutorial002_py39.py hl[6] *}
-여기서, `shutdown` 이벤트 핸들러 함수는 `"Application shutdown"`이라는 텍스트를 `log.txt` 파일에 기록합니다.
+여기서 `shutdown` 이벤트 핸들러 함수는 텍스트 한 줄 `"Application shutdown"`을 `log.txt` 파일에 기록합니다.
/// info | 정보
-`open()` 함수에서 `mode="a"`는 "추가"를 의미하므로, 파일에 있는 기존 내용은 덮어쓰지 않고 새로운 줄이 추가됩니다.
+`open()` 함수에서 `mode="a"`는 "append"(추가)를 의미하므로, 기존 내용을 덮어쓰지 않고 파일에 있던 내용 뒤에 줄이 추가됩니다.
///
/// tip | 팁
-이 경우, 우리는 표준 파이썬 `open()` 함수를 사용하여 파일과 상호작용하고 있습니다.
+이 경우에는 파일과 상호작용하는 표준 파이썬 `open()` 함수를 사용하고 있습니다.
-따라서 I/O(입출력) 작업이 포함되어 있어 디스크에 기록되는 것을 "기다리는" 과정이 필요합니다.
+따라서 I/O(input/output)가 포함되어 있어 디스크에 기록되는 것을 "기다리는" 과정이 필요합니다.
하지만 `open()`은 `async`와 `await`를 사용하지 않습니다.
-그래서 우리는 이벤트 핸들러 함수를 `async def` 대신 일반 `def`로 선언합니다.
+그래서 이벤트 핸들러 함수는 `async def` 대신 표준 `def`로 선언합니다.
///
-### `startup`과 `shutdown`을 함께 사용
+### `startup`과 `shutdown`을 함께 { #startup-and-shutdown-together }
-*시작*과 *종료* 로직이 연결될 가능성이 높습니다. 예를 들어, 무언가를 시작한 후 끝내거나, 자원을 획득한 후 해제하는 등의 작업을 할 수 있습니다.
+*시작*과 *종료* 로직은 연결되어 있을 가능성이 높습니다. 무언가를 시작했다가 끝내거나, 자원을 획득했다가 해제하는 등의 작업이 필요할 수 있습니다.
-이러한 작업을 별도의 함수로 처리하면 서로 로직이나 변수를 공유하지 않기 때문에 더 어려워집니다. 값들을 전역 변수에 저장하거나 비슷한 트릭을 사용해야 할 수 있습니다.
+로직이나 변수를 함께 공유하지 않는 분리된 함수에서 이를 처리하면, 전역 변수에 값을 저장하거나 비슷한 트릭이 필요해져 더 어렵습니다.
-그렇기 때문에 위에서 설명한 대로 `lifespan`을 사용하는 것이 권장됩니다.
+그 때문에, 이제는 위에서 설명한 대로 `lifespan`을 사용하는 것이 권장됩니다.
-## 기술적 세부사항
+## 기술적 세부사항 { #technical-details }
호기심 많은 분들을 위한 기술적인 세부사항입니다. 🤓
-ASGI 기술 사양에 따르면, 이는 Lifespan Protocol의 일부이며, `startup`과 `shutdown`이라는 이벤트를 정의합니다.
+내부적으로 ASGI 기술 사양에서는 이것이 Lifespan Protocol의 일부이며, `startup`과 `shutdown`이라는 이벤트를 정의합니다.
/// info | 정보
-Starlette의 `lifespan` 핸들러에 대해 더 읽고 싶다면 Starlette의 Lifespan 문서에서 확인할 수 있습니다.
+Starlette `lifespan` 핸들러에 대해서는 Starlette의 Lifespan 문서에서 더 읽어볼 수 있습니다.
-이 문서에는 코드의 다른 영역에서 사용할 수 있는 lifespan 상태를 처리하는 방법도 포함되어 있습니다.
+또한 코드의 다른 영역에서 사용할 수 있는 lifespan 상태를 처리하는 방법도 포함되어 있습니다.
///
-## 서브 애플리케이션
+## 서브 애플리케이션 { #sub-applications }
-🚨 이 lifespan 이벤트(`startup`과 `shutdown`)는 메인 애플리케이션에 대해서만 실행되며, [서브 애플리케이션 - Mounts](sub-applications.md){.internal-link target=_blank}에는 실행되지 않음을 유의하세요.
+🚨 이 lifespan 이벤트(startup 및 shutdown)는 메인 애플리케이션에 대해서만 실행되며, [서브 애플리케이션 - Mounts](sub-applications.md){.internal-link target=_blank}에는 실행되지 않음을 유의하세요.
diff --git a/docs/ko/docs/advanced/index.md b/docs/ko/docs/advanced/index.md
index 31704727ca..78ef5ffece 100644
--- a/docs/ko/docs/advanced/index.md
+++ b/docs/ko/docs/advanced/index.md
@@ -1,6 +1,6 @@
-# 심화 사용자 안내서 - 도입부
+# 심화 사용자 안내서 - 도입부 { #advanced-user-guide }
-## 추가 기능
+## 추가 기능 { #additional-features }
메인 [자습서 - 사용자 안내서](../tutorial/index.md){.internal-link target=_blank}는 여러분이 **FastAPI**의 모든 주요 기능을 둘러보시기에 충분할 것입니다.
@@ -14,14 +14,8 @@
///
-## 자습서를 먼저 읽으십시오
+## 자습서를 먼저 읽으십시오 { #read-the-tutorial-first }
여러분은 메인 [자습서 - 사용자 안내서](../tutorial/index.md){.internal-link target=_blank}의 지식으로 **FastAPI**의 대부분의 기능을 사용하실 수 있습니다.
이어지는 장들은 여러분이 메인 자습서 - 사용자 안내서를 이미 읽으셨으며 주요 아이디어를 알고 계신다고 가정합니다.
-
-## TestDriven.io 강좌
-
-여러분이 문서의 이 부분을 보완하시기 위해 심화-기초 강좌 수강을 희망하신다면 다음을 참고 하시기를 바랍니다: **TestDriven.io**의 FastAPI와 Docker를 사용한 테스트 주도 개발.
-
-그들은 현재 전체 수익의 10퍼센트를 **FastAPI** 개발에 기부하고 있습니다. 🎉 😄
diff --git a/docs/ko/docs/advanced/middlewares.md b/docs/ko/docs/advanced/middlewares.md
deleted file mode 100644
index 5778528a8e..0000000000
--- a/docs/ko/docs/advanced/middlewares.md
+++ /dev/null
@@ -1,96 +0,0 @@
-# 고급 미들웨어
-
-메인 튜토리얼에서 [Custom Middleware](../tutorial/middleware.md){.internal-link target=_blank}를 응용프로그램에 추가하는 방법을 읽으셨습니다.
-
-그리고 [CORS with the `CORSMiddleware`](){.internal-link target=_blank}하는 방법도 보셨습니다.
-
-이 섹션에서는 다른 미들웨어들을 사용하는 방법을 알아보겠습니다.
-
-## ASGI 미들웨어 추가하기
-
-**FastAPI**는 Starlette을 기반으로 하고 있으며, ASGI 사양을 구현하므로 ASGI 미들웨어를 사용할 수 있습니다.
-
-미들웨어가 FastAPI나 Starlette용으로 만들어지지 않아도 ASGI 사양을 준수하는 한 동작할 수 있습니다.
-
-일반적으로 ASGI 미들웨어는 첫 번째 인수로 ASGI 앱을 받는 클래스들입니다.
-
-따라서 타사 ASGI 미들웨어 문서에서 일반적으로 다음과 같이 사용하도록 안내할 것입니다.
-
-```Python
-from unicorn import UnicornMiddleware
-
-app = SomeASGIApp()
-
-new_app = UnicornMiddleware(app, some_config="rainbow")
-```
-
-하지만 내부 미들웨어가 서버 오류를 처리하고 사용자 정의 예외 처리기가 제대로 작동하도록 하는 더 간단한 방법을 제공하는 FastAPI(실제로는 Starlette)가 있습니다.
-
-이를 위해 `app.add_middleware()`를 사용합니다(CORS의 예에서와 같이).
-
-```Python
-from fastapi import FastAPI
-from unicorn import UnicornMiddleware
-
-app = FastAPI()
-
-app.add_middleware(UnicornMiddleware, some_config="rainbow")
-```
-
-`app.add_middleware()`는 첫 번째 인수로 미들웨어 클래스와 미들웨어에 전달할 추가 인수를 받습니다.
-
-## 통합 미들웨어
-
-**FastAPI**에는 일반적인 사용 사례를 위한 여러 미들웨어가 포함되어 있으며, 사용 방법은 다음에서 살펴보겠습니다.
-
-/// note | 기술 세부 사항
-
-다음 예제에서는 `from starlette.middleware.something import SomethingMiddleware`를 사용할 수도 있습니다.
-
-**FastAPI**는 개발자의 편의를 위해 `fastapi.middleware`에 여러 미들웨어를 제공합니다. 그러나 사용 가능한 대부분의 미들웨어는 Starlette에서 직접 제공합니다.
-
-///
-
-## `HTTPSRedirectMiddleware`
-
-들어오는 모든 요청이 `https` 또는 `wss`여야 합니다.
-
-`http` 또는 `ws`로 들어오는 모든 요청은 대신 보안 체계로 리디렉션됩니다.
-
-{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *}
-
-## `TrustedHostMiddleware`
-
-HTTP 호스트 헤더 공격을 방지하기 위해 모든 수신 요청에 올바르게 설정된 `Host` 헤더를 갖도록 강제합니다.
-
-{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *}
-
-다음 인수가 지원됩니다:
-
-* `allowed_hosts` - 호스트 이름으로 허용해야 하는 도메인 이름 목록입니다. 일치하는 하위 도메인에 대해 `*.example.com`과 같은 와일드카드 도메인이 지원됩니다. 모든 호스트 이름을 허용하려면 `allowed_hosts=[“*”]`를 사용하거나 미들웨어를 생략하세요.
-
-수신 요청의 유효성이 올바르게 확인되지 않으면 `400`이라는 응답이 전송됩니다.
-
-## `GZipMiddleware`
-
-`Accept-Encoding` 헤더에 `“gzip”`이 포함된 모든 요청에 대해 GZip 응답을 처리합니다.
-
-미들웨어는 표준 응답과 스트리밍 응답을 모두 처리합니다.
-
-{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *}
-
-지원되는 인수는 다음과 같습니다:
-
-* `minimum_size` - 이 최소 크기(바이트)보다 작은 응답은 GZip하지 않습니다. 기본값은 `500`입니다.
-* `compresslevel` - GZip 압축 중에 사용됩니다. 1에서 9 사이의 정수입니다. 기본값은 `9`입니다. 값이 낮을수록 압축 속도는 빨라지지만 파일 크기는 커지고, 값이 높을수록 압축 속도는 느려지지만 파일 크기는 작아집니다.
-
-## 기타 미들웨어
-
-다른 많은 ASGI 미들웨어가 있습니다.
-
-예를 들어:
-
-유비콘의 `ProxyHeadersMiddleware`>
-MessagePack
-
-사용 가능한 다른 미들웨어를 확인하려면 스타렛의 미들웨어 문서 및 ASGI Awesome List를 참조하세요.
diff --git a/docs/ko/docs/advanced/response-change-status-code.md b/docs/ko/docs/advanced/response-change-status-code.md
index 1ba9aa3ccb..4dfadde9df 100644
--- a/docs/ko/docs/advanced/response-change-status-code.md
+++ b/docs/ko/docs/advanced/response-change-status-code.md
@@ -1,31 +1,31 @@
-# 응답 - 상태 코드 변경
+# 응답 - 상태 코드 변경 { #response-change-status-code }
기본 [응답 상태 코드 설정](../tutorial/response-status-code.md){.internal-link target=_blank}이 가능하다는 걸 이미 알고 계실 겁니다.
하지만 경우에 따라 기본 설정과 다른 상태 코드를 반환해야 할 때가 있습니다.
-## 사용 예
+## 사용 예 { #use-case }
예를 들어 기본적으로 HTTP 상태 코드 "OK" `200`을 반환하고 싶다고 가정해 봅시다.
하지만 데이터가 존재하지 않으면 이를 새로 생성하고, HTTP 상태 코드 "CREATED" `201`을 반환하고자 할 때가 있을 수 있습니다.
-이때도 여전히 `response_model`을 사용하여 반환하는 데이터를 필터링하고 변환하고 싶을 수 있습니다.
+하지만 여전히 `response_model`을 사용하여 반환하는 데이터를 필터링하고 변환할 수 있기를 원합니다.
이런 경우에는 `Response` 파라미터를 사용할 수 있습니다.
-## `Response` 파라미터 사용하기
+## `Response` 파라미터 사용하기 { #use-a-response-parameter }
-*경로 작동 함수*에 `Response` 타입의 파라미터를 선언할 수 있습니다. (쿠키와 헤더에 대해 선언하는 것과 유사하게)
+*경로 처리 함수*에 `Response` 타입의 파라미터를 선언할 수 있습니다. (쿠키와 헤더에 대해 선언하는 것과 유사하게)
그리고 이 *임시* 응답 객체에서 `status_code`를 설정할 수 있습니다.
-{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *}
+{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *}
-그리고 평소처럼 원하는 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다.
+그리고 평소처럼 필요한 어떤 객체든 반환할 수 있습니다(`dict`, 데이터베이스 모델 등).
`response_model`을 선언했다면 반환된 객체는 여전히 필터링되고 변환됩니다.
-**FastAPI**는 이 *임시* 응답 객체에서 상태 코드(쿠키와 헤더 포함)를 추출하여, `response_model`로 필터링된 반환 값을 최종 응답에 넣습니다.
+**FastAPI**는 이 *임시* 응답 객체에서 상태 코드(쿠키와 헤더 포함)를 추출하여, `response_model`로 필터링된 반환 값을 포함하는 최종 응답에 넣습니다.
또한, 의존성에서도 `Response` 파라미터를 선언하고 그 안에서 상태 코드를 설정할 수 있습니다. 단, 마지막으로 설정된 상태 코드가 우선 적용된다는 점을 유의하세요.
diff --git a/docs/ko/docs/advanced/response-cookies.md b/docs/ko/docs/advanced/response-cookies.md
index 50da713fe6..eef74276fd 100644
--- a/docs/ko/docs/advanced/response-cookies.md
+++ b/docs/ko/docs/advanced/response-cookies.md
@@ -1,49 +1,51 @@
-# 응답 쿠키
+# 응답 쿠키 { #response-cookies }
-## `Response` 매개변수 사용하기
+## `Response` 매개변수 사용하기 { #use-a-response-parameter }
-*경로 작동 함수*에서 `Response` 타입의 매개변수를 선언할 수 있습니다.
+*경로 처리 함수*에서 `Response` 타입의 매개변수를 선언할 수 있습니다.
그런 다음 해당 *임시* 응답 객체에서 쿠키를 설정할 수 있습니다.
-{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *}
+{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *}
-그런 다음 필요한 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다.
+그런 다음 일반적으로 하듯이 필요한 어떤 객체든 반환할 수 있습니다(`dict`, 데이터베이스 모델 등).
그리고 `response_model`을 선언했다면 반환한 객체를 거르고 변환하는 데 여전히 사용됩니다.
-**FastAPI**는 그 *임시* 응답에서 쿠키(또한 헤더 및 상태 코드)를 추출하고, 반환된 값이 포함된 최종 응답에 이를 넣습니다. 이 값은 `response_model`로 걸러지게 됩니다.
+**FastAPI**는 그 *임시* 응답에서 쿠키(또한 헤더 및 상태 코드)를 추출하고, `response_model`로 필터링된 반환 값이 포함된 최종 응답에 이를 넣습니다.
또한 의존관계에서 `Response` 매개변수를 선언하고, 해당 의존성에서 쿠키(및 헤더)를 설정할 수도 있습니다.
-## `Response`를 직접 반환하기
+## `Response`를 직접 반환하기 { #return-a-response-directly }
코드에서 `Response`를 직접 반환할 때도 쿠키를 생성할 수 있습니다.
이를 위해 [Response를 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 설명한 대로 응답을 생성할 수 있습니다.
그런 다음 쿠키를 설정하고 반환하면 됩니다:
-{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
-/// tip
+
+{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *}
+
+/// tip | 팁
`Response` 매개변수를 사용하지 않고 응답을 직접 반환하는 경우, FastAPI는 이를 직접 반환한다는 점에 유의하세요.
따라서 데이터가 올바른 유형인지 확인해야 합니다. 예: `JSONResponse`를 반환하는 경우, JSON과 호환되는지 확인하세요.
-또한 `response_model`로 걸러져야 할 데이터가 전달되지 않도록 확인하세요.
+또한 `response_model`로 필터링되어야 했던 데이터를 전송하지 않도록 하세요.
///
-### 추가 정보
+### 추가 정보 { #more-info }
-/// note | 기술적 세부사항
+/// note | 기술 세부사항
`from starlette.responses import Response` 또는 `from starlette.responses import JSONResponse`를 사용할 수도 있습니다.
-**FastAPI**는 개발자의 편의를 위해 `fastapi.responses`로 동일한 `starlette.responses`를 제공합니다. 그러나 대부분의 응답은 Starlette에서 직접 제공됩니다.
+**FastAPI**는 개발자의 편의를 위해 `fastapi.responses`로 동일한 `starlette.responses`를 제공합니다. 하지만 사용 가능한 대부분의 응답은 Starlette에서 직접 제공됩니다.
또한 `Response`는 헤더와 쿠키를 설정하는 데 자주 사용되므로, **FastAPI**는 이를 `fastapi.Response`로도 제공합니다.
///
-사용 가능한 모든 매개변수와 옵션은 Starlette 문서에서 확인할 수 있습니다.
+사용 가능한 모든 매개변수와 옵션은 Starlette의 문서에서 확인할 수 있습니다.
diff --git a/docs/ko/docs/advanced/response-directly.md b/docs/ko/docs/advanced/response-directly.md
index 08d63c43ce..abf06bb18b 100644
--- a/docs/ko/docs/advanced/response-directly.md
+++ b/docs/ko/docs/advanced/response-directly.md
@@ -1,20 +1,20 @@
-# 응답을 직접 반환하기
+# 응답을 직접 반환하기 { #return-a-response-directly }
-**FastAPI**에서 *경로 작업(path operation)*을 생성할 때, 일반적으로 `dict`, `list`, Pydantic 모델, 데이터베이스 모델 등의 데이터를 반환할 수 있습니다.
+**FastAPI**에서 *경로 처리(path operation)*를 생성할 때, 일반적으로 `dict`, `list`, Pydantic 모델, 데이터베이스 모델 등의 데이터를 반환할 수 있습니다.
-기본적으로 **FastAPI**는 [JSON 호환 가능 인코더](../tutorial/encoder.md){.internal-link target=_blank}에 설명된 `jsonable_encoder`를 사용해 해당 반환 값을 자동으로 `JSON`으로 변환합니다.
+기본적으로 **FastAPI**는 [JSON 호환 가능 인코더](../tutorial/encoder.md){.internal-link target=_blank}에 설명된 `jsonable_encoder`를 사용해 해당 반환 값을 자동으로 JSON으로 변환합니다.
-그런 다음, JSON 호환 데이터(예: `dict`)를 `JSONResponse`에 넣어 사용자의 응답을 전송하는 방식으로 처리됩니다.
+그런 다음, 내부적으로는 JSON 호환 데이터(예: `dict`)를 `JSONResponse`에 넣어 클라이언트로 응답을 전송하는 데 사용합니다.
-그러나 *경로 작업*에서 `JSONResponse`를 직접 반환할 수도 있습니다.
+하지만 *경로 처리*에서 `JSONResponse`를 직접 반환할 수도 있습니다.
예를 들어, 사용자 정의 헤더나 쿠키를 반환해야 하는 경우에 유용할 수 있습니다.
-## `Response` 반환하기
+## `Response` 반환하기 { #return-a-response }
사실, `Response` 또는 그 하위 클래스를 반환할 수 있습니다.
-/// tip
+/// tip | 팁
`JSONResponse` 자체도 `Response`의 하위 클래스입니다.
@@ -26,38 +26,40 @@ Pydantic 모델로 데이터 변환을 수행하지 않으며, 내용을 다른
이로 인해 많은 유연성을 얻을 수 있습니다. 어떤 데이터 유형이든 반환할 수 있고, 데이터 선언이나 유효성 검사를 재정의할 수 있습니다.
-## `Response`에서 `jsonable_encoder` 사용하기
+## `Response`에서 `jsonable_encoder` 사용하기 { #using-the-jsonable-encoder-in-a-response }
-**FastAPI**는 반환하는 `Response`에 아무런 변환을 하지 않으므로, 그 내용이 준비되어 있어야 합니다.
+**FastAPI**는 반환하는 `Response`에 아무런 변경도 하지 않으므로, 그 내용이 준비되어 있는지 확인해야 합니다.
-예를 들어, Pydantic 모델을 `dict`로 변환해 `JSONResponse`에 넣지 않으면 JSON 호환 유형으로 변환된 데이터 유형(예: `datetime`, `UUID` 등)이 사용되지 않습니다.
+예를 들어, Pydantic 모델을 먼저 `dict`로 변환하고 `datetime`, `UUID` 등의 모든 데이터 타입을 JSON 호환 타입으로 변환하지 않으면 Pydantic 모델을 `JSONResponse`에 넣을 수 없습니다.
이러한 경우, 데이터를 응답에 전달하기 전에 `jsonable_encoder`를 사용하여 변환할 수 있습니다:
-{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *}
+{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *}
-/// note | 기술적 세부 사항
+/// note | 기술 세부사항
`from starlette.responses import JSONResponse`를 사용할 수도 있습니다.
-**FastAPI**는 개발자의 편의를 위해 `starlette.responses`를 `fastapi.responses`로 제공합니다. 그러나 대부분의 가능한 응답은 Starlette에서 직접 제공합니다.
+**FastAPI**는 개발자의 편의를 위해 `starlette.responses`를 `fastapi.responses`로 제공합니다. 하지만 대부분의 사용 가능한 응답은 Starlette에서 직접 제공합니다.
///
-## 사용자 정의 `Response` 반환하기
-위 예제는 필요한 모든 부분을 보여주지만, 아직 유용하지는 않습니다. 사실 데이터를 직접 반환하면 **FastAPI**가 이를 `JSONResponse`에 넣고 `dict`로 변환하는 등 모든 작업을 자동으로 처리합니다.
+## 사용자 정의 `Response` 반환하기 { #returning-a-custom-response }
-이제, 사용자 정의 응답을 반환하는 방법을 알아보겠습니다.
+위 예제는 필요한 모든 부분을 보여주지만, 아직은 그다지 유용하지 않습니다. `item`을 그냥 직접 반환했어도 **FastAPI**가 기본으로 이를 `JSONResponse`에 넣고 `dict`로 변환하는 등의 작업을 모두 수행해 주었을 것이기 때문입니다.
-예를 들어 XML 응답을 반환하고 싶다고 가정해보겠습니다.
+이제, 이를 사용해 사용자 정의 응답을 반환하는 방법을 알아보겠습니다.
+
+예를 들어 XML 응답을 반환하고 싶다고 가정해 보겠습니다.
XML 내용을 문자열에 넣고, 이를 `Response`에 넣어 반환할 수 있습니다:
-{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
+{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
+
+## 참고 사항 { #notes }
-## 참고 사항
`Response`를 직접 반환할 때, 그 데이터는 자동으로 유효성 검사되거나, 변환(직렬화)되거나, 문서화되지 않습니다.
그러나 [OpenAPI에서 추가 응답](additional-responses.md){.internal-link target=_blank}에서 설명된 대로 문서화할 수 있습니다.
-이후 단락에서 자동 데이터 변환, 문서화 등을 사용하면서 사용자 정의 `Response`를 선언하는 방법을 확인할 수 있습니다.
+이후 섹션에서 자동 데이터 변환, 문서화 등을 계속 사용하면서 이러한 사용자 정의 `Response`를 사용하는/선언하는 방법을 확인할 수 있습니다.
diff --git a/docs/ko/docs/advanced/response-headers.md b/docs/ko/docs/advanced/response-headers.md
index e4e022c9b7..1c36db9b95 100644
--- a/docs/ko/docs/advanced/response-headers.md
+++ b/docs/ko/docs/advanced/response-headers.md
@@ -1,12 +1,12 @@
-# 응답 헤더
+# 응답 헤더 { #response-headers }
-## `Response` 매개변수 사용하기
+## `Response` 매개변수 사용하기 { #use-a-response-parameter }
-여러분은 *경로 작동 함수*에서 `Response` 타입의 매개변수를 선언할 수 있습니다 (쿠키와 같이 사용할 수 있습니다).
+여러분은 *경로 처리 함수*에서 `Response` 타입의 매개변수를 선언할 수 있습니다 (쿠키와 같이 사용할 수 있습니다).
그런 다음, 여러분은 해당 *임시* 응답 객체에서 헤더를 설정할 수 있습니다.
-{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *}
+{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *}
그 후, 일반적으로 사용하듯이 필요한 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다.
@@ -16,26 +16,26 @@
또한, 종속성에서 `Response` 매개변수를 선언하고 그 안에서 헤더(및 쿠키)를 설정할 수 있습니다.
-## `Response` 직접 반환하기
+## `Response` 직접 반환하기 { #return-a-response-directly }
`Response`를 직접 반환할 때에도 헤더를 추가할 수 있습니다.
[응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 설명한 대로 응답을 생성하고, 헤더를 추가 매개변수로 전달하세요.
-{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *}
+{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *}
-/// note | 기술적 세부사항
+/// note | 기술 세부사항
`from starlette.responses import Response`나 `from starlette.responses import JSONResponse`를 사용할 수도 있습니다.
-**FastAPI**는 `starlette.responses`를 `fastapi.responses`로 개발자의 편의를 위해 직접 제공하지만, 대부분의 응답은 Starlette에서 직접 제공됩니다.
+**FastAPI**는 해당 *임시* 응답에서 헤더(쿠키와 상태 코드도 포함)를 추출하여, 여러분이 반환한 값을 포함하는 최종 응답에 `response_model`로 필터링된 값을 넣습니다.
그리고 `Response`는 헤더와 쿠키를 설정하는 데 자주 사용될 수 있으므로, **FastAPI**는 `fastapi.Response`로도 이를 제공합니다.
///
-## 커스텀 헤더
+## 커스텀 헤더 { #custom-headers }
-‘X-’ 접두어를 사용하여 커스텀 사설 헤더를 추가할 수 있습니다.
+`X-` 접두어를 사용하여 커스텀 사설 헤더를 추가할 수 있다는 점을 기억하세요.
-하지만, 여러분이 브라우저에서 클라이언트가 볼 수 있기를 원하는 커스텀 헤더가 있는 경우, CORS 설정에 이를 추가해야 합니다([CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}에서 자세히 알아보세요). `expose_headers` 매개변수를 사용하여 Starlette의 CORS 설명서에 문서화된 대로 설정할 수 있습니다.
+하지만, 여러분이 브라우저에서 클라이언트가 볼 수 있기를 원하는 커스텀 헤더가 있는 경우, CORS 설정에 이를 추가해야 합니다([CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}에서 자세히 알아보세요). Starlette의 CORS 문서에 문서화된 `expose_headers` 매개변수를 사용하세요.
diff --git a/docs/ko/docs/advanced/sub-applications.md b/docs/ko/docs/advanced/sub-applications.md
index c5835de15c..e1554ca5d6 100644
--- a/docs/ko/docs/advanced/sub-applications.md
+++ b/docs/ko/docs/advanced/sub-applications.md
@@ -1,67 +1,67 @@
-# 하위 응용프로그램 - 마운트
+# 하위 응용프로그램 - 마운트 { #sub-applications-mounts }
-만약 각각의 독립적인 OpenAPI와 문서 UI를 갖는 두 개의 독립적인 FastAPI 응용프로그램이 필요하다면, 메인 어플리케이션에 하나 (또는 그 이상의) 하위-응용프로그램(들)을 “마운트"해서 사용할 수 있습니다.
+각각의 독립적인 OpenAPI와 문서 UI를 갖는 두 개의 독립적인 FastAPI 애플리케이션이 필요하다면, 메인 앱을 두고 하나(또는 그 이상)의 하위 응용프로그램을 "마운트"할 수 있습니다.
-## **FastAPI** 응용프로그램 마운트
+## **FastAPI** 애플리케이션 마운트 { #mounting-a-fastapi-application }
-“마운트"이란 완전히 “독립적인" 응용프로그램을 특정 경로에 추가하여 해당 하위 응용프로그램에서 선언된 *경로 동작*을 통해 해당 경로 아래에 있는 모든 작업들을 처리할 수 있도록 하는 것을 의미합니다.
+"마운트"란 완전히 "독립적인" 애플리케이션을 특정 경로에 추가하고, 그 하위 응용프로그램에 선언된 _경로 처리_로 해당 경로 아래의 모든 것을 처리하도록 하는 것을 의미합니다.
-### 최상단 응용프로그램
+### 최상위 애플리케이션 { #top-level-application }
-먼저, 메인, 최상단의 **FastAPI** 응용프로그램과 이것의 *경로 동작*을 생성합니다:
+먼저, 메인 최상위 **FastAPI** 애플리케이션과 그 *경로 처리*를 생성합니다:
-{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *}
+{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *}
-### 하위 응용프로그램
+### 하위 응용프로그램 { #sub-application }
-다음으로, 하위 응용프로그램과 이것의 *경로 동작*을 생성합니다:
+그 다음, 하위 응용프로그램과 그 *경로 처리*를 생성합니다.
-이 하위 응용프로그램은 또 다른 표준 FastAPI 응용프로그램입니다. 다만 이것은 “마운트”될 것입니다:
+이 하위 응용프로그램은 또 다른 표준 FastAPI 애플리케이션이지만, "마운트"될 애플리케이션입니다:
-{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *}
+{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *}
-### 하위 응용프로그램 마운트
+### 하위 응용프로그램 마운트 { #mount-the-sub-application }
-최상단 응용프로그램, `app`에 하위 응용프로그램, `subapi`를 마운트합니다.
+최상위 애플리케이션 `app`에서 하위 응용프로그램 `subapi`를 마운트합니다.
-이 예시에서, 하위 응용프로그램션은 `/subapi` 경로에 마운트 될 것입니다:
+이 경우 `/subapi` 경로에 마운트됩니다:
-{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *}
+{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *}
-### 자동으로 생성된 API 문서 확인
+### 자동 API 문서 확인 { #check-the-automatic-api-docs }
-이제, `uvicorn`으로 메인 응용프로그램을 실행하십시오. 당신의 파일이 `main.py`라면, 이렇게 실행합니다:
+이제 파일과 함께 `fastapi` 명령을 실행하세요:
+
-다음으로, http://127.0.0.1:8000/subapi/docs에서 하위 응용프로그램의 문서를 여십시오.
+그 다음, http://127.0.0.1:8000/subapi/docs에서 하위 응용프로그램의 문서를 여세요.
-하위 경로 접두사 `/subapi` 아래에 선언된 *경로 동작* 을 포함하는, 하위 응용프로그램에 대한 자동 API 문서를 확인할 수 있습니다:
+하위 응용프로그램의 자동 API 문서를 보게 될 것이며, 하위 경로 접두사 `/subapi` 아래에 올바르게 포함된 하위 응용프로그램 자체의 _경로 처리_만 포함됩니다:
-
+
-두 사용자 인터페이스 중 어느 하나를 사용해야하는 경우, 브라우저는 특정 응용프로그램 또는 하위 응용프로그램과 각각 통신할 수 있기 때문에 올바르게 동작할 것입니다.
+두 사용자 인터페이스 중 어느 것과 상호작용을 시도하더라도 올바르게 동작할 것입니다. 브라우저가 각 특정 앱 또는 하위 앱과 통신할 수 있기 때문입니다.
-### 기술적 세부사항: `root_path`
+### 기술적 세부사항: `root_path` { #technical-details-root-path }
-위에 설명된 것과 같이 하위 응용프로그램을 마운트하는 경우, FastAPI는 `root_path`라고 하는 ASGI 명세의 매커니즘을 사용하여 하위 응용프로그램에 대한 마운트 경로 통신을 처리합니다.
+위에서 설명한 대로 하위 응용프로그램을 마운트하면, FastAPI는 ASGI 명세의 메커니즘인 `root_path`를 사용해 하위 응용프로그램에 대한 마운트 경로를 전달하는 작업을 처리합니다.
-이를 통해, 하위 응용프로그램은 문서 UI를 위해 경로 접두사를 사용해야 한다는 사실을 인지합니다.
+이렇게 하면 하위 응용프로그램은 문서 UI를 위해 해당 경로 접두사를 사용해야 한다는 것을 알게 됩니다.
-하위 응용프로그램에도 역시 다른 하위 응용프로그램을 마운트하는 것이 가능하며 FastAPI가 모든 `root_path` 들을 자동적으로 처리하기 때문에 모든 것은 올바르게 동작할 것입니다.
+또한 하위 응용프로그램도 자체적으로 하위 앱을 마운트할 수 있으며, FastAPI가 이 모든 `root_path`를 자동으로 처리하기 때문에 모든 것이 올바르게 동작합니다.
-`root_path`와 이것을 사용하는 방법에 대해서는 [프록시의 뒷단](./behind-a-proxy.md){.internal-link target=_blank} 섹션에서 배울 수 있습니다.
+`root_path`와 이를 명시적으로 사용하는 방법에 대해서는 [프록시 뒤](behind-a-proxy.md){.internal-link target=_blank} 섹션에서 더 알아볼 수 있습니다.
diff --git a/docs/ko/docs/advanced/templates.md b/docs/ko/docs/advanced/templates.md
index 6126357132..fffffa6a54 100644
--- a/docs/ko/docs/advanced/templates.md
+++ b/docs/ko/docs/advanced/templates.md
@@ -1,4 +1,4 @@
-# 템플릿
+# 템플릿 { #templates }
**FastAPI**와 함께 원하는 어떤 템플릿 엔진도 사용할 수 있습니다.
@@ -6,10 +6,9 @@
설정을 쉽게 할 수 있는 유틸리티가 있으며, 이를 **FastAPI** 애플리케이션에서 직접 사용할 수 있습니다(Starlette 제공).
-## 의존성 설치
-
-가상 환경을 생성하고(virtual environment{.internal-link target=_blank}), 활성화한 후 jinja2를 설치해야 합니다:
+## 의존성 설치 { #install-dependencies }
+[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고, 활성화한 후 `jinja2`를 설치해야 합니다:
-**FastAPI** WebSocket 응용 프로그램이 응답을 돌려줄 것입니다:
+그리고 WebSockets가 포함된 **FastAPI** 응용 프로그램이 응답을 돌려줄 것입니다:
@@ -94,9 +94,9 @@ $ fastapi dev main.py
-모든 메시지는 동일한 WebSocket 연결을 사용합니다.
+그리고 모든 메시지는 동일한 WebSocket 연결을 사용합니다.
-## `Depends` 및 기타 사용하기
+## `Depends` 및 기타 사용하기 { #using-depends-and-others }
WebSocket 엔드포인트에서 `fastapi`에서 다음을 가져와 사용할 수 있습니다:
@@ -107,21 +107,21 @@ WebSocket 엔드포인트에서 `fastapi`에서 다음을 가져와 사용할
* `Path`
* `Query`
-이들은 다른 FastAPI 엔드포인트/*경로 작동*과 동일하게 동작합니다:
+이들은 다른 FastAPI 엔드포인트/*경로 처리*와 동일하게 동작합니다:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *}
/// info | 정보
-WebSocket에서는 `HTTPException`을 발생시키는 것이 적합하지 않습니다. 대신 `WebSocketException`을 발생시킵니다.
+WebSocket이기 때문에 `HTTPException`을 발생시키는 것은 적절하지 않습니다. 대신 `WebSocketException`을 발생시킵니다.
명세서에 정의된 유효한 코드를 사용하여 종료 코드를 설정할 수 있습니다.
///
-### 종속성을 가진 WebSockets 테스트
+### 종속성을 가진 WebSockets 시도해보기 { #try-the-websockets-with-dependencies }
-파일 이름이 `main.py`라고 가정하고 응용 프로그램을 실행합니다:
+파일 이름이 `main.py`라고 가정하고 다음으로 응용 프로그램을 실행합니다:
-## 연결 해제 및 다중 클라이언트 처리
+## 연결 해제 및 다중 클라이언트 처리 { #handling-disconnections-and-multiple-clients }
-WebSocket 연결이 닫히면, `await websocket.receive_text()`가 `WebSocketDisconnect` 예외를 발생시킵니다. 이를 잡아 처리할 수 있습니다:
+WebSocket 연결이 닫히면, `await websocket.receive_text()`가 `WebSocketDisconnect` 예외를 발생시킵니다. 그러면 이 예제처럼 이를 잡아 처리할 수 있습니다.
{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *}
@@ -160,7 +160,7 @@ WebSocket 연결이 닫히면, `await websocket.receive_text()`가 `WebSocketDis
* 여러 브라우저 탭에서 앱을 엽니다.
* 각 탭에서 메시지를 작성합니다.
-* 한 탭을 닫아보세요.
+* 그런 다음 탭 중 하나를 닫아보세요.
`WebSocketDisconnect` 예외가 발생하며, 다른 모든 클라이언트가 다음과 같은 메시지를 수신합니다:
@@ -170,17 +170,17 @@ Client #1596980209979 left the chat
/// tip | 팁
-위 응용 프로그램은 여러 WebSocket 연결에 메시지를 브로드캐스트하는 방법을 보여주는 간단한 예제입니다.
+위 앱은 여러 WebSocket 연결에 메시지를 처리하고 브로드캐스트하는 방법을 보여주는 최소한의 간단한 예제입니다.
-그러나 모든 것을 메모리의 단일 리스트로 처리하므로, 프로세스가 실행 중인 동안만 동작하며 단일 프로세스에서만 작동합니다.
+하지만 모든 것을 메모리의 단일 리스트로 처리하므로, 프로세스가 실행 중인 동안만 동작하며 단일 프로세스에서만 작동한다는 점을 기억하세요.
-FastAPI와 쉽게 통합할 수 있으면서 더 견고하고 Redis, PostgreSQL 등을 지원하는 도구를 찾고 있다면, encode/broadcaster를 확인하세요.
+FastAPI와 쉽게 통합할 수 있으면서 더 견고하고 Redis, PostgreSQL 등을 지원하는 도구가 필요하다면, encode/broadcaster를 확인하세요.
///
-## 추가 정보
+## 추가 정보 { #more-info }
-다음 옵션에 대한 자세한 내용을 보려면 Starlette의 문서를 확인하세요:
+다음 옵션에 대해 더 알아보려면 Starlette의 문서를 확인하세요:
* `WebSocket` 클래스.
* 클래스 기반 WebSocket 처리.
diff --git a/docs/ko/docs/advanced/wsgi.md b/docs/ko/docs/advanced/wsgi.md
index 3e9de3e6ca..89cf57cfef 100644
--- a/docs/ko/docs/advanced/wsgi.md
+++ b/docs/ko/docs/advanced/wsgi.md
@@ -1,10 +1,10 @@
-# WSGI 포함하기 - Flask, Django 그 외
+# WSGI 포함하기 - Flask, Django 그 외 { #including-wsgi-flask-django-others }
-[서브 응용 프로그램 - 마운트](sub-applications.md){.internal-link target=_blank}, [프록시 뒤편에서](behind-a-proxy.md){.internal-link target=_blank}에서 보았듯이 WSGI 응용 프로그램들을 다음과 같이 마운트 할 수 있습니다.
+[서브 응용 프로그램 - 마운트](sub-applications.md){.internal-link target=_blank}, [프록시 뒤편에서](behind-a-proxy.md){.internal-link target=_blank}에서 보았듯이 WSGI 응용 프로그램들을 마운트 할 수 있습니다.
-`WSGIMiddleware`를 사용하여 WSGI 응용 프로그램(예: Flask, Django 등)을 감쌀 수 있습니다.
+이를 위해 `WSGIMiddleware`를 사용해 WSGI 응용 프로그램(예: Flask, Django 등)을 감쌀 수 있습니다.
-## `WSGIMiddleware` 사용하기
+## `WSGIMiddleware` 사용하기 { #using-wsgimiddleware }
`WSGIMiddleware`를 불러와야 합니다.
@@ -12,9 +12,9 @@
그 후, 해당 경로에 마운트합니다.
-{* ../../docs_src/wsgi/tutorial001.py hl[2:3,23] *}
+{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *}
-## 확인하기
+## 확인하기 { #check-it }
이제 `/v1/` 경로에 있는 모든 요청은 Flask 응용 프로그램에서 처리됩니다.
@@ -26,7 +26,7 @@
Hello, World from Flask!
```
-그리고 다음으로 이동하면 http://localhost:8000/v2 Flask의 응답을 볼 수 있습니다:
+그리고 다음으로 이동하면 http://localhost:8000/v2 **FastAPI**의 응답을 볼 수 있습니다:
```JSON
{
diff --git a/docs/ko/docs/benchmarks.md b/docs/ko/docs/benchmarks.md
index aff8ae70ef..2d4fdbeddb 100644
--- a/docs/ko/docs/benchmarks.md
+++ b/docs/ko/docs/benchmarks.md
@@ -1,10 +1,10 @@
-# 벤치마크
+# 벤치마크 { #benchmarks }
-독립적인 TechEmpower 벤치마크에 따르면 **FastAPI** 애플리케이션이 Uvicorn을 사용하여 가장 빠른 Python 프레임워크 중 하나로 실행되며, Starlette와 Uvicorn 자체(내부적으로 FastAPI가 사용하는 도구)보다 조금 아래에 위치합니다.
+독립적인 TechEmpower 벤치마크에 따르면 **FastAPI** 애플리케이션이 Uvicorn을 사용하여 사용 가능한 가장 빠른 Python 프레임워크 중 하나로 실행되며, Starlette와 Uvicorn 자체(내부적으로 FastAPI가 사용하는 도구)보다 조금 아래에 위치합니다.
그러나 벤치마크와 비교를 확인할 때 다음 사항을 염두에 두어야 합니다.
-## 벤치마크와 속도
+## 벤치마크와 속도 { #benchmarks-and-speed }
벤치마크를 확인할 때, 일반적으로 여러 가지 유형의 도구가 동등한 것으로 비교되는 것을 볼 수 있습니다.
@@ -16,7 +16,7 @@
* **Uvicorn**: ASGI 서버
* **Starlette**: (Uvicorn 사용) 웹 마이크로 프레임워크
- * **FastAPI**: (Starlette 사용) API 구축을 위한 데이터 검증 등 여러 추가 기능이 포함된 API 마이크로 프레임워크
+ * **FastAPI**: (Starlette 사용) 데이터 검증 등 API를 구축하기 위한 여러 추가 기능이 포함된 API 마이크로 프레임워크
* **Uvicorn**:
* 서버 자체 외에는 많은 추가 코드가 없기 때문에 최고의 성능을 발휘합니다.
@@ -29,6 +29,6 @@
* **FastAPI**:
* Starlette가 Uvicorn을 사용하므로 Uvicorn보다 빨라질 수 없는 것과 마찬가지로, **FastAPI**는 Starlette를 사용하므로 더 빠를 수 없습니다.
* FastAPI는 Starlette에 추가적으로 더 많은 기능을 제공합니다. API를 구축할 때 거의 항상 필요한 데이터 검증 및 직렬화와 같은 기능들이 포함되어 있습니다. 그리고 이를 사용하면 문서 자동화 기능도 제공됩니다(문서 자동화는 응용 프로그램 실행 시 오버헤드를 추가하지 않고 시작 시 생성됩니다).
- * FastAPI를 사용하지 않고 직접 Starlette(또는 Sanic, Flask, Responder 등)를 사용했다면 데이터 검증 및 직렬화를 직접 구현해야 합니다. 따라서 최종 응용 프로그램은 FastAPI를 사용한 것과 동일한 오버헤드를 가지게 될 것입니다. 많은 경우 데이터 검증 및 직렬화가 응용 프로그램에서 작성된 코드 중 가장 많은 부분을 차지합니다.
- * 따라서 FastAPI를 사용함으로써 개발 시간, 버그, 코드 라인을 줄일 수 있으며, FastAPI를 사용하지 않았을 때와 동일하거나 더 나은 성능을 얻을 수 있습니다(코드에서 모두 구현해야 하기 때문에).
- * FastAPI를 비교할 때는 Flask-apispec, NestJS, Molten 등 데이터 검증, 직렬화 및 문서화가 통합된 자동 데이터 검증, 직렬화 및 문서화를 제공하는 웹 응용 프로그램 프레임워크(또는 도구 집합)와 비교하세요.
+ * FastAPI를 사용하지 않고 직접 Starlette(또는 다른 도구, 예: Sanic, Flask, Responder 등)를 사용했다면 데이터 검증 및 직렬화를 직접 구현해야 합니다. 따라서 최종 응용 프로그램은 FastAPI를 사용한 것과 동일한 오버헤드를 가지게 될 것입니다. 많은 경우 데이터 검증 및 직렬화가 응용 프로그램에서 작성된 코드 중 가장 많은 부분을 차지합니다.
+ * 따라서 FastAPI를 사용함으로써 개발 시간, 버그, 코드 라인을 줄일 수 있으며, FastAPI를 사용하지 않았을 때와 동일한 성능(또는 더 나은 성능)을 얻을 수 있을 것입니다(코드에서 모두 구현해야 하기 때문에).
+ * FastAPI를 비교할 때는 Flask-apispec, NestJS, Molten 등 데이터 검증, 직렬화 및 문서화를 제공하는 웹 애플리케이션 프레임워크(또는 도구 집합)와 비교하세요. 통합된 자동 데이터 검증, 직렬화 및 문서화를 제공하는 프레임워크입니다.
diff --git a/docs/ko/docs/deployment/cloud.md b/docs/ko/docs/deployment/cloud.md
index dbc814bbdb..0705e120c4 100644
--- a/docs/ko/docs/deployment/cloud.md
+++ b/docs/ko/docs/deployment/cloud.md
@@ -1,13 +1,24 @@
-# FastAPI를 클라우드 제공업체에서 배포하기
+# 클라우드 제공업체에서 FastAPI 배포하기 { #deploy-fastapi-on-cloud-providers }
사실상 거의 **모든 클라우드 제공업체**를 사용하여 여러분의 FastAPI 애플리케이션을 배포할 수 있습니다.
대부분의 경우, 주요 클라우드 제공업체에서는 FastAPI를 배포할 수 있도록 가이드를 제공합니다.
-## 클라우드 제공업체 - 후원자들
+## FastAPI Cloud { #fastapi-cloud }
-몇몇 클라우드 제공업체들은 [**FastAPI를 후원하며**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, 이를 통해 FastAPI와 FastAPI **생태계**가 지속적이고 건전한 **발전**을 할 수 있습니다.
+**FastAPI Cloud**는 **FastAPI**를 만든 동일한 작성자와 팀이 구축했습니다.
-이는 FastAPI와 **커뮤니티** (여러분)에 대한 진정한 헌신을 보여줍니다. 그들은 여러분에게 **좋은 서비스**를 제공할 뿐 만이 아니라 여러분이 **훌륭하고 건강한 프레임워크인** FastAPI 를 사용하길 원하기 때문입니다. 🙇
+최소한의 노력으로 API를 **구축**, **배포**, **접근**하는 과정을 간소화합니다.
-아래와 같은 서비스를 사용해보고 각 서비스의 가이드를 따를 수도 있습니다.
+FastAPI로 앱을 빌드할 때의 동일한 **개발자 경험**을 클라우드에 **배포**하는 데에도 제공합니다. 🎉
+
+FastAPI Cloud는 *FastAPI and friends* 오픈 소스 프로젝트의 주요 후원자이자 자금 제공자입니다. ✨
+
+## 클라우드 제공업체 - 후원자들 { #cloud-providers-sponsors }
+
+다른 몇몇 클라우드 제공업체들도 ✨ [**FastAPI를 후원합니다**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨. 🙇
+
+가이드를 따라 하고 서비스를 사용해보기 위해 이들도 고려해볼 수 있습니다:
+
+* Render
+* Railway
diff --git a/docs/ko/docs/deployment/docker.md b/docs/ko/docs/deployment/docker.md
index e8b2746c5f..be04c923a5 100644
--- a/docs/ko/docs/deployment/docker.md
+++ b/docs/ko/docs/deployment/docker.md
@@ -1,17 +1,17 @@
-# 컨테이너의 FastAPI - 도커
+# 컨테이너의 FastAPI - 도커 { #fastapi-in-containers-docker }
-FastAPI 어플리케이션을 배포할 때 일반적인 접근 방법은 **리눅스 컨테이너 이미지**를 생성하는 것입니다. 이 방법은 주로 **도커**를 사용해 이루어집니다. 그런 다음 해당 컨테이너 이미지를 몇가지 방법으로 배포할 수 있습니다.
+FastAPI 애플리케이션을 배포할 때 일반적인 접근 방법은 **리눅스 컨테이너 이미지**를 빌드하는 것입니다. 보통 **Docker**를 사용해 수행합니다. 그런 다음 해당 컨테이너 이미지를 몇 가지 가능한 방법 중 하나로 배포할 수 있습니다.
-리눅스 컨테이너를 사용하는 데에는 **보안**, **반복 가능성**, **단순함** 등의 장점이 있습니다.
+리눅스 컨테이너를 사용하면 **보안**, **재현 가능성**, **단순함** 등 여러 장점이 있습니다.
/// tip | 팁
-시간에 쫓기고 있고 이미 이런것들을 알고 있다면 [`Dockerfile`👇](#build-a-docker-image-for-fastapi)로 점프할 수 있습니다.
+시간이 없고 이미 이런 내용들을 알고 계신가요? 아래의 [`Dockerfile` 👇](#build-a-docker-image-for-fastapi)로 이동하세요.
///
-## 테마 변경
+## 테마 변경 { #change-the-theme }
동일한 방식으로 `"syntaxHighlight.theme"` 키를 사용하여 구문 강조 테마를 설정할 수 있습니다 (중간에 점이 포함된 것을 참고하십시오).
-{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *}
+{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *}
이 설정은 구문 강조 색상 테마를 변경합니다:
-## 기본 Swagger UI 매개변수 변경
+## 기본 Swagger UI 매개변수 변경 { #change-default-swagger-ui-parameters }
FastAPI는 대부분의 사용 사례에 적합한 몇 가지 기본 구성 매개변수를 포함하고 있습니다.
기본 구성에는 다음이 포함됩니다:
-{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *}
+{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *}
`swagger_ui_parameters` 인수에 다른 값을 설정하여 이러한 기본값 중 일부를 재정의할 수 있습니다.
예를 들어, `deepLinking`을 비활성화하려면 `swagger_ui_parameters`에 다음 설정을 전달할 수 있습니다:
-{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *}
+{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *}
-## 기타 Swagger UI 매개변수
+## 기타 Swagger UI 매개변수 { #other-swagger-ui-parameters }
-사용할 수 있는 다른 모든 구성 옵션을 확인하려면, Swagger UI 매개변수에 대한 공식 문서를 참조하십시오.
+사용할 수 있는 다른 모든 구성 옵션을 확인하려면, Swagger UI 매개변수에 대한 공식 Swagger UI 매개변수 문서를 참조하십시오.
-## JavaScript 전용 설정
+## JavaScript 전용 설정 { #javascript-only-settings }
Swagger UI는 **JavaScript 전용** 객체(예: JavaScript 함수)로 다른 구성을 허용하기도 합니다.
@@ -67,4 +67,4 @@ presets: [
이들은 문자열이 아닌 **JavaScript** 객체이므로 Python 코드에서 직접 전달할 수 없습니다.
-이와 같은 JavaScript 전용 구성을 사용해야 하는 경우, 위의 방법 중 하나를 사용하여 모든 Swagger UI 경로 작업을 재정의하고 필요한 JavaScript를 수동으로 작성할 수 있습니다.
+이와 같은 JavaScript 전용 구성을 사용해야 하는 경우, 위의 방법 중 하나를 사용할 수 있습니다. Swagger UI *경로 처리*를 모두 재정의하고 필요한 JavaScript를 수동으로 작성하세요.
diff --git a/docs/ko/docs/index.md b/docs/ko/docs/index.md
index b6b4765dad..0c317f296d 100644
--- a/docs/ko/docs/index.md
+++ b/docs/ko/docs/index.md
@@ -1,11 +1,11 @@
-# FastAPI
+# FastAPI { #fastapi }
FastAPI 프레임워크, 고성능, 간편한 학습, 빠른 코드 작성, 준비된 프로덕션
@@ -27,7 +27,7 @@
---
-**문서**: https://fastapi.tiangolo.com
+**문서**: https://fastapi.tiangolo.com
**소스 코드**: https://github.com/fastapi/fastapi
@@ -37,36 +37,41 @@ FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트
주요 특징으로:
-* **빠름**: (Starlette과 Pydantic 덕분에) **NodeJS** 및 **Go**와 대등할 정도로 매우 높은 성능. [사용 가능한 가장 빠른 파이썬 프레임워크 중 하나](#_11).
-
+* **빠름**: (Starlette과 Pydantic 덕분에) **NodeJS** 및 **Go**와 대등할 정도로 매우 높은 성능. [사용 가능한 가장 빠른 파이썬 프레임워크 중 하나](#performance).
* **빠른 코드 작성**: 약 200%에서 300%까지 기능 개발 속도 증가. *
* **적은 버그**: 사람(개발자)에 의한 에러 약 40% 감소. *
* **직관적**: 훌륭한 편집기 지원. 모든 곳에서 자동완성. 적은 디버깅 시간.
* **쉬움**: 쉽게 사용하고 배우도록 설계. 적은 문서 읽기 시간.
* **짧음**: 코드 중복 최소화. 각 매개변수 선언의 여러 기능. 적은 버그.
* **견고함**: 준비된 프로덕션 용 코드를 얻으십시오. 자동 대화형 문서와 함께.
-* **표준 기반**: API에 대한 (완전히 호환되는) 개방형 표준 기반: OpenAPI (이전에 Swagger로 알려졌던) 및 JSON 스키마.
+* **표준 기반**: API에 대한 (완전히 호환되는) 개방형 표준 기반: OpenAPI (이전에 Swagger로 알려졌던) 및 JSON Schema.
* 내부 개발팀의 프로덕션 애플리케이션을 빌드한 테스트에 근거한 측정
-## 골드 스폰서
+## 스폰서 { #sponsors }
-{% if sponsors %}
+### 키스톤 스폰서 { #keystone-sponsor }
+
+{% for sponsor in sponsors.keystone -%}
+
+{% endfor -%}
+
+### 골드 및 실버 스폰서 { #gold-and-silver-sponsors }
+
{% for sponsor in sponsors.gold -%}
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
{% endfor %}
-{% endif %}
-다른 스폰서
+다른 스폰서
-## 의견들
+## 의견들 { #opinions }
"_[...] 저는 요즘 **FastAPI**를 많이 사용하고 있습니다. [...] 사실 우리 팀의 **마이크로소프트 ML 서비스** 전부를 바꿀 계획입니다. 그중 일부는 핵심 **Windows**와 몇몇의 **Office** 제품들이 통합되고 있습니다._"
@@ -94,7 +99,7 @@ FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트
"_솔직히, 당신이 만든 것은 매우 견고하고 세련되어 보입니다. 여러 면에서 **Hug**가 이렇게 되었으면 합니다 - 그걸 만든 누군가를 보는 것은 많은 영감을 줍니다._"
-
+
+## **Typer**, CLI를 위한 FastAPI { #typer-the-fastapi-of-clis }
async def 사용하기...uvicorn main:app --reload 명령에 관하여...fastapi dev main.py 명령에 관하여...HTTPX - `TestClient`를 사용하려면 필요.
-* jinja2 - 기본 템플릿 설정을 사용하려면 필요.
-* python-multipart - `request.form()`과 함께 "parsing"의 지원을 원하면 필요.
-* itsdangerous - `SessionMiddleware` 지원을 위해 필요.
-* pyyaml - Starlette의 `SchemaGenerator` 지원을 위해 필요 (FastAPI와 쓸때는 필요 없을 것입니다).
-* graphene - `GraphQLApp` 지원을 위해 필요.
+* httpx - `TestClient`를 사용하려면 필요.
+* jinja2 - 기본 템플릿 설정을 사용하려면 필요.
+* python-multipart - `request.form()`과 함께 form "parsing" 지원을 원하면 필요.
-FastAPI / Starlette이 사용하는:
+FastAPI가 사용하는:
-* uvicorn - 애플리케이션을 로드하고 제공하는 서버.
-* orjson - `ORJSONResponse`을 사용하려면 필요.
+* uvicorn - 애플리케이션을 로드하고 제공하는 서버를 위한 것입니다. 여기에는 고성능 서빙에 필요한 일부 의존성(예: `uvloop`)이 포함된 `uvicorn[standard]`가 포함됩니다.
+* `fastapi-cli[standard]` - `fastapi` 명령을 제공하기 위한 것입니다.
+ * 여기에는 FastAPI 애플리케이션을 FastAPI Cloud에 배포할 수 있게 해주는 `fastapi-cloud-cli`가 포함됩니다.
+
+### `standard` 의존성 없이 { #without-standard-dependencies }
+
+`standard` 선택적 의존성을 포함하고 싶지 않다면, `pip install "fastapi[standard]"` 대신 `pip install fastapi`로 설치할 수 있습니다.
+
+### `fastapi-cloud-cli` 없이 { #without-fastapi-cloud-cli }
+
+표준 의존성과 함께 FastAPI를 설치하되 `fastapi-cloud-cli` 없이 설치하고 싶다면, `pip install "fastapi[standard-no-fastapi-cloud-cli]"`로 설치할 수 있습니다.
+
+### 추가 선택적 의존성 { #additional-optional-dependencies }
+
+추가로 설치하고 싶을 수 있는 의존성도 있습니다.
+
+추가 선택적 Pydantic 의존성:
+
+* pydantic-settings - 설정 관리를 위한 것입니다.
+* pydantic-extra-types - Pydantic에서 사용할 추가 타입을 위한 것입니다.
+
+추가 선택적 FastAPI 의존성:
+
+* orjson - `ORJSONResponse`를 사용하려면 필요.
* ujson - `UJSONResponse`를 사용하려면 필요.
-`pip install fastapi[all]`를 통해 이 모두를 설치 할 수 있습니다.
-
-## 라이센스
+## 라이센스 { #license }
이 프로젝트는 MIT 라이센스 조약에 따라 라이센스가 부여됩니다.
diff --git a/docs/ko/docs/learn/index.md b/docs/ko/docs/learn/index.md
index 7ac3a99b64..0b4d14ff47 100644
--- a/docs/ko/docs/learn/index.md
+++ b/docs/ko/docs/learn/index.md
@@ -1,5 +1,5 @@
-# 배우기
+# 배우기 { #learn }
-여기 **FastAPI**를 배우기 위한 입문 자료와 자습서가 있습니다.
+여기 **FastAPI**를 배우기 위한 입문 섹션과 자습서가 있습니다.
-여러분은 FastAPI를 배우기 위해 **책**, **강의**, **공식 자료** 그리고 추천받은 방법을 고려할 수 있습니다. 😎
+여러분은 이것을 FastAPI를 배우기 위한 **책**, **강의**, **공식**이자 권장되는 방법으로 생각할 수 있습니다. 😎
diff --git a/docs/ko/docs/openapi-webhooks.md b/docs/ko/docs/openapi-webhooks.md
deleted file mode 100644
index 96339aa961..0000000000
--- a/docs/ko/docs/openapi-webhooks.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# OpenAPI 웹훅(Webhooks)
-
-API **사용자**에게 특정 **이벤트**가 발생할 때 *그들*의 앱(시스템)에 요청을 보내 **알림**을 전달할 수 있다는 것을 알리고 싶은 경우가 있습니다.
-
-즉, 일반적으로 사용자가 API에 요청을 보내는 것과는 반대로, **API**(또는 앱)가 **사용자의 시스템**(그들의 API나 앱)으로 **요청을 보내는** 상황을 의미합니다.
-
-이를 흔히 **웹훅(Webhook)**이라고 부릅니다.
-
-## 웹훅 스텝
-
-**코드에서** 웹훅으로 보낼 메시지, 즉 요청의 **바디(body)**를 정의하는 것이 일반적인 프로세스입니다.
-
-앱에서 해당 요청이나 이벤트를 전송할 **시점**을 정의합니다.
-
-**사용자**는 앱이 해당 요청을 보낼 **URL**을 정의합니다. (예: 웹 대시보드에서 설정)
-
-웹훅의 URL을 등록하는 방법과 이러한 요청을 실제로 전송하는 코드에 대한 모든 로직은 여러분에게 달려 있습니다. 원하는대로 **고유의 코드**를 작성하면 됩니다.
-
-## **FastAPI**와 OpenAPI로 웹훅 문서화하기
-
-**FastAPI**를 사용하여 OpenAPI와 함께 웹훅의 이름, 앱이 보낼 수 있는 HTTP 작업 유형(예: `POST`, `PUT` 등), 그리고 보낼 요청의 **바디**를 정의할 수 있습니다.
-
-이를 통해 사용자가 **웹훅** 요청을 수신할 **API 구현**을 훨씬 쉽게 할 수 있으며, 경우에 따라 사용자 API 코드의 일부를 자동 생성할 수도 있습니다.
-
-/// info
-
-웹훅은 OpenAPI 3.1.0 이상에서 지원되며, FastAPI `0.99.0` 이상 버전에서 사용할 수 있습니다.
-
-///
-
-## 웹훅이 포함된 앱 만들기
-
-**FastAPI** 애플리케이션을 만들 때, `webhooks` 속성을 사용하여 *웹훅*을 정의할 수 있습니다. 이는 `@app.webhooks.post()`와 같은 방식으로 *경로(path) 작업*을 정의하는 것과 비슷합니다.
-
-{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *}
-
-이렇게 정의한 웹훅은 **OpenAPI** 스키마와 자동 **문서화 UI**에 표시됩니다.
-
-/// info
-
-`app.webhooks` 객체는 사실 `APIRouter`일 뿐이며, 여러 파일로 앱을 구성할 때 사용하는 것과 동일한 타입입니다.
-
-///
-
-웹훅에서는 실제 **경로(path)** (예: `/items/`)를 선언하지 않는 점에 유의해야 합니다. 여기서 전달하는 텍스트는 **식별자**로, 웹훅의 이름(이벤트 이름)입니다. 예를 들어, `@app.webhooks.post("new-subscription")`에서 웹훅 이름은 `new-subscription`입니다.
-
-이는 실제 **URL 경로**는 **사용자**가 다른 방법(예: 웹 대시보드)을 통해 지정하도록 기대되기 때문입니다.
-
-### 문서 확인하기
-
-이제 앱을 시작하고 http://127.0.0.1:8000/docs로 이동해 봅시다.
-
-문서에서 기존 *경로 작업*뿐만 아니라 **웹훅**도 표시된 것을 확인할 수 있습니다:
-
-
diff --git a/docs/ko/docs/project-generation.md b/docs/ko/docs/project-generation.md
index dd11fca703..73ea67d3e5 100644
--- a/docs/ko/docs/project-generation.md
+++ b/docs/ko/docs/project-generation.md
@@ -1,4 +1,4 @@
-# Full Stack FastAPI 템플릿
+# Full Stack FastAPI 템플릿 { #full-stack-fastapi-template }
템플릿은 일반적으로 특정 설정과 함께 제공되지만, 유연하고 커스터마이징이 가능하게 디자인 되었습니다. 이 특성들은 여러분이 프로젝트의 요구사항에 맞춰 수정, 적용을 할 수 있게 해주고, 템플릿이 완벽한 시작점이 되게 해줍니다. 🏁
@@ -6,23 +6,23 @@
GitHub 저장소: Full Stack FastAPI 템플릿
-## Full Stack FastAPI 템플릿 - 기술 스택과 기능들
+## Full Stack FastAPI 템플릿 - 기술 스택과 기능들 { #full-stack-fastapi-template-technology-stack-and-features }
-- ⚡ [**FastAPI**](https://fastapi.tiangolo.com): Python 백엔드 API.
- - 🧰 [SQLModel](https://sqlmodel.tiangolo.com): Python SQL 데이터 상호작용을 위한 (ORM).
- - 🔍 [Pydantic](https://docs.pydantic.dev): FastAPI에 의해 사용되는, 데이터 검증과 설정관리.
- - 💾 [PostgreSQL](https://www.postgresql.org): SQL 데이터베이스.
-- 🚀 [React](https://react.dev): 프론트엔드.
- - 💃 TypeScript, hooks, [Vite](https://vitejs.dev) 및 기타 현대적인 프론트엔드 스택을 사용.
- - 🎨 [Chakra UI](https://chakra-ui.com): 프론트엔드 컴포넌트.
+- ⚡ Python 백엔드 API를 위한 [**FastAPI**](https://fastapi.tiangolo.com/ko).
+ - 🧰 Python SQL 데이터베이스 상호작용을 위한 [SQLModel](https://sqlmodel.tiangolo.com) (ORM).
+ - 🔍 FastAPI에 의해 사용되는, 데이터 검증과 설정 관리를 위한 [Pydantic](https://docs.pydantic.dev).
+ - 💾 SQL 데이터베이스로서의 [PostgreSQL](https://www.postgresql.org).
+- 🚀 프론트엔드를 위한 [React](https://react.dev).
+ - 💃 TypeScript, hooks, Vite 및 기타 현대적인 프론트엔드 스택을 사용.
+ - 🎨 프론트엔드 컴포넌트를 위한 [Tailwind CSS](https://tailwindcss.com) 및 [shadcn/ui](https://ui.shadcn.com).
- 🤖 자동으로 생성된 프론트엔드 클라이언트.
- - 🧪 E2E 테스트를 위한 [Playwright](https://playwright.dev).
+ - 🧪 End-to-End 테스트를 위한 [Playwright](https://playwright.dev).
- 🦇 다크 모드 지원.
-- 🐋 [Docker Compose](https://www.docker.com): 개발 환경과 프로덕션(운영).
+- 🐋 개발 환경과 프로덕션(운영)을 위한 [Docker Compose](https://www.docker.com).
- 🔒 기본으로 지원되는 안전한 비밀번호 해싱.
-- 🔑 JWT 토큰 인증.
+- 🔑 JWT (JSON Web Token) 인증.
- 📫 이메일 기반 비밀번호 복구.
-- ✅ [Pytest]를 이용한 테스트(https://pytest.org).
-- 📞 [Traefik](https://traefik.io): 리버스 프록시 / 로드 밸런서.
+- ✅ [Pytest](https://pytest.org)를 이용한 테스트.
+- 📞 리버스 프록시 / 로드 밸런서로서의 [Traefik](https://traefik.io).
- 🚢 Docker Compose를 이용한 배포 지침: 자동 HTTPS 인증서를 처리하기 위한 프론트엔드 Traefik 프록시 설정 방법을 포함.
- 🏭 GitHub Actions를 기반으로 CI (지속적인 통합) 및 CD (지속적인 배포).
diff --git a/docs/ko/docs/python-types.md b/docs/ko/docs/python-types.md
index 18d4b341e4..dc264df805 100644
--- a/docs/ko/docs/python-types.md
+++ b/docs/ko/docs/python-types.md
@@ -1,313 +1,466 @@
-# 파이썬 타입 소개
+# 파이썬 타입 소개 { #python-types-intro }
-파이썬은 선택적으로 "타입 힌트(type hints)"를 지원합니다.
+파이썬은 선택적으로 "타입 힌트(type hints)"(“type annotations”라고도 함)를 지원합니다.
-이러한 **타입 힌트**들은 변수의 타입을 선언할 수 있게 해주는 특수한 구문입니다.
+이러한 **"타입 힌트"** 또는 애너테이션은 변수의 타입을 선언할 수 있게 해주는 특수한 구문입니다.
-변수의 타입을 지정하면 에디터와 툴이 더 많은 도움을 줄 수 있게 됩니다.
+변수의 타입을 선언하면 에디터와 도구가 더 나은 지원을 제공할 수 있습니다.
-이 문서는 파이썬 타입 힌트에 대한 **빠른 자습서 / 내용환기** 수준의 문서입니다. 여기서는 **FastAPI**를 쓰기 위한 최소한의 내용만을 다룹니다.
+이 문서는 파이썬 타입 힌트에 대한 **빠른 자습서 / 내용 환기**입니다. **FastAPI**와 함께 사용하기 위해 필요한 최소한만 다룹니다... 실제로는 아주 조금만 있으면 됩니다.
-**FastAPI**는 타입 힌트에 기반을 두고 있으며, 이는 많은 장점과 이익이 있습니다.
+**FastAPI**는 모두 이러한 타입 힌트에 기반을 두고 있으며, 이는 많은 장점과 이점을 제공합니다.
-비록 **FastAPI**를 쓰지 않는다고 하더라도, 조금이라도 알아두면 도움이 될 것입니다.
+하지만 **FastAPI**를 전혀 사용하지 않더라도, 타입 힌트를 조금만 배워도 도움이 됩니다.
/// note | 참고
-파이썬에 능숙하셔서 타입 힌트에 대해 모두 아신다면, 다음 챕터로 건너뛰세요.
+파이썬에 능숙하고 타입 힌트에 대해 이미 모두 알고 있다면, 다음 장으로 건너뛰세요.
///
-## 동기 부여
+## 동기 부여 { #motivation }
-간단한 예제부터 시작해봅시다:
+간단한 예제로 시작해봅시다:
-{* ../../docs_src/python_types/tutorial001.py *}
+{* ../../docs_src/python_types/tutorial001_py39.py *}
-
-이 프로그램을 실행한 결과값:
+이 프로그램을 호출하면 다음이 출력됩니다:
```
John Doe
```
-함수는 아래와 같이 실행됩니다:
+이 함수는 다음을 수행합니다:
* `first_name`과 `last_name`를 받습니다.
-* `title()`로 각 첫 문자를 대문자로 변환시킵니다.
-* 두 단어를 중간에 공백을 두고 연결합니다.
+* `title()`로 각각의 첫 글자를 대문자로 변환합니다.
+* 가운데에 공백을 두고 연결합니다.
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
+{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
+### 수정하기 { #edit-it }
-### 코드 수정
+매우 간단한 프로그램입니다.
-이건 매우 간단한 프로그램입니다.
+하지만 이제, 이것을 처음부터 작성한다고 상상해봅시다.
-그런데 처음부터 작성한다고 생각을 해봅시다.
+어느 시점엔 함수를 정의하기 시작했고, 매개변수도 준비해두었을 겁니다...
-여러분은 매개변수를 준비했고, 함수를 정의하기 시작했을 겁니다.
+그런데 "첫 글자를 대문자로 변환하는 그 메서드"를 호출해야 합니다.
-이때 "첫 글자를 대문자로 바꾸는 함수"를 호출해야 합니다.
+`upper`였나요? `uppercase`였나요? `first_uppercase`? `capitalize`?
-`upper`였나? 아니면 `uppercase`? `first_uppercase`? `capitalize`?
+그 다음, 개발자들의 오랜 친구인 에디터 자동완성을 시도합니다.
-그때 개발자들의 오랜 친구, 에디터 자동완성을 시도해봅니다.
+함수의 첫 번째 매개변수인 `first_name`을 입력하고, 점(`.`)을 찍은 다음, 완성을 트리거하기 위해 `Ctrl+Space`를 누릅니다.
-당신은 `first_name`를 입력한 뒤 점(`.`)을 입력하고 자동완성을 켜기 위해서 `Ctrl+Space`를 눌렀습니다.
-
-하지만 슬프게도 아무런 도움이 되지 않습니다:
+하지만, 슬프게도 쓸만한 게 아무것도 없습니다:
-### 타입 추가하기
+### 타입 추가하기 { #add-types }
이전 버전에서 한 줄만 수정해봅시다.
-저희는 이 함수의 매개변수 부분:
+함수의 매개변수인 정확히 이 부분을:
```Python
first_name, last_name
```
-을 아래와 같이 바꿀 겁니다:
+에서:
```Python
first_name: str, last_name: str
```
+로 바꾸겠습니다.
+
이게 다입니다.
-이게 "타입 힌트"입니다:
+이것들이 "타입 힌트"입니다:
-{* ../../docs_src/python_types/tutorial002.py hl[1] *}
+{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
-
-타입힌트는 다음과 같이 기본 값을 선언하는 것과는 다릅니다:
+이것은 다음처럼 기본값을 선언하는 것과는 다릅니다:
```Python
first_name="john", last_name="doe"
```
-이는 다른 것입니다.
+다른 것입니다.
-등호(`=`) 대신 콜론(`:`)을 쓰고 있습니다.
+등호(`=`)가 아니라 콜론(`:`)을 사용합니다.
-일반적으로 타입힌트를 추가한다고 해서 특별하게 어떤 일이 일어나지도 않습니다.
+그리고 보통 타입 힌트를 추가해도, 타입 힌트 없이 일어나는 일과 비교해 특별히 달라지는 것은 없습니다.
-그렇지만 이제, 다시 함수를 만드는 도중이라고 생각해봅시다. 다만 이번엔 타입 힌트가 있습니다.
+하지만 이제, 타입 힌트를 포함해 그 함수를 다시 만드는 중이라고 상상해봅시다.
-같은 상황에서 `Ctrl+Space`로 자동완성을 작동시키면,
+같은 지점에서 `Ctrl+Space`로 자동완성을 트리거하면 다음이 보입니다:
-아래와 같이 "그렇지!"하는 옵션이 나올때까지 스크롤을 내려서 볼 수 있습니다:
+그러면 스크롤하며 옵션을 보다가, "기억나는" 것을 찾을 수 있습니다:
-## 더 큰 동기부여
+## 더 큰 동기부여 { #more-motivation }
-아래 함수를 보면, 이미 타입 힌트가 적용되어 있는 걸 볼 수 있습니다:
+이 함수를 확인해보세요. 이미 타입 힌트가 있습니다:
-{* ../../docs_src/python_types/tutorial003.py hl[1] *}
+{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}
-
-편집기가 변수의 타입을 알고 있기 때문에, 자동완성 뿐 아니라 에러도 확인할 수 있습니다:
+에디터가 변수의 타입을 알고 있기 때문에, 자동완성만 되는 게 아니라 오류 검사도 할 수 있습니다:
-이제 고쳐야하는 걸 알기 때문에, `age`를 `str(age)`과 같이 문자열로 바꾸게 됩니다:
+이제 고쳐야 한다는 것을 알고, `age`를 `str(age)`로 문자열로 바꿉니다:
-{* ../../docs_src/python_types/tutorial004.py hl[2] *}
+{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
+## 타입 선언 { #declaring-types }
-## 타입 선언
+방금 타입 힌트를 선언하는 주요 위치를 보았습니다. 함수 매개변수입니다.
-방금 함수의 매개변수로써 타입 힌트를 선언하는 주요 장소를 보았습니다.
+이것은 **FastAPI**와 함께 사용할 때도 주요 위치입니다.
-이 위치는 여러분이 **FastAPI**와 함께 이를 사용하는 주요 장소입니다.
-
-### Simple 타입
+### Simple 타입 { #simple-types }
`str`뿐 아니라 모든 파이썬 표준 타입을 선언할 수 있습니다.
-예를 들면:
+예를 들어 다음을 사용할 수 있습니다:
* `int`
* `float`
* `bool`
* `bytes`
-{* ../../docs_src/python_types/tutorial005.py hl[1] *}
+{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
+### 타입 매개변수가 있는 Generic(제네릭) 타입 { #generic-types-with-type-parameters }
-### 타입 매개변수를 활용한 Generic(제네릭) 타입
+`dict`, `list`, `set`, `tuple`처럼 다른 값을 담을 수 있는 데이터 구조가 있습니다. 그리고 내부 값에도 각자의 타입이 있을 수 있습니다.
-`dict`, `list`, `set`, `tuple`과 같은 값을 저장할 수 있는 데이터 구조가 있고, 내부의 값은 각자의 타입을 가질 수도 있습니다.
+이렇게 내부 타입을 가지는 타입을 "**generic**" 타입이라고 부릅니다. 그리고 내부 타입까지 포함해 선언할 수도 있습니다.
-타입과 내부 타입을 선언하기 위해서는 파이썬 표준 모듈인 `typing`을 이용해야 합니다.
+이런 타입과 내부 타입을 선언하려면 표준 파이썬 모듈 `typing`을 사용할 수 있습니다. 이 모듈은 이러한 타입 힌트를 지원하기 위해 존재합니다.
-구체적으로는 아래 타입 힌트를 지원합니다.
+#### 더 최신 버전의 Python { #newer-versions-of-python }
-#### `List`
+`typing`을 사용하는 문법은 Python 3.6부터 최신 버전까지, Python 3.9, Python 3.10 등을 포함한 모든 버전과 **호환**됩니다.
-예를 들면, `str`의 `list`인 변수를 정의해봅시다.
+파이썬이 발전함에 따라 **더 최신 버전**에서는 이러한 타입 애너테이션 지원이 개선되며, 많은 경우 타입 애너테이션을 선언하기 위해 `typing` 모듈을 import해서 사용할 필요조차 없게 됩니다.
-`typing`에서 `List`(대문자 `L`)를 import 합니다.
+프로젝트에서 더 최신 버전의 파이썬을 선택할 수 있다면, 그 추가적인 단순함을 활용할 수 있습니다.
-{* ../../docs_src/python_types/tutorial006.py hl[1] *}
+이 문서 전체에는 각 파이썬 버전과 호환되는 예제가 있습니다(차이가 있을 때).
+예를 들어 "**Python 3.6+**"는 Python 3.6 이상(3.7, 3.8, 3.9, 3.10 등 포함)과 호환된다는 뜻입니다. 그리고 "**Python 3.9+**"는 Python 3.9 이상(3.10 등 포함)과 호환된다는 뜻입니다.
-콜론(`:`) 문법을 이용하여 변수를 선언합니다.
+**최신 버전의 Python**을 사용할 수 있다면, 최신 버전용 예제를 사용하세요. 예를 들어 "**Python 3.10+**"처럼, 가장 **좋고 가장 단순한 문법**을 갖게 됩니다.
-타입으로는 `List`를 넣어줍니다.
+#### List { #list }
-이때 배열은 내부 타입을 포함하는 타입이기 때문에 대괄호 안에 넣어줍니다.
+예를 들어, `str`의 `list`인 변수를 정의해봅시다.
-{* ../../docs_src/python_types/tutorial006.py hl[4] *}
+같은 콜론(`:`) 문법으로 변수를 선언합니다.
+타입으로 `list`를 넣습니다.
-/// tip | 팁
-
-대괄호 안의 내부 타입은 "타입 매개변수(type paramters)"라고 합니다.
-
-이번 예제에서는 `str`이 `List`에 들어간 타입 매개변수 입니다.
-
-///
-
-이는 "`items`은 `list`인데, 배열에 들어있는 아이템 각각은 `str`이다"라는 뜻입니다.
-
-이렇게 함으로써, 에디터는 배열에 들어있는 아이템을 처리할때도 도움을 줄 수 있게 됩니다:
-
-
-
-타입이 없으면 이건 거의 불가능이나 다름 없습니다.
-
-변수 `item`은 `items`의 개별 요소라는 사실을 알아두세요.
-
-그리고 에디터는 계속 `str`라는 사실을 알고 도와줍니다.
-
-#### `Tuple`과 `Set`
-
-`tuple`과 `set`도 동일하게 선언할 수 있습니다.
-
-{* ../../docs_src/python_types/tutorial007.py hl[1,4] *}
-
-
-이 뜻은 아래와 같습니다:
-
-* 변수 `items_t`는, 차례대로 `int`, `int`, `str`인 `tuple`이다.
-* 변수 `items_s`는, 각 아이템이 `bytes`인 `set`이다.
-
-#### `Dict`
-
-`dict`를 선언하려면 컴마로 구분된 2개의 파라미터가 필요합니다.
-
-첫 번째 매개변수는 `dict`의 키(key)이고,
-
-두 번째 매개변수는 `dict`의 값(value)입니다.
-
-{* ../../docs_src/python_types/tutorial008.py hl[1,4] *}
-
-
-이 뜻은 아래와 같습니다:
-
-* 변수 `prices`는 `dict`이다:
- * `dict`의 키(key)는 `str`타입이다. (각 아이템의 이름(name))
- * `dict`의 값(value)는 `float`타입이다. (각 아이템의 가격(price))
-
-#### `Optional`
-
-`str`과 같이 타입을 선언할 때 `Optional`을 쓸 수도 있는데, "선택적(Optional)"이기때문에 `None`도 될 수 있습니다:
-
-```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
-```
-
-`Optional[str]`을 `str` 대신 쓰게 되면, 특정 값이 실제로는 `None`이 될 수도 있는데 항상 `str`이라고 가정하는 상황에서 에디터가 에러를 찾게 도와줄 수 있습니다.
-
-#### Generic(제네릭) 타입
-
-이 타입은 대괄호 안에 매개변수를 가지며, 종류는:
-
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Optional`
-* ...등등
-
-위와 같은 타입은 **Generic(제네릭) 타입** 혹은 **Generics(제네릭스)**라고 불립니다.
-
-### 타입으로서의 클래스
-
-변수의 타입으로 클래스를 선언할 수도 있습니다.
-
-이름(name)을 가진 `Person` 클래스가 있다고 해봅시다.
-
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
-
-
-그렇게 하면 변수를 `Person`이라고 선언할 수 있게 됩니다.
-
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
-
-
-그리고 역시나 모든 에디터 도움을 받게 되겠죠.
-
-
-
-## Pydantic 모델
-
-Pydantic은 데이터 검증(Validation)을 위한 파이썬 라이브러리입니다.
-
-당신은 속성들을 포함한 클래스 형태로 "모양(shape)"을 선언할 수 있습니다.
-
-그리고 각 속성은 타입을 가지고 있습니다.
-
-이 클래스를 활용하여서 값을 가지고 있는 인스턴스를 만들게 되면, 필요한 경우에는 적당한 타입으로 변환까지 시키기도 하여 데이터가 포함된 객체를 반환합니다.
-
-그리고 결과 객체에 대해서는 에디터의 도움을 받을 수 있게 됩니다.
-
-Pydantic 공식 문서 예시:
-
-{* ../../docs_src/python_types/tutorial011.py *}
+`list`는 내부 타입을 포함하는 타입이므로, 그 타입들을 대괄호 안에 넣습니다:
+{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
/// info | 정보
-Pydantic<에 대해 더 배우고 싶다면 공식 문서를 참고하세요.
+대괄호 안의 내부 타입은 "type parameters"라고 부릅니다.
+
+이 경우 `str`이 `list`에 전달된 타입 매개변수입니다.
///
-**FastAPI**는 모두 Pydantic을 기반으로 되어 있습니다.
+이는 "변수 `items`는 `list`이고, 이 `list`의 각 아이템은 `str`이다"라는 뜻입니다.
-이 모든 것이 실제로 어떻게 사용되는지에 대해서는 [자습서 - 사용자 안내서](tutorial/index.md){.internal-link target=_blank} 에서 더 많이 확인하실 수 있습니다.
+이렇게 하면, 에디터는 리스트의 아이템을 처리하는 동안에도 지원을 제공할 수 있습니다:
-## **FastAPI**에서의 타입 힌트
+
-**FastAPI**는 여러 부분에서 타입 힌트의 장점을 취하고 있습니다.
+타입이 없으면, 이는 거의 불가능합니다.
-**FastAPI**에서 타입 힌트와 함께 매개변수를 선언하면 장점은:
+변수 `item`이 리스트 `items`의 요소 중 하나라는 점에 주목하세요.
+
+그리고 에디터는 여전히 이것이 `str`임을 알고, 그에 대한 지원을 제공합니다.
+
+#### Tuple과 Set { #tuple-and-set }
+
+`tuple`과 `set`도 동일하게 선언할 수 있습니다:
+
+{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
+
+이는 다음을 의미합니다:
+
+* 변수 `items_t`는 3개의 아이템을 가진 `tuple`이며, `int`, 또 다른 `int`, 그리고 `str`입니다.
+* 변수 `items_s`는 `set`이며, 각 아이템의 타입은 `bytes`입니다.
+
+#### Dict { #dict }
+
+`dict`를 정의하려면, 쉼표로 구분된 2개의 타입 매개변수를 전달합니다.
+
+첫 번째 타입 매개변수는 `dict`의 키를 위한 것입니다.
+
+두 번째 타입 매개변수는 `dict`의 값을 위한 것입니다:
+
+{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
+
+이는 다음을 의미합니다:
+
+* 변수 `prices`는 `dict`입니다:
+ * 이 `dict`의 키는 `str` 타입입니다(예: 각 아이템의 이름).
+ * 이 `dict`의 값은 `float` 타입입니다(예: 각 아이템의 가격).
+
+#### Union { #union }
+
+변수가 **여러 타입 중 어떤 것이든** 될 수 있다고 선언할 수 있습니다. 예를 들어 `int` 또는 `str`입니다.
+
+Python 3.6 이상(3.10 포함)에서는 `typing`의 `Union` 타입을 사용하고, 대괄호 안에 허용할 수 있는 타입들을 넣을 수 있습니다.
+
+Python 3.10에는 가능한 타입들을 세로 막대(`|`)로 구분해 넣을 수 있는 **새 문법**도 있습니다.
+
+//// tab | Python 3.10+
+
+```Python hl_lines="1"
+{!> ../../docs_src/python_types/tutorial008b_py310.py!}
+```
+
+////
+
+//// tab | Python 3.9+
+
+```Python hl_lines="1 4"
+{!> ../../docs_src/python_types/tutorial008b_py39.py!}
+```
+
+////
+
+두 경우 모두 이는 `item`이 `int` 또는 `str`일 수 있다는 뜻입니다.
+
+#### `None`일 수도 있음 { #possibly-none }
+
+값이 `str` 같은 타입일 수도 있지만, `None`일 수도 있다고 선언할 수 있습니다.
+
+Python 3.6 이상(3.10 포함)에서는 `typing` 모듈에서 `Optional`을 import해서 사용하여 선언할 수 있습니다.
+
+```Python hl_lines="1 4"
+{!../../docs_src/python_types/tutorial009_py39.py!}
+```
+
+그냥 `str` 대신 `Optional[str]`을 사용하면, 값이 항상 `str`이라고 가정하고 있지만 실제로는 `None`일 수도 있는 상황에서 에디터가 오류를 감지하도록 도와줍니다.
+
+`Optional[Something]`은 사실 `Union[Something, None]`의 축약이며, 서로 동등합니다.
+
+또한 이는 Python 3.10에서 `Something | None`을 사용할 수 있다는 의미이기도 합니다:
+
+//// tab | Python 3.10+
+
+```Python hl_lines="1"
+{!> ../../docs_src/python_types/tutorial009_py310.py!}
+```
+
+////
+
+//// tab | Python 3.9+
+
+```Python hl_lines="1 4"
+{!> ../../docs_src/python_types/tutorial009_py39.py!}
+```
+
+////
+
+//// tab | Python 3.9+ alternative
+
+```Python hl_lines="1 4"
+{!> ../../docs_src/python_types/tutorial009b_py39.py!}
+```
+
+////
+
+#### `Union` 또는 `Optional` 사용하기 { #using-union-or-optional }
+
+Python 3.10 미만 버전을 사용한다면, 아주 **주관적인** 관점에서의 팁입니다:
+
+* 🚨 `Optional[SomeType]` 사용을 피하세요
+* 대신 ✨ **`Union[SomeType, None]`을 사용하세요** ✨.
+
+둘은 동등하고 내부적으로는 같은 것이지만, `Optional`이라는 단어가 값이 선택 사항인 것처럼 보일 수 있기 때문에 `Optional` 대신 `Union`을 권장합니다. 실제 의미는 값이 선택 사항이라는 뜻이 아니라, "값이 `None`일 수 있다"는 뜻이기 때문입니다. 선택 사항이 아니고 여전히 필수인 경우에도요.
+
+`Union[SomeType, None]`이 의미를 더 명확하게 드러낸다고 생각합니다.
+
+이건 단지 단어와 이름의 문제입니다. 하지만 그런 단어들이 여러분과 팀원이 코드에 대해 생각하는 방식에 영향을 줄 수 있습니다.
+
+예로, 이 함수를 봅시다:
+
+{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
+
+매개변수 `name`은 `Optional[str]`로 정의되어 있지만, **선택 사항이 아닙니다**. 매개변수 없이 함수를 호출할 수 없습니다:
+
+```Python
+say_hi() # Oh, no, this throws an error! 😱
+```
+
+기본값이 없기 때문에 `name` 매개변수는 **여전히 필수입니다**(*optional*이 아님). 그럼에도 `name`은 값으로 `None`을 허용합니다:
+
+```Python
+say_hi(name=None) # This works, None is valid 🎉
+```
+
+좋은 소식은 Python 3.10을 사용하면, 타입의 유니온을 정의하기 위해 간단히 `|`를 사용할 수 있어서 이런 걱정을 할 필요가 없다는 점입니다:
+
+{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
+
+그러면 `Optional`이나 `Union` 같은 이름에 대해 걱정할 필요도 없습니다. 😎
+
+#### Generic(제네릭) 타입 { #generic-types }
+
+대괄호 안에 타입 매개변수를 받는 이러한 타입들은 **Generic types** 또는 **Generics**라고 부릅니다. 예를 들면:
+
+//// tab | Python 3.10+
+
+대괄호와 내부 타입을 사용해, 동일한 내장 타입들을 제네릭으로 사용할 수 있습니다:
+
+* `list`
+* `tuple`
+* `set`
+* `dict`
+
+그리고 이전 파이썬 버전과 마찬가지로 `typing` 모듈의 다음도 사용할 수 있습니다:
+
+* `Union`
+* `Optional`
+* ...그 밖의 것들.
+
+Python 3.10에서는 제네릭 `Union`과 `Optional`을 사용하는 대안으로, 타입 유니온을 선언하기 위해 세로 막대(`|`)를 사용할 수 있는데, 훨씬 더 좋고 단순합니다.
+
+////
+
+//// tab | Python 3.9+
+
+대괄호와 내부 타입을 사용해, 동일한 내장 타입들을 제네릭으로 사용할 수 있습니다:
+
+* `list`
+* `tuple`
+* `set`
+* `dict`
+
+그리고 `typing` 모듈의 제네릭들:
+
+* `Union`
+* `Optional`
+* ...그 밖의 것들.
+
+////
+
+### 타입으로서의 클래스 { #classes-as-types }
+
+변수의 타입으로 클래스를 선언할 수도 있습니다.
+
+이름을 가진 `Person` 클래스가 있다고 해봅시다:
+
+{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
+
+그러면 `Person` 타입의 변수를 선언할 수 있습니다:
+
+{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
+
+그리고 다시, 에디터의 모든 지원을 받을 수 있습니다:
+
+
+
+이는 "`one_person`은 `Person` 클래스의 **인스턴스**"라는 뜻입니다.
+
+"`one_person`은 `Person`이라는 **클래스**다"라는 뜻이 아닙니다.
+
+## Pydantic 모델 { #pydantic-models }
+
+Pydantic은 데이터 검증을 수행하는 파이썬 라이브러리입니다.
+
+속성을 가진 클래스 형태로 데이터의 "모양(shape)"을 선언합니다.
+
+그리고 각 속성은 타입을 가집니다.
+
+그 다음 그 클래스의 인스턴스를 몇 가지 값으로 생성하면, 값들을 검증하고, (그런 경우라면) 적절한 타입으로 변환한 뒤, 모든 데이터를 가진 객체를 제공합니다.
+
+그리고 그 결과 객체에 대해 에디터의 모든 지원을 받을 수 있습니다.
+
+Pydantic 공식 문서의 예시:
+
+{* ../../docs_src/python_types/tutorial011_py310.py *}
+
+/// info | 정보
+
+Pydantic에 대해 더 알아보려면 문서를 확인하세요.
+
+///
+
+**FastAPI**는 모두 Pydantic에 기반을 두고 있습니다.
+
+이 모든 것은 [자습서 - 사용자 안내서](tutorial/index.md){.internal-link target=_blank}에서 실제로 많이 보게 될 것입니다.
+
+/// tip | 팁
+
+Pydantic은 기본값 없이 `Optional` 또는 `Union[Something, None]`을 사용할 때 특별한 동작이 있습니다. 이에 대해서는 Pydantic 문서의 Required Optional fields에서 더 읽을 수 있습니다.
+
+///
+
+## 메타데이터 애너테이션이 있는 타입 힌트 { #type-hints-with-metadata-annotations }
+
+파이썬에는 `Annotated`를 사용해 이러한 타입 힌트에 **추가 메타데이터**를 넣을 수 있는 기능도 있습니다.
+
+Python 3.9부터 `Annotated`는 표준 라이브러리의 일부이므로, `typing`에서 import할 수 있습니다.
+
+{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
+
+파이썬 자체는 이 `Annotated`로 아무것도 하지 않습니다. 그리고 에디터와 다른 도구들에게는 타입이 여전히 `str`입니다.
+
+하지만 `Annotated`의 이 공간을 사용해, 애플리케이션이 어떻게 동작하길 원하는지에 대한 추가 메타데이터를 **FastAPI**에 제공할 수 있습니다.
+
+기억해야 할 중요한 점은 `Annotated`에 전달하는 **첫 번째 *타입 매개변수***가 **실제 타입**이라는 것입니다. 나머지는 다른 도구를 위한 메타데이터일 뿐입니다.
+
+지금은 `Annotated`가 존재하며, 표준 파이썬이라는 것만 알면 됩니다. 😎
+
+나중에 이것이 얼마나 **강력**할 수 있는지 보게 될 것입니다.
+
+/// tip | 팁
+
+이것이 **표준 파이썬**이라는 사실은, 에디터에서 가능한 **최고의 개발자 경험**을 계속 얻을 수 있다는 뜻이기도 합니다. 사용하는 도구로 코드를 분석하고 리팩터링하는 등에서도요. ✨
+
+또한 코드가 많은 다른 파이썬 도구 및 라이브러리와 매우 호환된다는 뜻이기도 합니다. 🚀
+
+///
+
+## **FastAPI**에서의 타입 힌트 { #type-hints-in-fastapi }
+
+**FastAPI**는 이러한 타입 힌트를 활용해 여러 가지를 합니다.
+
+**FastAPI**에서는 타입 힌트로 매개변수를 선언하면 다음을 얻습니다:
* **에디터 도움**.
* **타입 확인**.
-...그리고 **FastAPI**는 같은 정의를 아래에도 적용합니다:
+...그리고 **FastAPI**는 같은 선언을 다음에도 사용합니다:
-* **요구사항 정의**: 요청 경로 매개변수, 쿼리 매개변수, 헤더, 바디, 의존성 등.
-* **데이터 변환**: 요청에서 요구한 타입으로.
-* **데이터 검증**: 각 요청마다:
- * 데이터가 유효하지 않은 경우에는 **자동으로 에러**를 발생합니다.
-* OpenAPI를 활용한 **API 문서화**:
- * 자동으로 상호작용하는 유저 인터페이스에 쓰이게 됩니다.
+* **요구사항 정의**: 요청 경로 매개변수, 쿼리 매개변수, 헤더, 바디, 의존성 등에서.
+* **데이터 변환**: 요청에서 필요한 타입으로.
+* **데이터 검증**: 각 요청에서:
+ * 데이터가 유효하지 않을 때 클라이언트에 반환되는 **자동 오류**를 생성합니다.
+* OpenAPI를 사용해 API를 **문서화**:
+ * 자동 상호작용 문서 UI에서 사용됩니다.
-위 내용이 다소 추상적일 수도 있지만, 걱정마세요. [자습서 - 사용자 안내서](tutorial/index.md){.internal-link target=_blank}에서 전부 확인 가능합니다.
+이 모든 것이 다소 추상적으로 들릴 수도 있습니다. 걱정하지 마세요. [자습서 - 사용자 안내서](tutorial/index.md){.internal-link target=_blank}에서 실제로 확인하게 될 것입니다.
-가장 중요한 건, 표준 파이썬 타입을 한 곳에서(클래스를 더하거나, 데코레이터 사용하는 대신) 사용함으로써 **FastAPI**가 당신을 위해 많은 일을 해준다는 사실이죠.
+가장 중요한 점은 표준 파이썬 타입을 한 곳에서 사용함으로써(더 많은 클래스, 데코레이터 등을 추가하는 대신) **FastAPI**가 여러분을 위해 많은 일을 해준다는 사실입니다.
/// info | 정보
-만약 모든 자습서를 다 보았음에도 타입에 대해서 더 보고자 방문한 경우에는 `mypy`에서 제공하는 "cheat sheet"이 좋은 자료가 될 겁니다.
+자습서를 모두 끝내고 타입에 대해 더 알아보기 위해 다시 돌아왔다면, 좋은 자료로 `mypy`의 "cheat sheet"가 있습니다.
///
diff --git a/docs/ko/docs/resources/index.md b/docs/ko/docs/resources/index.md
index e804dd4d56..477b93a584 100644
--- a/docs/ko/docs/resources/index.md
+++ b/docs/ko/docs/resources/index.md
@@ -1,3 +1,3 @@
-# 리소스
+# 리소스 { #resources }
-추가 리소스, 외부 링크, 기사 등. ✈️
+추가 리소스, 외부 링크 등. ✈️
diff --git a/docs/ko/docs/security/index.md b/docs/ko/docs/security/index.md
deleted file mode 100644
index 5a6c733f05..0000000000
--- a/docs/ko/docs/security/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# 고급 보안
-
-## 추가 기능
-
-[자습서 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank} 문서에서 다룬 내용 외에도 보안 처리를 위한 몇 가지 추가 기능이 있습니다.
-
-/// tip
-
-다음 섹션은 **반드시 "고급"** 기능은 아닙니다.
-
-그리고 여러분의 사용 사례에 따라, 적합한 해결책이 그 중 하나에 있을 가능성이 있습니다.
-
-///
-
-## 먼저 자습서 읽기
-
-다음 섹션은 이미 [자습서 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank} 문서를 읽었다고 가정합니다.
-
-이 섹션들은 모두 동일한 개념을 바탕으로 하며, 추가 기능을 제공합니다.
diff --git a/docs/ko/docs/tutorial/background-tasks.md b/docs/ko/docs/tutorial/background-tasks.md
index 9c4d57481f..9e868f2fa7 100644
--- a/docs/ko/docs/tutorial/background-tasks.md
+++ b/docs/ko/docs/tutorial/background-tasks.md
@@ -1,84 +1,86 @@
-# 백그라운드 작업
+# 백그라운드 작업 { #background-tasks }
-FastAPI에서는 응답을 반환한 후에 실행할 백그라운드 작업을 정의할 수 있습니다.
+FastAPI에서는 응답을 반환한 *후에* 실행할 백그라운드 작업을 정의할 수 있습니다.
-백그라운드 작업은 클라이언트가 응답을 받기 위해 작업이 완료될 때까지 기다릴 필요가 없기 때문에 요청 후에 발생해야하는 작업에 매우 유용합니다.
+백그라운드 작업은 요청 후에 발생해야 하지만, 클라이언트가 응답을 받기 전에 작업이 완료될 때까지 기다릴 필요가 없는 작업에 유용합니다.
-이러한 작업에는 다음이 포함됩니다.
+예를 들면 다음과 같습니다.
-* 작업을 수행한 후 전송되는 이메일 알림
- * 이메일 서버에 연결하고 이메일을 전송하는 것은 (몇 초 정도) "느린" 경향이 있으므로, 응답은 즉시 반환하고 이메일 알림은 백그라운드에서 전송하는 게 가능합니다.
+* 작업을 수행한 후 전송되는 이메일 알림:
+ * 이메일 서버에 연결하고 이메일을 전송하는 것은 (몇 초 정도) "느린" 경향이 있으므로, 응답은 즉시 반환하고 이메일 알림은 백그라운드에서 전송할 수 있습니다.
* 데이터 처리:
- * 예를 들어 처리에 오랜 시간이 걸리는 데이터를 받았을 때 "Accepted" (HTTP 202)을 반환하고, 백그라운드에서 데이터를 처리할 수 있습니다.
+ * 예를 들어 처리에 오랜 시간이 걸리는 프로세스를 거쳐야 하는 파일을 받았다면, "Accepted"(HTTP 202) 응답을 반환하고 백그라운드에서 파일을 처리할 수 있습니다.
-## `백그라운드 작업` 사용
+## `BackgroundTasks` 사용 { #using-backgroundtasks }
-먼저 아래와 같이 `BackgroundTasks`를 임포트하고, `BackgroundTasks`를 _경로 작동 함수_ 에서 매개변수로 가져오고 정의합니다.
+먼저 `BackgroundTasks`를 임포트하고, `BackgroundTasks` 타입 선언으로 *경로 처리 함수*에 매개변수를 정의합니다:
-{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
-**FastAPI** 는 `BackgroundTasks` 개체를 생성하고, 매개 변수로 전달합니다.
+**FastAPI**가 `BackgroundTasks` 타입의 객체를 생성하고 해당 매개변수로 전달합니다.
-## 작업 함수 생성
+## 작업 함수 생성 { #create-a-task-function }
-백그라운드 작업으로 실행할 함수를 정의합니다.
+백그라운드 작업으로 실행할 함수를 생성합니다.
-이것은 단순히 매개변수를 받을 수 있는 표준 함수일 뿐입니다.
+이는 매개변수를 받을 수 있는 표준 함수일 뿐입니다.
-**FastAPI**는 이것이 `async def` 함수이든, 일반 `def` 함수이든 내부적으로 이를 올바르게 처리합니다.
+`async def` 함수일 수도, 일반 `def` 함수일 수도 있으며, **FastAPI**가 이를 올바르게 처리하는 방법을 알고 있습니다.
-이 경우, 아래 작업은 파일에 쓰는 함수입니다. (이메일 보내기 시물레이션)
+이 경우 작업 함수는 파일에 쓰기를 수행합니다(이메일 전송을 시뮬레이션).
-그리고 이 작업은 `async`와 `await`를 사용하지 않으므로 일반 `def` 함수로 선언합니다.
+그리고 쓰기 작업은 `async`와 `await`를 사용하지 않으므로, 일반 `def`로 함수를 정의합니다:
-{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
-## 백그라운드 작업 추가
+## 백그라운드 작업 추가 { #add-the-background-task }
-_경로 작동 함수_ 내에서 작업 함수를 `.add_task()` 함수 통해 _백그라운드 작업_ 개체에 전달합니다.
+*경로 처리 함수* 내부에서 `.add_task()` 메서드로 작업 함수를 *백그라운드 작업* 객체에 전달합니다:
-{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
-`.add_task()` 함수는 다음과 같은 인자를 받습니다 :
+`.add_task()`는 다음 인자를 받습니다:
-- 백그라운드에서 실행되는 작업 함수 (`write_notification`).
-- 작업 함수에 순서대로 전달되어야 하는 일련의 인자 (`email`).
-- 작업 함수에 전달되어야하는 모든 키워드 인자 (`message="some notification"`).
+* 백그라운드에서 실행될 작업 함수(`write_notification`).
+* 작업 함수에 순서대로 전달되어야 하는 인자 시퀀스(`email`).
+* 작업 함수에 전달되어야 하는 키워드 인자(`message="some notification"`).
-## 의존성 주입
+## 의존성 주입 { #dependency-injection }
-`BackgroundTasks`를 의존성 주입 시스템과 함께 사용하면 _경로 작동 함수_, 종속성, 하위 종속성 등 여러 수준에서 BackgroundTasks 유형의 매개변수를 선언할 수 있습니다.
+`BackgroundTasks`는 의존성 주입 시스템에서도 동작하며, *경로 처리 함수*, 의존성(dependable), 하위 의존성 등 여러 수준에서 `BackgroundTasks` 타입의 매개변수를 선언할 수 있습니다.
-**FastAPI**는 각 경우에 수행할 작업과 동일한 개체를 내부적으로 재사용하기에, 모든 백그라운드 작업이 함께 병합되고 나중에 백그라운드에서 실행됩니다.
+**FastAPI**는 각 경우에 무엇을 해야 하는지와 동일한 객체를 어떻게 재사용해야 하는지를 알고 있으므로, 모든 백그라운드 작업이 함께 병합되어 이후 백그라운드에서 실행됩니다:
-{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *}
-이 예제에서는 응답이 반환된 후에 `log.txt` 파일에 메시지가 기록됩니다.
+{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *}
-요청에 쿼리가 있는 경우 백그라운드 작업의 로그에 기록됩니다.
-그리고 _경로 작동 함수_ 에서 생성된 또 다른 백그라운드 작업은 경로 매개 변수를 활용하여 사용하여 메시지를 작성합니다.
+이 예제에서는 응답이 전송된 *후에* 메시지가 `log.txt` 파일에 작성됩니다.
-## 기술적 세부사항
+요청에 쿼리가 있었다면, 백그라운드 작업으로 로그에 작성됩니다.
+
+그 다음 *경로 처리 함수*에서 생성된 또 다른 백그라운드 작업이 `email` 경로 매개변수를 사용해 메시지를 작성합니다.
+
+## 기술적 세부사항 { #technical-details }
`BackgroundTasks` 클래스는 `starlette.background`에서 직접 가져옵니다.
-`BackgroundTasks` 클래스는 FastAPI에서 직접 임포트하거나 포함하기 때문에 실수로 `BackgroundTask` (끝에 `s`가 없음)을 임포트하더라도 starlette.background에서 `BackgroundTask`를 가져오는 것을 방지할 수 있습니다.
+FastAPI에 직접 임포트/포함되어 있으므로 `fastapi`에서 임포트할 수 있고, 실수로 `starlette.background`에서 대안인 `BackgroundTask`(끝에 `s`가 없음)를 임포트하는 것을 피할 수 있습니다.
-(`BackgroundTask`가 아닌) `BackgroundTasks`를 사용하면, _경로 작동 함수_ 매개변수로 사용할 수 있게 되고 나머지는 **FastAPI**가 대신 처리하도록 할 수 있습니다. 이것은 `Request` 객체를 직접 사용하는 것과 같은 방식입니다.
+`BackgroundTask`가 아닌 `BackgroundTasks`만 사용하면, 이를 *경로 처리 함수*의 매개변수로 사용할 수 있고 나머지는 **FastAPI**가 `Request` 객체를 직접 사용할 때처럼 대신 처리해 줍니다.
-FastAPI에서 `BackgroundTask`를 단독으로 사용하는 것은 여전히 가능합니다. 하지만 객체를 코드에서 생성하고, 이 객체를 포함하는 Starlette `Response`를 반환해야 합니다.
+FastAPI에서 `BackgroundTask`만 단독으로 사용하는 것도 가능하지만, 코드에서 객체를 생성하고 이를 포함하는 Starlette `Response`를 반환해야 합니다.
-`Starlette의 공식 문서`에서 백그라운드 작업에 대한 자세한 내용을 확인할 수 있습니다.
+더 자세한 내용은 Starlette의 Background Tasks 공식 문서에서 확인할 수 있습니다.
-## 경고
+## 주의사항 { #caveat }
-만약 무거운 백그라운드 작업을 수행해야하고 동일한 프로세스에서 실행할 필요가 없는 경우 (예: 메모리, 변수 등을 공유할 필요가 없음) `Celery`와 같은 큰 도구를 사용하면 도움이 될 수 있습니다.
+무거운 백그라운드 계산을 수행해야 하고, 반드시 동일한 프로세스에서 실행할 필요가 없다면(예: 메모리, 변수 등을 공유할 필요가 없음) Celery 같은 더 큰 도구를 사용하는 것이 도움이 될 수 있습니다.
-RabbitMQ 또는 Redis와 같은 메시지/작업 큐 시스템 보다 복잡한 구성이 필요한 경향이 있지만, 여러 작업 프로세스를 특히 여러 서버의 백그라운드에서 실행할 수 있습니다.
+이들은 RabbitMQ나 Redis 같은 메시지/작업 큐 관리자 등 더 복잡한 설정을 필요로 하는 경향이 있지만, 여러 프로세스에서, 특히 여러 서버에서 백그라운드 작업을 실행할 수 있습니다.
-그러나 동일한 FastAPI 앱에서 변수 및 개체에 접근해야햐는 작은 백그라운드 수행이 필요한 경우 (예 : 알림 이메일 보내기) 간단하게 `BackgroundTasks`를 사용해보세요.
+하지만 동일한 **FastAPI** 앱의 변수와 객체에 접근해야 하거나(또는 이메일 알림 전송처럼) 작은 백그라운드 작업을 수행해야 한다면, `BackgroundTasks`를 간단히 사용하면 됩니다.
-## 요약
+## 요약 { #recap }
-백그라운드 작업을 추가하기 위해 _경로 작동 함수_ 에 매개변수로 `BackgroundTasks`를 가져오고 사용합니다.
+*경로 처리 함수*와 의존성에서 매개변수로 `BackgroundTasks`를 임포트해 사용하여 백그라운드 작업을 추가합니다.
diff --git a/docs/ko/docs/tutorial/body-fields.md b/docs/ko/docs/tutorial/body-fields.md
index 4708e7099f..c98734ab37 100644
--- a/docs/ko/docs/tutorial/body-fields.md
+++ b/docs/ko/docs/tutorial/body-fields.md
@@ -1,8 +1,8 @@
-# 본문 - 필드
+# 본문 - 필드 { #body-fields }
-`Query`, `Path`와 `Body`를 사용해 *경로 작동 함수* 매개변수 내에서 추가적인 검증이나 메타데이터를 선언한 것처럼 Pydantic의 `Field`를 사용하여 모델 내에서 검증과 메타데이터를 선언할 수 있습니다.
+`Query`, `Path`와 `Body`를 사용해 *경로 처리 함수* 매개변수 내에서 추가적인 검증이나 메타데이터를 선언한 것처럼 Pydantic의 `Field`를 사용하여 모델 내에서 검증과 메타데이터를 선언할 수 있습니다.
-## `Field` 임포트
+## `Field` 임포트 { #import-field }
먼저 이를 임포트해야 합니다:
@@ -14,7 +14,7 @@
///
-## 모델 어트리뷰트 선언
+## 모델 어트리뷰트 선언 { #declare-model-attributes }
그 다음 모델 어트리뷰트와 함께 `Field`를 사용할 수 있습니다:
@@ -22,7 +22,7 @@
`Field`는 `Query`, `Path`와 `Body`와 같은 방식으로 동작하며, 모두 같은 매개변수들 등을 가집니다.
-/// note | 기술적 세부사항
+/// note | 기술 세부사항
실제로 `Query`, `Path`등, 여러분이 앞으로 볼 다른 것들은 공통 클래스인 `Param` 클래스의 서브클래스 객체를 만드는데, 그 자체로 Pydantic의 `FieldInfo` 클래스의 서브클래스입니다.
@@ -36,11 +36,11 @@
/// tip | 팁
-주목할 점은 타입, 기본 값 및 `Field`로 이루어진 각 모델 어트리뷰트가 `Path`, `Query`와 `Body`대신 `Field`를 사용하는 *경로 작동 함수*의 매개변수와 같은 구조를 가진다는 점 입니다.
+주목할 점은 타입, 기본 값 및 `Field`로 이루어진 각 모델 어트리뷰트가 `Path`, `Query`와 `Body`대신 `Field`를 사용하는 *경로 처리 함수*의 매개변수와 같은 구조를 가진다는 점 입니다.
///
-## 별도 정보 추가
+## 별도 정보 추가 { #add-extra-information }
`Field`, `Query`, `Body`, 그 외 안에 별도 정보를 선언할 수 있습니다. 이는 생성된 JSON 스키마에 포함됩니다.
@@ -53,7 +53,7 @@
///
-## 요약
+## 요약 { #recap }
모델 어트리뷰트를 위한 추가 검증과 메타데이터 선언하기 위해 Pydantic의 `Field` 를 사용할 수 있습니다.
diff --git a/docs/ko/docs/tutorial/body-multiple-params.md b/docs/ko/docs/tutorial/body-multiple-params.md
index edf892dfab..701351e637 100644
--- a/docs/ko/docs/tutorial/body-multiple-params.md
+++ b/docs/ko/docs/tutorial/body-multiple-params.md
@@ -1,26 +1,24 @@
-# 본문 - 다중 매개변수
+# 본문 - 다중 매개변수 { #body-multiple-parameters }
-지금부터 `Path`와 `Query`를 어떻게 사용하는지 확인하겠습니다.
+이제 `Path`와 `Query`를 어떻게 사용하는지 확인했으니, 요청 본문 선언에 대한 더 고급 사용법을 살펴보겠습니다.
-요청 본문 선언에 대한 심화 사용법을 알아보겠습니다.
+## `Path`, `Query` 및 본문 매개변수 혼합 { #mix-path-query-and-body-parameters }
-## `Path`, `Query` 및 본문 매개변수 혼합
+먼저, 물론 `Path`, `Query` 및 요청 본문 매개변수 선언을 자유롭게 혼합해서 사용할 수 있고, **FastAPI**는 어떤 동작을 할지 압니다.
-당연하게 `Path`, `Query` 및 요청 본문 매개변수 선언을 자유롭게 혼합해서 사용할 수 있고, **FastAPI**는 어떤 동작을 할지 압니다.
+또한 기본 값을 `None`으로 설정해 본문 매개변수를 선택사항으로 선언할 수 있습니다:
-또한, 기본 값을 `None`으로 설정해 본문 매개변수를 선택사항으로 선언할 수 있습니다.
-
-{* ../../docs_src/body_multiple_params/tutorial001.py hl[19:21] *}
+{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *}
/// note | 참고
-이 경우에는 본문으로 부터 가져온 ` item`은 기본값이 `None`이기 때문에, 선택사항이라는 점을 유의해야 합니다.
+이 경우에는 본문에서 가져올 `item`이 선택사항이라는 점을 유의하세요. 기본값이 `None`이기 때문입니다.
///
-## 다중 본문 매개변수
+## 다중 본문 매개변수 { #multiple-body-parameters }
-이전 예제에서 보듯이, *경로 작동*은 아래와 같이 `Item` 속성을 가진 JSON 본문을 예상합니다:
+이전 예제에서, *경로 처리*는 아래처럼 `Item`의 속성을 가진 JSON 본문을 예상합니다:
```JSON
{
@@ -33,11 +31,12 @@
하지만, 다중 본문 매개변수 역시 선언할 수 있습니다. 예. `item`과 `user`:
-{* ../../docs_src/body_multiple_params/tutorial002.py hl[22] *}
+{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *}
-이 경우에, **FastAPI**는 이 함수 안에 한 개 이상의 본문 매개변수(Pydantic 모델인 두 매개변수)가 있다고 알 것입니다.
-그래서, 본문의 매개변수 이름을 키(필드 명)로 사용할 수 있고, 다음과 같은 본문을 예측합니다:
+이 경우에, **FastAPI**는 이 함수에 본문 매개변수가 1개보다 많다는 것을 알아챌 것입니다(두 매개변수가 Pydantic 모델입니다).
+
+그래서, 본문에서 매개변수 이름을 키(필드 이름)로 사용하고, 다음과 같은 본문을 예상합니다:
```JSON
{
@@ -56,29 +55,28 @@
/// note | 참고
-이전과 같이 `item`이 선언 되었더라도, 본문 내의 `item` 키가 있을 것이라고 예측합니다.
+`item`이 이전과 같은 방식으로 선언되었더라도, 이제는 본문에서 `item` 키 안에 있을 것으로 예상된다는 점을 유의하세요.
///
-FastAPI는 요청을 자동으로 변환해, 매개변수의 `item`과 `user`를 특별한 내용으로 받도록 할 것입니다.
+**FastAPI**는 요청에서 자동으로 변환을 수행하여, 매개변수 `item`이 해당하는 내용을 받고 `user`도 마찬가지로 받도록 합니다.
-복합 데이터의 검증을 수행하고 OpenAPI 스키마 및 자동 문서를 문서화합니다.
+복합 데이터의 검증을 수행하고, OpenAPI 스키마 및 자동 문서에도 그에 맞게 문서화합니다.
-## 본문 내의 단일 값
+## 본문 내의 단일 값 { #singular-values-in-body }
-쿼리 및 경로 매개변수에 대한 추가 데이터를 정의하는 `Query`와 `Path`와 같이, **FastAPI**는 동등한 `Body`를 제공합니다.
+쿼리 및 경로 매개변수에 대한 추가 데이터를 정의하는 `Query`와 `Path`가 있는 것과 같은 방식으로, **FastAPI**는 동등한 `Body`를 제공합니다.
-예를 들어 이전의 모델을 확장하면, `item`과 `user`와 동일한 본문에 또 다른 `importance`라는 키를 갖도록 할 수있습니다.
+예를 들어 이전 모델을 확장해서, `item`과 `user` 외에도 같은 본문에 `importance`라는 다른 키를 두고 싶을 수 있습니다.
-단일 값을 그대로 선언한다면, **FastAPI**는 쿼리 매개변수로 가정할 것입니다.
+단일 값이므로 그대로 선언하면, **FastAPI**는 이를 쿼리 매개변수라고 가정할 것입니다.
-하지만, **FastAPI**의 `Body`를 사용해 다른 본문 키로 처리하도록 제어할 수 있습니다:
+하지만 `Body`를 사용하여 다른 본문 키로 처리하도록 **FastAPI**에 지시할 수 있습니다:
+
+{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *}
-{* ../../docs_src/body_multiple_params/tutorial003.py hl[23] *}
-
-이 경우에는 **FastAPI**는 본문을 이와 같이 예측할 것입니다:
-
+이 경우에는 **FastAPI**가 다음과 같은 본문을 예상할 것입니다:
```JSON
{
@@ -96,58 +94,55 @@ FastAPI는 요청을 자동으로 변환해, 매개변수의 `item`과 `user`를
}
```
-다시 말해, 데이터 타입, 검증, 문서 등을 변환합니다.
+다시 말해, 데이터 타입을 변환하고, 검증하고, 문서화하는 등의 작업을 수행합니다.
-## 다중 본문 매개변수와 쿼리
+## 다중 본문 매개변수와 쿼리 { #multiple-body-params-and-query }
-당연히, 필요할 때마다 추가적인 쿼리 매개변수를 선언할 수 있고, 이는 본문 매개변수에 추가됩니다.
+물론, 필요할 때마다 어떤 본문 매개변수에 추가로 쿼리 매개변수도 선언할 수 있습니다.
-기본적으로 단일 값은 쿼리 매개변수로 해석되므로, 명시적으로 `Query`를 추가할 필요가 없고, 아래처럼 할 수 있습니다:
-
-{* ../../docs_src/body_multiple_params/tutorial004.py hl[27] *}
-
-이렇게:
+기본적으로 단일 값은 쿼리 매개변수로 해석되므로, 명시적으로 `Query`를 추가할 필요 없이 이렇게 하면 됩니다:
```Python
-q: Optional[str] = None
+q: Union[str, None] = None
```
+또는 Python 3.10 이상에서는:
+
+```Python
+q: str | None = None
+```
+
+예를 들어:
+
+{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *}
+
+
/// info | 정보
-`Body` 또한 `Query`, `Path` 그리고 이후에 볼 다른 것들처럼 동일한 추가 검증과 메타데이터 매개변수를 갖고 있습니다.
+`Body` 또한 `Query`, `Path` 그리고 이후에 볼 다른 것들과 마찬가지로 동일한 추가 검증과 메타데이터 매개변수를 모두 갖고 있습니다.
///
-## 단일 본문 매개변수 삽입하기
+## 단일 본문 매개변수 삽입하기 { #embed-a-single-body-parameter }
-Pydantic 모델 `Item`의 `item`을 본문 매개변수로 오직 한개만 갖고있다고 하겠습니다.
+Pydantic 모델 `Item`에서 가져온 단일 `item` 본문 매개변수만 있다고 하겠습니다.
-기본적으로 **FastAPI**는 직접 본문으로 예측할 것입니다.
+기본적으로 **FastAPI**는 그 본문을 직접 예상합니다.
-하지만, 만약 모델 내용에 `item `키를 가진 JSON으로 예측하길 원한다면, 추가적인 본문 매개변수를 선언한 것처럼 `Body`의 특별한 매개변수인 `embed`를 사용할 수 있습니다:
-
-{* ../../docs_src/body_multiple_params/tutorial005.py hl[17] *}
-
-아래 처럼:
+하지만 추가 본문 매개변수를 선언할 때처럼, `item` 키를 가지고 그 안에 모델 내용이 들어 있는 JSON을 예상하게 하려면, `Body`의 특별한 매개변수 `embed`를 사용할 수 있습니다:
```Python
-item: Item = Body(..., embed=True)
+item: Item = Body(embed=True)
```
-이 경우에 **FastAPI**는 본문을 아래 대신에:
+다음과 같이요:
+
+{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *}
+
+
+이 경우 **FastAPI**는 다음과 같은 본문을 예상합니다:
```JSON hl_lines="2"
-{
- "name": "Foo",
- "description": "The pretender",
- "price": 42.0,
- "tax": 3.2
-}
-```
-
-아래 처럼 예측할 것 입니다:
-
-```JSON
{
"item": {
"name": "Foo",
@@ -158,12 +153,23 @@ item: Item = Body(..., embed=True)
}
```
-## 정리
+다음 대신에:
-요청이 단 한개의 본문을 가지고 있더라도, *경로 작동 함수*로 다중 본문 매개변수를 추가할 수 있습니다.
+```JSON
+{
+ "name": "Foo",
+ "description": "The pretender",
+ "price": 42.0,
+ "tax": 3.2
+}
+```
-하지만, **FastAPI**는 이를 처리하고, 함수에 올바른 데이터를 제공하며, *경로 작동*으로 올바른 스키마를 검증하고 문서화 합니다.
+## 정리 { #recap }
-또한, 단일 값을 본문의 일부로 받도록 선언할 수 있습니다.
+요청은 본문을 하나만 가질 수 있지만, *경로 처리 함수*에 다중 본문 매개변수를 추가할 수 있습니다.
-그리고 **FastAPI**는 단 한개의 매개변수가 선언 되더라도, 본문 내의 키로 삽입 시킬 수 있습니다.
+하지만 **FastAPI**는 이를 처리하고, 함수에 올바른 데이터를 제공하며, *경로 처리*에서 올바른 스키마를 검증하고 문서화합니다.
+
+또한 단일 값을 본문의 일부로 받도록 선언할 수 있습니다.
+
+그리고 단 하나의 매개변수만 선언되어 있더라도, **FastAPI**에 본문을 키 안에 삽입하도록 지시할 수 있습니다.
diff --git a/docs/ko/docs/tutorial/body-nested-models.md b/docs/ko/docs/tutorial/body-nested-models.md
index ebd7b3ba6a..4a8c1afc13 100644
--- a/docs/ko/docs/tutorial/body-nested-models.md
+++ b/docs/ko/docs/tutorial/body-nested-models.md
@@ -1,35 +1,26 @@
-# 본문 - 중첩 모델
+# 본문 - 중첩 모델 { #body-nested-models }
-**FastAPI**를 이용하면 (Pydantic 덕분에) 단독으로 깊이 중첩된 모델을 정의, 검증, 문서화하며 사용할 수 있습니다.
-## 리스트 필드
+**FastAPI**를 사용하면 (Pydantic 덕분에) 임의로 깊게 중첩된 모델을 정의, 검증, 문서화하고 사용할 수 있습니다.
+
+## 리스트 필드 { #list-fields }
어트리뷰트를 서브타입으로 정의할 수 있습니다. 예를 들어 파이썬 `list`는:
-{* ../../docs_src/body_nested_models/tutorial001.py hl[14] *}
+{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *}
-이는 `tags`를 항목 리스트로 만듭니다. 각 항목의 타입을 선언하지 않더라도요.
+이는 `tags`를 리스트로 만들지만, 리스트 요소의 타입을 선언하지는 않습니다.
-## 타입 매개변수가 있는 리스트 필드
+## 타입 매개변수가 있는 리스트 필드 { #list-fields-with-type-parameter }
-하지만 파이썬은 내부의 타입이나 "타입 매개변수"를 선언할 수 있는 특정 방법이 있습니다:
+하지만 파이썬에는 내부 타입, 즉 "타입 매개변수"를 사용해 리스트를 선언하는 특정한 방법이 있습니다:
-### typing의 `List` 임포트
+### 타입 매개변수로 `list` 선언 { #declare-a-list-with-a-type-parameter }
-먼저, 파이썬 표준 `typing` 모듈에서 `List`를 임포트합니다:
-
-{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
-
-### 타입 매개변수로 `List` 선언
-
-`list`, `dict`, `tuple`과 같은 타입 매개변수(내부 타입)를 갖는 타입을 선언하려면:
-
-* `typing` 모듈에서 임포트
-* 대괄호를 사용하여 "타입 매개변수"로 내부 타입 전달: `[` 및 `]`
+`list`, `dict`, `tuple`처럼 타입 매개변수(내부 타입)를 갖는 타입을 선언하려면,
+대괄호 `[` 및 `]`를 사용해 내부 타입(들)을 "타입 매개변수"로 전달하세요.
```Python
-from typing import List
-
-my_list: List[str]
+my_list: list[str]
```
이 모든 것은 타입 선언을 위한 표준 파이썬 문법입니다.
@@ -38,45 +29,45 @@ my_list: List[str]
마찬가지로 예제에서 `tags`를 구체적으로 "문자열의 리스트"로 만들 수 있습니다:
-{* ../../docs_src/body_nested_models/tutorial002.py hl[14] *}
+{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}
-## 집합 타입
+## 집합 타입 { #set-types }
-그런데 생각해보니 태그는 반복되면 안 되고, 고유한(Unique) 문자열이어야 할 것 같습니다.
+그런데 생각해보니 태그는 반복되면 안 되고, 아마 고유한 문자열이어야 할 것입니다.
-그리고 파이썬은 집합을 위한 특별한 데이터 타입 `set`이 있습니다.
+그리고 파이썬에는 고유한 항목들의 집합을 위한 특별한 데이터 타입 `set`이 있습니다.
-그렇다면 `Set`을 임포트 하고 `tags`를 `str`의 `set`으로 선언할 수 있습니다:
+그렇다면 `tags`를 문자열의 집합으로 선언할 수 있습니다:
-{* ../../docs_src/body_nested_models/tutorial003.py hl[1,14] *}
+{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *}
-덕분에 중복 데이터가 있는 요청을 수신하더라도 고유한 항목들의 집합으로 변환됩니다.
+이렇게 하면 중복 데이터가 있는 요청을 받더라도 고유한 항목들의 집합으로 변환됩니다.
-그리고 해당 데이터를 출력 할 때마다 소스에 중복이 있더라도 고유한 항목들의 집합으로 출력됩니다.
+그리고 해당 데이터를 출력할 때마다, 소스에 중복이 있더라도 고유한 항목들의 집합으로 출력됩니다.
또한 그에 따라 주석이 생기고 문서화됩니다.
-## 중첩 모델
+## 중첩 모델 { #nested-models }
Pydantic 모델의 각 어트리뷰트는 타입을 갖습니다.
-그런데 해당 타입 자체로 또다른 Pydantic 모델의 타입이 될 수 있습니다.
+그런데 그 타입 자체가 또 다른 Pydantic 모델일 수 있습니다.
-그러므로 특정한 어트리뷰트의 이름, 타입, 검증을 사용하여 깊게 중첩된 JSON "객체"를 선언할 수 있습니다.
+따라서 특정한 어트리뷰트 이름, 타입, 검증을 사용하여 깊게 중첩된 JSON "객체"를 선언할 수 있습니다.
-모든 것이 단독으로 중첩됩니다.
+모든 것이 임의의 깊이로 중첩됩니다.
-### 서브모델 정의
+### 서브모델 정의 { #define-a-submodel }
-예를 들어, `Image` 모델을 선언할 수 있습니다:
+예를 들어, `Image` 모델을 정의할 수 있습니다:
-{* ../../docs_src/body_nested_models/tutorial004.py hl[9:11] *}
+{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *}
-### 서브모듈을 타입으로 사용
+### 서브모델을 타입으로 사용 { #use-the-submodel-as-a-type }
-그리고 어트리뷰트의 타입으로 사용할 수 있습니다:
+그리고 이를 어트리뷰트의 타입으로 사용할 수 있습니다:
-{* ../../docs_src/body_nested_models/tutorial004.py hl[20] *}
+{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *}
이는 **FastAPI**가 다음과 유사한 본문을 기대한다는 것을 의미합니다:
@@ -94,32 +85,32 @@ Pydantic 모델의 각 어트리뷰트는 타입을 갖습니다.
}
```
-다시 한번, **FastAPI**를 사용하여 해당 선언을 함으로써 얻는 것은:
+다시 한번, **FastAPI**로 그 선언만 해도 얻는 것은:
* 중첩 모델도 편집기 지원(자동완성 등)
* 데이터 변환
* 데이터 검증
* 자동 문서화
-## 특별한 타입과 검증
+## 특별한 타입과 검증 { #special-types-and-validation }
-`str`, `int`, `float` 등과 같은 단일 타입과는 별개로, `str`을 상속하는 더 복잡한 단일 타입을 사용할 수 있습니다.
+`str`, `int`, `float` 등과 같은 일반적인 단일 타입과는 별개로, `str`을 상속하는 더 복잡한 단일 타입을 사용할 수 있습니다.
-모든 옵션을 보려면, Pydantic's exotic types 문서를 확인하세요. 다음 장에서 몇가지 예제를 볼 수 있습니다.
+사용할 수 있는 모든 옵션을 보려면 Pydantic의 Type Overview를 확인하세요. 다음 장에서 몇 가지 예제를 볼 수 있습니다.
-예를 들어 `Image` 모델 안에 `url` 필드를 `str` 대신 Pydantic의 `HttpUrl`로 선언할 수 있습니다:
+예를 들어 `Image` 모델에는 `url` 필드가 있으므로, 이를 `str` 대신 Pydantic의 `HttpUrl` 인스턴스로 선언할 수 있습니다:
-{* ../../docs_src/body_nested_models/tutorial005.py hl[4,10] *}
+{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *}
-이 문자열이 유효한 URL인지 검사하고 JSON 스키마/OpenAPI로 문서화 됩니다.
+이 문자열은 유효한 URL인지 검사되며, JSON Schema / OpenAPI에도 그에 맞게 문서화됩니다.
-## 서브모델 리스트를 갖는 어트리뷰트
+## 서브모델 리스트를 갖는 어트리뷰트 { #attributes-with-lists-of-submodels }
`list`, `set` 등의 서브타입으로 Pydantic 모델을 사용할 수도 있습니다:
-{* ../../docs_src/body_nested_models/tutorial006.py hl[20] *}
+{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *}
-아래와 같은 JSON 본문으로 예상(변환, 검증, 문서화 등을)합니다:
+아래와 같은 JSON 본문을 예상(변환, 검증, 문서화 등)합니다:
```JSON hl_lines="11"
{
@@ -147,84 +138,84 @@ Pydantic 모델의 각 어트리뷰트는 타입을 갖습니다.
/// info | 정보
-`images` 키가 어떻게 이미지 객체 리스트를 갖는지 주목하세요.
+`images` 키가 이제 이미지 객체 리스트를 갖는지 주목하세요.
///
-## 깊게 중첩된 모델
+## 깊게 중첩된 모델 { #deeply-nested-models }
-단독으로 깊게 중첩된 모델을 정의할 수 있습니다:
+임의로 깊게 중첩된 모델을 정의할 수 있습니다:
-{* ../../docs_src/body_nested_models/tutorial007.py hl[9,14,20,23,27] *}
+{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *}
/// info | 정보
-`Offer`가 선택사항 `Image` 리스트를 차례로 갖는 `Item` 리스트를 어떻게 가지고 있는지 주목하세요
+`Offer`가 `Item`의 리스트를 가지고, 그 `Item`이 다시 선택 사항인 `Image` 리스트를 갖는지 주목하세요
///
-## 순수 리스트의 본문
+## 순수 리스트의 본문 { #bodies-of-pure-lists }
-예상되는 JSON 본문의 최상위 값이 JSON `array`(파이썬 `list`)면, Pydantic 모델에서와 마찬가지로 함수의 매개변수에서 타입을 선언할 수 있습니다:
+예상되는 JSON 본문의 최상위 값이 JSON `array`(파이썬 `list`)라면, Pydantic 모델에서와 마찬가지로 함수의 매개변수에서 타입을 선언할 수 있습니다:
```Python
-images: List[Image]
+images: list[Image]
```
이를 아래처럼:
-{* ../../docs_src/body_nested_models/tutorial008.py hl[15] *}
+{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}
-## 어디서나 편집기 지원
+## 어디서나 편집기 지원 { #editor-support-everywhere }
-그리고 어디서나 편집기 지원을 받을수 있습니다.
+그리고 어디서나 편집기 지원을 받을 수 있습니다.
리스트 내부 항목의 경우에도:
-Pydantic 모델 대신에 `dict`를 직접 사용하여 작업할 경우, 이러한 편집기 지원을 받을수 없습니다.
+Pydantic 모델 대신 `dict`로 직접 작업한다면 이런 종류의 편집기 지원을 받을 수 없습니다.
-하지만 수신한 딕셔너리가 자동으로 변환되고 출력도 자동으로 JSON으로 변환되므로 걱정할 필요는 없습니다.
+하지만 그 부분에 대해서도 걱정할 필요는 없습니다. 들어오는 dict는 자동으로 변환되고, 출력도 자동으로 JSON으로 변환됩니다.
-## 단독 `dict`의 본문
+## 임의의 `dict` 본문 { #bodies-of-arbitrary-dicts }
-일부 타입의 키와 다른 타입의 값을 사용하여 `dict`로 본문을 선언할 수 있습니다.
+또한 키는 어떤 타입이고 값은 다른 타입인 `dict`로 본문을 선언할 수 있습니다.
-(Pydantic을 사용한 경우처럼) 유효한 필드/어트리뷰트 이름이 무엇인지 알 필요가 없습니다.
+이렇게 하면 (Pydantic 모델을 사용하는 경우처럼) 유효한 필드/어트리뷰트 이름이 무엇인지 미리 알 필요가 없습니다.
-아직 모르는 키를 받으려는 경우 유용합니다.
+아직 모르는 키를 받으려는 경우에 유용합니다.
---
-다른 유용한 경우는 다른 타입의 키를 가질 때입니다. 예. `int`.
+또 다른 유용한 경우는 다른 타입(예: `int`)의 키를 갖고 싶을 때입니다.
여기서 그 경우를 볼 것입니다.
-이 경우, `float` 값을 가진 `int` 키가 있는 모든 `dict`를 받아들입니다:
+이 경우, `int` 키와 `float` 값을 가진 한 어떤 `dict`든 받아들입니다:
-{* ../../docs_src/body_nested_models/tutorial009.py hl[15] *}
+{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}
/// tip | 팁
-JSON은 오직 `str`형 키만 지원한다는 것을 염두에 두세요.
+JSON은 키로 `str`만 지원한다는 것을 염두에 두세요.
-하지만 Pydantic은 자동 데이터 변환이 있습니다.
+하지만 Pydantic은 자동 데이터 변환 기능이 있습니다.
-즉, API 클라이언트가 문자열을 키로 보내더라도 해당 문자열이 순수한 정수를 포함하는한 Pydantic은 이를 변환하고 검증합니다.
+즉, API 클라이언트는 키로 문자열만 보낼 수 있더라도, 해당 문자열이 순수한 정수를 포함하기만 하면 Pydantic이 이를 변환하고 검증합니다.
-그러므로 `weights`로 받은 `dict`는 실제로 `int` 키와 `float` 값을 가집니다.
+그리고 `weights`로 받는 `dict`는 실제로 `int` 키와 `float` 값을 갖게 됩니다.
///
-## 요약
+## 요약 { #recap }
-**FastAPI**를 사용하면 Pydantic 모델이 제공하는 최대 유연성을 확보하면서 코드를 간단하고 짧게, 그리고 우아하게 유지할 수 있습니다.
+**FastAPI**를 사용하면 Pydantic 모델이 제공하는 최대 유연성을 확보하면서 코드를 간단하고 짧고 우아하게 유지할 수 있습니다.
-물론 아래의 이점도 있습니다:
+하지만 아래의 모든 이점도 있습니다:
-* 편집기 지원 (자동완성이 어디서나!)
-* 데이터 변환 (일명 파싱/직렬화)
+* 편집기 지원(어디서나 자동완성!)
+* 데이터 변환(일명 파싱/직렬화)
* 데이터 검증
* 스키마 문서화
* 자동 문서
diff --git a/docs/ko/docs/tutorial/body.md b/docs/ko/docs/tutorial/body.md
index b3914fa4b4..1e66c60c22 100644
--- a/docs/ko/docs/tutorial/body.md
+++ b/docs/ko/docs/tutorial/body.md
@@ -1,10 +1,10 @@
-# 요청 본문
+# 요청 본문 { #request-body }
클라이언트(브라우저라고 해봅시다)로부터 여러분의 API로 데이터를 보내야 할 때, **요청 본문**으로 보냅니다.
**요청** 본문은 클라이언트에서 API로 보내지는 데이터입니다. **응답** 본문은 API가 클라이언트로 보내는 데이터입니다.
-여러분의 API는 대부분의 경우 **응답** 본문을 보내야 합니다. 하지만 클라이언트는 **요청** 본문을 매 번 보낼 필요가 없습니다.
+여러분의 API는 대부분의 경우 **응답** 본문을 보내야 합니다. 하지만 클라이언트는 항상 **요청 본문**을 보낼 필요는 없고, 때로는 (쿼리 매개변수와 함께) 어떤 경로만 요청하고 본문은 보내지 않을 수도 있습니다.
**요청** 본문을 선언하기 위해서 모든 강력함과 이점을 갖춘 Pydantic 모델을 사용합니다.
@@ -18,13 +18,13 @@
///
-## Pydantic의 `BaseModel` 임포트
+## Pydantic의 `BaseModel` 임포트 { #import-pydantics-basemodel }
먼저 `pydantic`에서 `BaseModel`를 임포트해야 합니다:
{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
-## 여러분의 데이터 모델 만들기
+## 여러분의 데이터 모델 만들기 { #create-your-data-model }
`BaseModel`를 상속받은 클래스로 여러분의 데이터 모델을 선언합니다.
@@ -32,6 +32,7 @@
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
+
쿼리 매개변수를 선언할 때와 같이, 모델 어트리뷰트가 기본 값을 가지고 있어도 이는 필수가 아닙니다. 그외에는 필수입니다. 그저 `None`을 사용하여 선택적으로 만들 수 있습니다.
예를 들면, 위의 이 모델은 JSON "`object`" (혹은 파이썬 `dict`)을 다음과 같이 선언합니다:
@@ -39,7 +40,7 @@
```JSON
{
"name": "Foo",
- "description": "선택적인 설명란",
+ "description": "An optional description",
"price": 45.2,
"tax": 3.5
}
@@ -54,15 +55,15 @@
}
```
-## 매개변수로서 선언하기
+## 매개변수로서 선언하기 { #declare-it-as-a-parameter }
-여러분의 *경로 작동*에 추가하기 위해, 경로 매개변수 그리고 쿼리 매개변수에서 선언했던 것과 같은 방식으로 선언하면 됩니다.
+여러분의 *경로 처리*에 추가하기 위해, 경로 매개변수 그리고 쿼리 매개변수에서 선언했던 것과 같은 방식으로 선언하면 됩니다.
{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
...그리고 만들어낸 모델인 `Item`으로 타입을 선언합니다.
-## 결과
+## 결과 { #results }
위에서의 단순한 파이썬 타입 선언으로, **FastAPI**는 다음과 같이 동작합니다:
@@ -72,20 +73,20 @@
* 만약 데이터가 유효하지 않다면, 정확히 어떤 것이 그리고 어디에서 데이터가 잘 못 되었는지 지시하는 친절하고 명료한 에러를 반환할 것입니다.
* 매개변수 `item`에 포함된 수신 데이터를 제공합니다.
* 함수 내에서 매개변수를 `Item` 타입으로 선언했기 때문에, 모든 어트리뷰트와 그에 대한 타입에 대한 편집기 지원(완성 등)을 또한 받을 수 있습니다.
-* 여러분의 모델을 위한 JSON 스키마 정의를 생성합니다. 여러분의 프로젝트에 적합하다면 여러분이 사용하고 싶은 곳 어디에서나 사용할 수 있습니다.
-* 이러한 스키마는, 생성된 OpenAPI 스키마 일부가 될 것이며, 자동 문서화 UI에 사용됩니다.
+* 여러분의 모델을 위한 JSON Schema 정의를 생성합니다. 여러분의 프로젝트에 적합하다면 여러분이 사용하고 싶은 곳 어디에서나 사용할 수 있습니다.
+* 이러한 스키마는, 생성된 OpenAPI 스키마 일부가 될 것이며, 자동 문서화 UIs에 사용됩니다.
-## 자동 문서화
+## 자동 문서화 { #automatic-docs }
모델의 JSON 스키마는 생성된 OpenAPI 스키마에 포함되며 대화형 API 문서에 표시됩니다:
-이를 필요로 하는 각각의 *경로 작동*내부의 API 문서에도 사용됩니다:
+이를 필요로 하는 각각의 *경로 처리* 내부의 API 문서에도 사용됩니다:
-## 편집기 지원
+## 편집기 지원 { #editor-support }
편집기에서, 함수 내에서 타입 힌트와 완성을 어디서나 (만약 Pydantic model 대신에 `dict`을 받을 경우 나타나지 않을 수 있습니다) 받을 수 있습니다:
@@ -97,13 +98,13 @@
단순한 우연이 아닙니다. 프레임워크 전체가 이러한 디자인을 중심으로 설계되었습니다.
-그 어떤 실행 전에, 모든 편집기에서 작동할 수 있도록 보장하기 위해 설계 단계에서 혹독하게 테스트되었습니다.
+그 어떤 구현 전에, 모든 편집기에서 작동할 수 있도록 보장하기 위해 설계 단계에서 혹독하게 테스트되었습니다.
이를 지원하기 위해 Pydantic 자체에서 몇몇 변경점이 있었습니다.
이전 스크린샷은 Visual Studio Code를 찍은 것입니다.
-하지만 똑같은 편집기 지원을 PyCharm에서 받을 수 있거나, 대부분의 다른 편집기에서도 받을 수 있습니다:
+하지만 똑같은 편집기 지원을 PyCharm와 대부분의 다른 파이썬 편집기에서도 받을 수 있습니다:
@@ -113,21 +114,21 @@
다음 사항을 포함해 Pydantic 모델에 대한 편집기 지원을 향상시킵니다:
-* 자동 완성
-* 타입 확인
-* 리팩토링
-* 검색
-* 점검
+* auto-completion
+* type checks
+* refactoring
+* searching
+* inspections
///
-## 모델 사용하기
+## 모델 사용하기 { #use-the-model }
함수 안에서 모델 객체의 모든 어트리뷰트에 직접 접근 가능합니다:
-{* ../../docs_src/body/tutorial002_py310.py hl[19] *}
+{* ../../docs_src/body/tutorial002_py310.py *}
-## 요청 본문 + 경로 매개변수
+## 요청 본문 + 경로 매개변수 { #request-body-path-parameters }
경로 매개변수와 요청 본문을 동시에 선언할 수 있습니다.
@@ -135,7 +136,8 @@
{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
-## 요청 본문 + 경로 + 쿼리 매개변수
+
+## 요청 본문 + 경로 + 쿼리 매개변수 { #request-body-path-query-parameters }
**본문**, **경로** 그리고 **쿼리** 매개변수 모두 동시에 선언할 수도 있습니다.
@@ -153,10 +155,12 @@
FastAPI는 `q`의 값이 필요없음을 알게 될 것입니다. 기본 값이 `= None`이기 때문입니다.
-`Union[str, None]`에 있는 `Union`은 FastAPI에 의해 사용된 것이 아니지만, 편집기로 하여금 더 나은 지원과 에러 탐지를 지원할 것입니다.
+Python 3.10+의 `str | None` 또는 Python 3.9+의 `Union[str, None]`에 있는 `Union`은 FastAPI가 `q` 값이 필수가 아님을 판단하기 위해 사용하지 않습니다. 기본 값이 `= None`이기 때문에 필수가 아님을 알게 됩니다.
+
+하지만 타입 어노테이션을 추가하면 편집기가 더 나은 지원을 제공하고 오류를 감지할 수 있습니다.
///
-## Pydantic없이
+## Pydantic없이 { #without-pydantic }
-만약 Pydantic 모델을 사용하고 싶지 않다면, **Body** 매개변수를 사용할 수도 있습니다. [Body - 다중 매개변수: 본문에 있는 유일한 값](body-multiple-params.md#_2){.internal-link target=_blank} 문서를 확인하세요.
+만약 Pydantic 모델을 사용하고 싶지 않다면, **Body** 매개변수를 사용할 수도 있습니다. [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank} 문서를 확인하세요.
diff --git a/docs/ko/docs/tutorial/cookie-param-models.md b/docs/ko/docs/tutorial/cookie-param-models.md
index e7eef0b1de..00238d1b79 100644
--- a/docs/ko/docs/tutorial/cookie-param-models.md
+++ b/docs/ko/docs/tutorial/cookie-param-models.md
@@ -1,8 +1,8 @@
-# 쿠키 매개변수 모델
+# 쿠키 매개변수 모델 { #cookie-parameter-models }
관련있는 **쿠키**들의 그룹이 있는 경우, **Pydantic 모델**을 생성하여 선언할 수 있습니다. 🍪
-이를 통해 **여러 위치**에서 **모델을 재사용** 할 수 있고 모든 매개변수에 대한 유효성 검사 및 메타데이터를 한 번에 선언할 수도 있습니다. 😍
+이를 통해 **여러 위치**에서 **모델을 재사용** 할 수 있고 모든 매개변수에 대한 유효성 검사 및 메타데이터를 한 번에 선언할 수도 있습니다. 😎
/// note | 참고
@@ -16,7 +16,7 @@
///
-## Pydantic 모델을 사용한 쿠키
+## Pydantic 모델을 사용한 쿠키 { #cookies-with-a-pydantic-model }
**Pydantic 모델**에 필요한 **쿠키** 매개변수를 선언한 다음, 해당 매개변수를 `Cookie`로 선언합니다:
@@ -24,7 +24,7 @@
**FastAPI**는 요청에서 받은 **쿠키**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다.
-## 문서 확인하기
+## 문서 확인하기 { #check-the-docs }
문서 UI `/docs`에서 정의한 쿠키를 볼 수 있습니다:
@@ -36,27 +36,27 @@
명심하세요, 내부적으로 **브라우저는 쿠키를 특별한 방식으로 처리**하기 때문에 **자바스크립트**가 쉽게 쿠키를 건드릴 수 **없습니다**.
-`/docs`에서 **API 문서 UI**로 이동하면 *경로 작업*에 대한 쿠키의 **문서**를 볼 수 있습니다.
+`/docs`에서 **API 문서 UI**로 이동하면 *경로 처리*에 대한 쿠키의 **문서**를 볼 수 있습니다.
하지만 아무리 **데이터를 입력**하고 "실행(Execute)"을 클릭해도, 문서 UI는 **자바스크립트**로 작동하기 때문에 쿠키는 전송되지 않고, 아무 값도 쓰지 않은 것처럼 **오류** 메시지를 보게 됩니다.
///
-## 추가 쿠키 금지하기
+## 추가 쿠키 금지하기 { #forbid-extra-cookies }
일부 특별한 사용 사례(흔하지는 않겠지만)에서는 수신하려는 쿠키를 **제한**할 수 있습니다.
-이제 API는 자신의 쿠키 동의를 제어할 수 있는 권한을 갖게 되었습니다. 🤪🍪
+이제 API는 자신의 cookie consent를 제어할 수 있는 권한을 갖게 되었습니다. 🤪🍪
Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다:
-{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
+{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *}
클라이언트가 **추가 쿠키**를 보내려고 시도하면, **오류** 응답을 받게 됩니다.
-API가 거부하는데도 동의를 얻기 위해 애쓰는 불쌍한 쿠키 배너(팝업)들. 🍪
+동의를 얻기 위해 애쓰는 불쌍한 쿠키 배너(팝업)들, API가 거부하는데도. 🍪
-예를 들어, 클라이언트가 `good-list-please` 값으로 `santa_tracker` 쿠키를 보내려고 하면 클라이언트는 `santa_tracker` 쿠키가 허용되지 않는다는 **오류** 응답을 받게 됩니다:
+예를 들어, 클라이언트가 `good-list-please` 값으로 `santa_tracker` 쿠키를 보내려고 하면 클라이언트는 `santa_tracker` 쿠키가 허용되지 않는다는 **오류** 응답을 받게 됩니다:
```json
{
@@ -71,6 +71,6 @@ Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forb
}
```
-## 요약
+## 요약 { #summary }
-**Pydantic 모델**을 사용하여 **FastAPI**에서 **쿠키**를 선언할 수 있습니다. 😍
+**Pydantic 모델**을 사용하여 **FastAPI**에서 **쿠키**를 선언할 수 있습니다. 😎
diff --git a/docs/ko/docs/tutorial/cookie-params.md b/docs/ko/docs/tutorial/cookie-params.md
index fba756d498..0591a5e96b 100644
--- a/docs/ko/docs/tutorial/cookie-params.md
+++ b/docs/ko/docs/tutorial/cookie-params.md
@@ -1,14 +1,14 @@
-# 쿠키 매개변수
+# 쿠키 매개변수 { #cookie-parameters }
쿠키 매개변수를 `Query`와 `Path` 매개변수들과 같은 방식으로 정의할 수 있습니다.
-## `Cookie` 임포트
+## `Cookie` 임포트 { #import-cookie }
먼저 `Cookie`를 임포트합니다:
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
-## `Cookie` 매개변수 선언
+## `Cookie` 매개변수 선언 { #declare-cookie-parameters }
그런 다음, `Path`와 `Query`처럼 동일한 구조를 사용하는 쿠키 매개변수를 선언합니다.
@@ -20,7 +20,7 @@
`Cookie`는 `Path` 및 `Query`의 "자매"클래스입니다. 이 역시 동일한 공통 `Param` 클래스를 상속합니다.
-`Query`, `Path`, `Cookie` 그리고 다른 것들은 `fastapi`에서 임포트 할 때, 실제로는 특별한 클래스를 반환하는 함수임을 기억하세요.
+하지만 `fastapi`에서 `Query`, `Path`, `Cookie` 그리고 다른 것들을 임포트할 때, 실제로는 특별한 클래스를 반환하는 함수임을 기억하세요.
///
@@ -30,6 +30,16 @@
///
-## 요약
+/// info | 정보
-`Cookie`는 `Query`, `Path`와 동일한 패턴을 사용하여 선언합니다.
+**브라우저는 쿠키를** 내부적으로 특별한 방식으로 처리하기 때문에, **JavaScript**가 쉽게 쿠키를 다루도록 허용하지 않는다는 점을 염두에 두세요.
+
+`/docs`의 **API docs UI**로 이동하면 *경로 처리*에 대한 쿠키 **문서**를 확인할 수 있습니다.
+
+하지만 **데이터를 채우고** "Execute"를 클릭하더라도, docs UI는 **JavaScript**로 동작하기 때문에 쿠키가 전송되지 않고, 아무 값도 입력하지 않은 것처럼 **오류** 메시지를 보게 될 것입니다.
+
+///
+
+## 요약 { #recap }
+
+`Query`와 `Path`에서 사용하는 것과 동일한 공통 패턴으로, `Cookie`를 사용해 쿠키를 선언합니다.
diff --git a/docs/ko/docs/tutorial/cors.md b/docs/ko/docs/tutorial/cors.md
index 1ef5a74803..0f3948a3dc 100644
--- a/docs/ko/docs/tutorial/cors.md
+++ b/docs/ko/docs/tutorial/cors.md
@@ -1,10 +1,10 @@
-# 교차 출처 리소스 공유
+# CORS (교차-출처 리소스 공유) { #cors-cross-origin-resource-sharing }
-CORS 또는 "교차-출처 리소스 공유"란, 브라우저에서 동작하는 프론트엔드가 자바스크립트로 코드로 백엔드와 통신하고, 백엔드는 해당 프론트엔드와 다른 "출처"에 존재하는 상황을 의미합니다.
+CORS 또는 "Cross-Origin Resource Sharing"란, 브라우저에서 실행되는 프론트엔드에 백엔드와 통신하는 JavaScript 코드가 있고, 백엔드가 프론트엔드와 다른 "출처(origin)"에 있는 상황을 의미합니다.
-## 출처
+## 출처 { #origin }
-출처란 프로토콜(`http` , `https`), 도메인(`myapp.com`, `localhost`, `localhost.tiangolo.com` ), 그리고 포트(`80`, `443`, `8080` )의 조합을 의미합니다.
+출처란 프로토콜(`http`, `https`), 도메인(`myapp.com`, `localhost`, `localhost.tiangolo.com`), 그리고 포트(`80`, `443`, `8080`)의 조합을 의미합니다.
따라서, 아래는 모두 상이한 출처입니다:
@@ -12,74 +12,78 @@
* `https://localhost`
* `http://localhost:8080`
-모두 `localhost` 에 있지만, 서로 다른 프로토콜과 포트를 사용하고 있으므로 다른 "출처"입니다.
+모두 `localhost`에 있더라도, 서로 다른 프로토콜이나 포트를 사용하므로 서로 다른 "출처"입니다.
-## 단계
+## 단계 { #steps }
-브라우저 내 `http://localhost:8080`에서 동작하는 프론트엔드가 있고, 자바스크립트는 `http://localhost`를 통해 백엔드와 통신한다고 가정해봅시다(포트를 명시하지 않는 경우, 브라우저는 `80` 을 기본 포트로 간주합니다).
+그러면 브라우저에서 `http://localhost:8080`으로 실행되는 프론트엔드가 있고, 그 JavaScript가 `http://localhost`에서 실행되는 백엔드와 통신하려고 한다고 해봅시다(포트를 명시하지 않았기 때문에, 브라우저는 기본 포트 `80`을 가정합니다).
-그러면 브라우저는 백엔드에 HTTP `OPTIONS` 요청을 보내고, 백엔드에서 이 다른 출처(`http://localhost:8080`)와의 통신을 허가하는 적절한 헤더를 보내면, 브라우저는 프론트엔드의 자바스크립트가 백엔드에 요청을 보낼 수 있도록 합니다.
+그러면 브라우저는 `:80`-백엔드에 HTTP `OPTIONS` 요청을 보내고, 백엔드가 이 다른 출처(`http://localhost:8080`)로부터의 통신을 허가하는 적절한 헤더를 보내면, `:8080`-브라우저는 프론트엔드의 JavaScript가 `:80`-백엔드에 요청을 보낼 수 있도록 합니다.
-이를 위해, 백엔드는 "허용된 출처(allowed origins)" 목록을 가지고 있어야만 합니다.
+이를 위해, `:80`-백엔드는 "허용된 출처(allowed origins)" 목록을 가지고 있어야 합니다.
-이 경우, 프론트엔드가 제대로 동작하기 위해 `http://localhost:8080`을 목록에 포함해야 합니다.
+이 경우, `:8080`-프론트엔드가 올바르게 동작하려면 목록에 `http://localhost:8080`이 포함되어야 합니다.
-## 와일드카드
+## 와일드카드 { #wildcards }
-모든 출처를 허용하기 위해 목록을 `"*"` ("와일드카드")로 선언하는 것도 가능합니다.
+또한 목록을 `"*"`("와일드카드")로 선언해 모두 허용된다고 말할 수도 있습니다.
-하지만 이것은 특정한 유형의 통신만을 허용하며, 쿠키 및 액세스 토큰과 사용되는 인증 헤더(Authoriztion header) 등이 포함된 경우와 같이 자격 증명(credentials)이 포함된 통신은 허용되지 않습니다.
+하지만 그러면 자격 증명(credentials)이 포함된 모든 것을 제외하고 특정 유형의 통신만 허용하게 됩니다. 예: 쿠키, Bearer Token에 사용되는 것과 같은 Authorization 헤더 등.
-따라서 모든 작업을 의도한대로 실행하기 위해, 허용되는 출처를 명시적으로 지정하는 것이 좋습니다.
+따라서 모든 것이 올바르게 동작하게 하려면, 허용된 출처를 명시적으로 지정하는 것이 더 좋습니다.
-## `CORSMiddleware` 사용
+## `CORSMiddleware` 사용 { #use-corsmiddleware }
-`CORSMiddleware` 을 사용하여 **FastAPI** 응용 프로그램의 교차 출처 리소스 공유 환경을 설정할 수 있습니다.
+`CORSMiddleware`를 사용하여 **FastAPI** 애플리케이션에서 이를 설정할 수 있습니다.
-* `CORSMiddleware` 임포트.
-* 허용되는 출처(문자열 형식)의 리스트 생성.
-* FastAPI 응용 프로그램에 "미들웨어(middleware)"로 추가.
+* `CORSMiddleware`를 임포트합니다.
+* 허용된 출처(문자열)의 리스트를 생성합니다.
+* **FastAPI** 애플리케이션에 "미들웨어(middleware)"로 추가합니다.
-백엔드에서 다음의 사항을 허용할지에 대해 설정할 수도 있습니다:
+또한 백엔드가 다음을 허용할지 여부도 지정할 수 있습니다:
-* 자격증명 (인증 헤더, 쿠키 등).
-* 특정한 HTTP 메소드(`POST`, `PUT`) 또는 와일드카드 `"*"` 를 사용한 모든 HTTP 메소드.
-* 특정한 HTTP 헤더 또는 와일드카드 `"*"` 를 사용한 모든 HTTP 헤더.
+* 자격 증명(Authorization 헤더, 쿠키 등).
+* 특정 HTTP 메서드(`POST`, `PUT`) 또는 와일드카드 `"*"`를 사용한 모든 메서드.
+* 특정 HTTP 헤더 또는 와일드카드 `"*"`를 사용한 모든 헤더.
-{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
+{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *}
-`CORSMiddleware` 에서 사용하는 기본 매개변수는 제한적이므로, 브라우저가 교차-도메인 상황에서 특정한 출처, 메소드, 헤더 등을 사용할 수 있도록 하려면 이들을 명시적으로 허용해야 합니다.
-다음의 인자들이 지원됩니다:
+`CORSMiddleware` 구현에서 사용하는 기본 매개변수는 기본적으로 제한적이므로, 브라우저가 Cross-Domain 컨텍스트에서 특정 출처, 메서드 또는 헤더를 사용할 수 있도록 하려면 이를 명시적으로 활성화해야 합니다.
-* `allow_origins` - 교차-출처 요청을 보낼 수 있는 출처의 리스트입니다. 예) `['https://example.org', 'https://www.example.org']`. 모든 출처를 허용하기 위해 `['*']` 를 사용할 수 있습니다.
-* `allow_origin_regex` - 교차-출처 요청을 보낼 수 있는 출처를 정규표현식 문자열로 나타냅니다. `'https://.*\.example\.org'`.
-* `allow_methods` - 교차-출처 요청을 허용하는 HTTP 메소드의 리스트입니다. 기본값은 `['GET']` 입니다. `['*']` 을 사용하여 모든 표준 메소드들을 허용할 수 있습니다.
-* `allow_headers` - 교차-출처를 지원하는 HTTP 요청 헤더의 리스트입니다. 기본값은 `[]` 입니다. 모든 헤더들을 허용하기 위해 `['*']` 를 사용할 수 있습니다. `Accept`, `Accept-Language`, `Content-Language` 그리고 `Content-Type` 헤더는 CORS 요청시 언제나 허용됩니다.
-* `allow_credentials` - 교차-출처 요청시 쿠키 지원 여부를 설정합니다. 기본값은 `False` 입니다. 또한 해당 항목을 허용할 경우 `allow_origins` 는 `['*']` 로 설정할 수 없으며, 출처를 반드시 특정해야 합니다.
-* `expose_headers` - 브라우저에 접근할 수 있어야 하는 모든 응답 헤더를 가리킵니다. 기본값은 `[]` 입니다.
-* `max_age` - 브라우저가 CORS 응답을 캐시에 저장하는 최대 시간을 초 단위로 설정합니다. 기본값은 `600` 입니다.
+다음 인자들이 지원됩니다:
-미들웨어는 두가지 특정한 종류의 HTTP 요청에 응답합니다...
+* `allow_origins` - 교차-출처 요청을 보낼 수 있도록 허용해야 하는 출처의 리스트입니다. 예: `['https://example.org', 'https://www.example.org']`. 모든 출처를 허용하려면 `['*']`를 사용할 수 있습니다.
+* `allow_origin_regex` - 교차-출처 요청을 보낼 수 있도록 허용해야 하는 출처와 매칭할 정규표현식 문자열입니다. 예: `'https://.*\.example\.org'`.
+* `allow_methods` - 교차-출처 요청에 허용되어야 하는 HTTP 메서드의 리스트입니다. 기본값은 `['GET']`입니다. 모든 표준 메서드를 허용하려면 `['*']`를 사용할 수 있습니다.
+* `allow_headers` - 교차-출처 요청에 대해 지원되어야 하는 HTTP 요청 헤더의 리스트입니다. 기본값은 `[]`입니다. 모든 헤더를 허용하려면 `['*']`를 사용할 수 있습니다. `Accept`, `Accept-Language`, `Content-Language`, `Content-Type` 헤더는 단순 CORS 요청에 대해 항상 허용됩니다.
+* `allow_credentials` - 교차-출처 요청에 대해 쿠키를 지원해야 함을 나타냅니다. 기본값은 `False`입니다.
-### CORS 사전 요청
+ `allow_credentials`가 `True`로 설정된 경우 `allow_origins`, `allow_methods`, `allow_headers` 중 어느 것도 `['*']`로 설정할 수 없습니다. 모두 명시적으로 지정되어야 합니다.
-`Origin` 및 `Access-Control-Request-Method` 헤더와 함께 전송하는 모든 `OPTIONS` 요청입니다.
+* `expose_headers` - 브라우저에서 접근 가능해야 하는 모든 응답 헤더를 나타냅니다. 기본값은 `[]`입니다.
+* `max_age` - 브라우저가 CORS 응답을 캐시하는 최대 시간을 초 단위로 설정합니다. 기본값은 `600`입니다.
-이 경우 미들웨어는 들어오는 요청을 가로채 적절한 CORS 헤더와, 정보 제공을 위한 `200` 또는 `400` 응답으로 응답합니다.
+미들웨어는 두 가지 특정한 종류의 HTTP 요청에 응답합니다...
-### 단순한 요청
+### CORS 사전 요청 { #cors-preflight-requests }
-`Origin` 헤더를 가진 모든 요청. 이 경우 미들웨어는 요청을 정상적으로 전달하지만, 적절한 CORS 헤더를 응답에 포함시킵니다.
+`Origin` 및 `Access-Control-Request-Method` 헤더가 있는 모든 `OPTIONS` 요청입니다.
-## 더 많은 정보
+이 경우 미들웨어는 들어오는 요청을 가로채 적절한 CORS 헤더와 함께, 정보 제공 목적으로 `200` 또는 `400` 응답을 반환합니다.
-CORS에 대한 더 많은 정보를 알고싶다면, Mozilla CORS 문서를 참고하기 바랍니다.
+### 단순한 요청 { #simple-requests }
-/// note | 기술적 세부 사항
+`Origin` 헤더가 있는 모든 요청입니다. 이 경우 미들웨어는 요청을 정상적으로 통과시키지만, 응답에 적절한 CORS 헤더를 포함합니다.
-`from starlette.middleware.cors import CORSMiddleware` 역시 사용할 수 있습니다.
+## 더 많은 정보 { #more-info }
-**FastAPI**는 개발자인 당신의 편의를 위해 `fastapi.middleware` 에서 몇가지의 미들웨어를 제공합니다. 하지만 대부분의 미들웨어가 Stralette으로부터 직접 제공됩니다.
+CORS에 대한 더 많은 정보는 Mozilla CORS 문서를 참고하세요.
+
+/// note | 기술 세부사항
+
+`from starlette.middleware.cors import CORSMiddleware`도 사용할 수 있습니다.
+
+**FastAPI**는 개발자인 여러분의 편의를 위해 `fastapi.middleware`에 여러 미들웨어를 제공합니다. 하지만 사용 가능한 미들웨어 대부분은 Starlette에서 직접 제공됩니다.
///
diff --git a/docs/ko/docs/tutorial/debugging.md b/docs/ko/docs/tutorial/debugging.md
index e42f1ba880..ca20acff64 100644
--- a/docs/ko/docs/tutorial/debugging.md
+++ b/docs/ko/docs/tutorial/debugging.md
@@ -1,14 +1,14 @@
-# 디버깅
+# 디버깅 { #debugging }
예를 들면 Visual Studio Code 또는 PyCharm을 사용하여 편집기에서 디버거를 연결할 수 있습니다.
-## `uvicorn` 호출
+## `uvicorn` 호출 { #call-uvicorn }
-FastAPI 애플리케이션에서 `uvicorn`을 직접 임포트하여 실행합니다
+FastAPI 애플리케이션에서 `uvicorn`을 직접 임포트하여 실행합니다:
-{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
+{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *}
-### `__name__ == "__main__"` 에 대하여
+### `__name__ == "__main__"` 에 대하여 { #about-name-main }
`__name__ == "__main__"`의 주요 목적은 다음과 같이 파일이 호출될 때 실행되는 일부 코드를 갖는 것입니다.
@@ -26,7 +26,7 @@ $ python myapp.py
from myapp import app
```
-#### 추가 세부사항
+#### 추가 세부사항 { #more-details }
파일 이름이 `myapp.py`라고 가정해 보겠습니다.
@@ -62,7 +62,7 @@ from myapp import app
# Some more code
```
-이 경우 `myapp.py` 내부의 자동 변수에는 값이 `"__main__"`인 변수 `__name__`이 없습니다.
+이 경우 `myapp.py` 내부의 자동 변수 `__name__`에는 값이 `"__main__"`이 들어가지 않습니다.
따라서 다음 행
@@ -74,11 +74,11 @@ from myapp import app
/// info | 정보
-자세한 내용은 공식 Python 문서를 확인하세요
+자세한 내용은 공식 Python 문서를 확인하세요.
///
-## 디버거로 코드 실행
+## 디버거로 코드 실행 { #run-your-code-with-your-debugger }
코드에서 직접 Uvicorn 서버를 실행하고 있기 때문에 디버거에서 직접 Python 프로그램(FastAPI 애플리케이션)을 호출할 수 있습니다.
@@ -101,7 +101,7 @@ from myapp import app
Pycharm을 사용하는 경우 다음을 수행할 수 있습니다
-* "Run" 메뉴를 엽니다
+* "Run" 메뉴를 엽니다.
* "Debug..." 옵션을 선택합니다.
* 그러면 상황에 맞는 메뉴가 나타납니다.
* 디버그할 파일을 선택합니다(이 경우 `main.py`).
diff --git a/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md
index 3e5cdcc8c8..68bba669ae 100644
--- a/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md
+++ b/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md
@@ -1,30 +1,30 @@
-# 의존성으로서의 클래스
+# 의존성으로서의 클래스 { #classes-as-dependencies }
-**의존성 주입** 시스템에 대해 자세히 살펴보기 전에 이전 예제를 업그레이드 해보겠습니다.
+**의존성 주입** 시스템에 대해 더 깊이 살펴보기 전에, 이전 예제를 업그레이드해 보겠습니다.
-## 이전 예제의 `딕셔너리`
+## 이전 예제의 `dict` { #a-dict-from-the-previous-example }
-이전 예제에서, 우리는 의존성(의존 가능한) 함수에서 `딕셔너리`객체를 반환하고 있었습니다:
+이전 예제에서는 의존성("dependable")에서 `dict`를 반환하고 있었습니다:
-{* ../../docs_src/dependencies/tutorial001.py hl[9] *}
+{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *}
-우리는 *경로 작동 함수*의 매개변수 `commons`에서 `딕셔너리` 객체를 얻습니다.
+하지만 그러면 *경로 처리 함수*의 매개변수 `commons`에서 `dict`를 받게 됩니다.
-그리고 우리는 에디터들이 `딕셔너리` 객체의 키나 밸류의 자료형을 알 수 없기 때문에 자동 완성과 같은 기능을 제공해 줄 수 없다는 것을 알고 있습니다.
+그리고 에디터는 `dict`의 키와 값 타입을 알 수 없기 때문에 `dict`에 대해서는 (완성 기능 같은) 많은 지원을 제공할 수 없다는 것을 알고 있습니다.
-더 나은 방법이 있을 것 같습니다...
+더 나은 방법이 있습니다...
-## 의존성으로 사용 가능한 것
+## 의존성이 되기 위한 조건 { #what-makes-a-dependency }
-지금까지 함수로 선언된 의존성을 봐왔습니다.
+지금까지는 함수로 선언된 의존성을 봤습니다.
-아마도 더 일반적이기는 하겠지만 의존성을 선언하는 유일한 방법은 아닙니다.
+하지만 그것만이 의존성을 선언하는 유일한 방법은 아닙니다(아마도 더 일반적이긴 하겠지만요).
-핵심 요소는 의존성이 "호출 가능"해야 한다는 것입니다
+핵심 요소는 의존성이 "호출 가능(callable)"해야 한다는 것입니다.
-파이썬에서의 "**호출 가능**"은 파이썬이 함수처럼 "호출"할 수 있는 모든 것입니다.
+파이썬에서 "**호출 가능(callable)**"이란 파이썬이 함수처럼 "호출"할 수 있는 모든 것입니다.
-따라서, 만약 당신이 `something`(함수가 아닐 수도 있음) 객체를 가지고 있고,
+따라서 `something`(함수가 _아닐_ 수도 있습니다)이라는 객체가 있고, 다음처럼 "호출"(실행)할 수 있다면:
```Python
something()
@@ -36,11 +36,11 @@ something()
something(some_argument, some_keyword_argument="foo")
```
-상기와 같은 방식으로 "호출(실행)" 할 수 있다면 "호출 가능"이 됩니다.
+그것은 "호출 가능(callable)"입니다.
-## 의존성으로서의 클래스
+## 의존성으로서의 클래스 { #classes-as-dependencies_1 }
-파이썬 클래스의 인스턴스를 생성하기 위해 사용하는 것과 동일한 문법을 사용한다는 걸 알 수 있습니다.
+파이썬 클래스의 인스턴스를 만들 때도 같은 문법을 사용한다는 것을 알 수 있을 겁니다.
예를 들어:
@@ -53,125 +53,236 @@ class Cat:
fluffy = Cat(name="Mr Fluffy")
```
-이 경우에 `fluffy`는 클래스 `Cat`의 인스턴스입니다. 그리고 우리는 `fluffy`를 만들기 위해서 `Cat`을 "호출"했습니다.
+이 경우 `fluffy`는 클래스 `Cat`의 인스턴스입니다.
-따라서, 파이썬 클래스는 **호출 가능**합니다.
+그리고 `fluffy`를 만들기 위해 `Cat`을 "호출"하고 있습니다.
-그래서 **FastAPI**에서는 파이썬 클래스를 의존성으로 사용할 수 있습니다.
+따라서 파이썬 클래스도 **호출 가능(callable)**합니다.
-FastAPI가 실질적으로 확인하는 것은 "호출 가능성"(함수, 클래스 또는 다른 모든 것)과 정의된 매개변수들입니다.
+그러면 **FastAPI**에서는 파이썬 클래스를 의존성으로 사용할 수 있습니다.
-"호출 가능"한 것을 의존성으로서 **FastAPI**에 전달하면, 그 "호출 가능"한 것의 매개변수들을 분석한 후 이를 *경로 작동 함수*를 위한 매개변수와 동일한 방식으로 처리합니다. 하위-의존성 또한 같은 방식으로 처리합니다.
+FastAPI가 실제로 확인하는 것은 그것이 "호출 가능(callable)"(함수, 클래스, 또는 다른 무엇이든)한지와 정의된 매개변수들입니다.
-매개변수가 없는 "호출 가능"한 것 역시 매개변수가 없는 *경로 작동 함수*와 동일한 방식으로 적용됩니다.
+**FastAPI**에서 "호출 가능(callable)"한 것을 의존성으로 넘기면, 그 "호출 가능(callable)"한 것의 매개변수들을 분석하고 *경로 처리 함수*의 매개변수와 동일한 방식으로 처리합니다. 하위 의존성도 포함해서요.
-그래서, 우리는 위 예제에서의 `common_paramenters` 의존성을 클래스 `CommonQueryParams`로 바꿀 수 있습니다.
+이것은 매개변수가 전혀 없는 callable에도 적용됩니다. 매개변수가 없는 *경로 처리 함수*에 적용되는 것과 동일합니다.
-{* ../../docs_src/dependencies/tutorial002.py hl[11:15] *}
+그러면 위의 의존성("dependable") `common_parameters`를 클래스 `CommonQueryParams`로 바꿀 수 있습니다:
-클래스의 인스턴스를 생성하는 데 사용되는 `__init__` 메서드에 주목하기 바랍니다:
+{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *}
-{* ../../docs_src/dependencies/tutorial002.py hl[12] *}
+클래스의 인스턴스를 만들 때 사용하는 `__init__` 메서드에 주목하세요:
-...이전 `common_parameters`와 동일한 매개변수를 가집니다:
+{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *}
-{* ../../docs_src/dependencies/tutorial001.py hl[9] *}
+...이전의 `common_parameters`와 동일한 매개변수를 가지고 있습니다:
-이 매개변수들은 **FastAPI**가 의존성을 "해결"하기 위해 사용할 것입니다
+{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *}
-함수와 클래스 두 가지 방식 모두, 아래 요소를 갖습니다:
+이 매개변수들이 **FastAPI**가 의존성을 "해결"하는 데 사용할 것들입니다.
-* `문자열`이면서 선택사항인 쿼리 매개변수 `q`.
-* 기본값이 `0`이면서 `정수형`인 쿼리 매개변수 `skip`
-* 기본값이 `100`이면서 `정수형`인 쿼리 매개변수 `limit`
+두 경우 모두 다음을 갖게 됩니다:
-두 가지 방식 모두, 데이터는 변환, 검증되고 OpenAPI 스키마에 문서화됩니다.
+* `str`인 선택적 쿼리 매개변수 `q`.
+* 기본값이 `0`인 `int` 쿼리 매개변수 `skip`.
+* 기본값이 `100`인 `int` 쿼리 매개변수 `limit`.
-## 사용해봅시다!
+두 경우 모두 데이터는 변환되고, 검증되며, OpenAPI 스키마에 문서화되는 등 여러 처리가 적용됩니다.
-이제 아래의 클래스를 이용해서 의존성을 정의할 수 있습니다.
+## 사용하기 { #use-it }
-{* ../../docs_src/dependencies/tutorial002.py hl[19] *}
+이제 이 클래스를 사용해 의존성을 선언할 수 있습니다.
-**FastAPI**는 `CommonQueryParams` 클래스를 호출합니다. 이것은 해당 클래스의 "인스턴스"를 생성하고 그 인스턴스는 함수의 매개변수 `commons`로 전달됩니다.
+{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *}
-## 타입 힌팅 vs `Depends`
+**FastAPI**는 `CommonQueryParams` 클래스를 호출합니다. 그러면 해당 클래스의 "인스턴스"가 생성되고, 그 인스턴스가 함수의 매개변수 `commons`로 전달됩니다.
-위 코드에서 `CommonQueryParams`를 두 번 작성한 방식에 주목하십시오:
+## 타입 어노테이션 vs `Depends` { #type-annotation-vs-depends }
+
+위 코드에서 `CommonQueryParams`를 두 번 작성하는 방식에 주목하세요:
+
+//// tab | Python 3.9+
+
+```Python
+commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
+```
+
+////
+
+//// tab | Python 3.9+ non-Annotated
+
+/// tip | 팁
+
+가능하다면 `Annotated` 버전을 사용하는 것을 권장합니다.
+
+///
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
-마지막 `CommonQueryParams` 변수를 보면:
+////
+
+마지막 `CommonQueryParams`는, 다음에서:
```Python
-... = Depends(CommonQueryParams)
+... Depends(CommonQueryParams)
```
-... **FastAPI**가 실제로 어떤 것이 의존성인지 알기 위해서 사용하는 방법입니다.
-FastAPI는 선언된 매개변수들을 추출할 것이고 실제로 이 변수들을 호출할 것입니다.
+...**FastAPI**가 실제로 무엇이 의존성인지 알기 위해 사용하는 것입니다.
+
+FastAPI는 여기에서 선언된 매개변수들을 추출하고, 실제로 이것을 호출합니다.
---
-이 경우에, 첫번째 `CommonQueryParams` 변수를 보면:
+이 경우 첫 번째 `CommonQueryParams`는 다음에서:
+
+//// tab | Python 3.9+
+
+```Python
+commons: Annotated[CommonQueryParams, ...
+```
+
+////
+
+//// tab | Python 3.9+ non-Annotated
+
+/// tip | 팁
+
+가능하다면 `Annotated` 버전을 사용하는 것을 권장합니다.
+
+///
```Python
commons: CommonQueryParams ...
```
-... **FastAPI**는 `CommonQueryParams` 변수에 어떠한 특별한 의미도 부여하지 않습니다. FastAPI는 이 변수를 데이터 변환, 검증 등에 활용하지 않습니다. (활용하려면 `= Depends(CommonQueryParams)`를 사용해야 합니다.)
+////
-사실 아래와 같이 작성해도 무관합니다:
+...**FastAPI**에 특별한 의미가 없습니다. FastAPI는 이를 데이터 변환, 검증 등에 사용하지 않습니다(그런 용도로는 `Depends(CommonQueryParams)`를 사용하고 있기 때문입니다).
+
+실제로는 이렇게만 작성해도 됩니다:
+
+//// tab | Python 3.9+
+
+```Python
+commons: Annotated[Any, Depends(CommonQueryParams)]
+```
+
+////
+
+//// tab | Python 3.9+ non-Annotated
+
+/// tip | 팁
+
+가능하다면 `Annotated` 버전을 사용하는 것을 권장합니다.
+
+///
```Python
commons = Depends(CommonQueryParams)
```
-..전체적인 코드는 아래와 같습니다:
+////
-{* ../../docs_src/dependencies/tutorial003.py hl[19] *}
+...다음과 같이요:
-그러나 자료형을 선언하면 에디터가 매개변수 `commons`로 전달될 것이 무엇인지 알게 되고, 이를 통해 코드 완성, 자료형 확인 등에 도움이 될 수 있으므로 권장됩니다.
+{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *}
-
+하지만 타입을 선언하는 것을 권장합니다. 그러면 에디터가 매개변수 `commons`에 무엇이 전달되는지 알 수 있고, 코드 완성, 타입 체크 등에서 도움을 받을 수 있습니다:
-## 코드 단축
+
-그러나 여기 `CommonQueryParams`를 두 번이나 작성하는, 코드 반복이 있다는 것을 알 수 있습니다:
+## 단축 { #shortcut }
+
+하지만 `CommonQueryParams`를 두 번 작성하는 코드 반복이 있다는 것을 볼 수 있습니다:
+
+//// tab | Python 3.9+
+
+```Python
+commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
+```
+
+////
+
+//// tab | Python 3.9+ non-Annotated
+
+/// tip | 팁
+
+가능하다면 `Annotated` 버전을 사용하는 것을 권장합니다.
+
+///
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
-**FastAPI**는 *특히* 의존성이 **FastAPI**가 클래스 자체의 인스턴스를 생성하기 위해 "호출"하는 클래스인 경우, 조금 더 쉬운 방법을 제공합니다.
+////
-이러한 특정한 경우에는 아래처럼 사용할 수 있습니다:
+**FastAPI**는 이런 경우를 위한 단축 방법을 제공합니다. 이때 의존성은 *특히* **FastAPI**가 "호출"해서 클래스 자체의 인스턴스를 만들도록 하는 클래스입니다.
-이렇게 쓰는 것 대신:
+이 특정한 경우에는 다음과 같이 할 수 있습니다:
+
+다음처럼 작성하는 대신:
+
+//// tab | Python 3.9+
+
+```Python
+commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
+```
+
+////
+
+//// tab | Python 3.9+ non-Annotated
+
+/// tip | 팁
+
+가능하다면 `Annotated` 버전을 사용하는 것을 권장합니다.
+
+///
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
-...이렇게 쓸 수 있습니다.:
+////
+
+...이렇게 작성합니다:
+
+//// tab | Python 3.9+
+
+```Python
+commons: Annotated[CommonQueryParams, Depends()]
+```
+
+////
+
+//// tab | Python 3.9+ non-Annotated
+
+/// tip | 팁
+
+가능하다면 `Annotated` 버전을 사용하는 것을 권장합니다.
+
+///
```Python
commons: CommonQueryParams = Depends()
```
-의존성을 매개변수의 타입으로 선언하는 경우 `Depends(CommonQueryParams)`처럼 클래스 이름 전체를 *다시* 작성하는 대신, 매개변수를 넣지 않은 `Depends()`의 형태로 사용할 수 있습니다.
+////
-아래에 같은 예제가 있습니다:
+의존성을 매개변수의 타입으로 선언하고, `Depends(CommonQueryParams)` 안에 클래스 전체를 *다시* 작성하는 대신 매개변수 없이 `Depends()`를 사용합니다.
-{* ../../docs_src/dependencies/tutorial004.py hl[19] *}
+그러면 같은 예제는 다음처럼 보일 겁니다:
-...이렇게 코드를 단축하여도 **FastAPI**는 무엇을 해야하는지 알고 있습니다.
+{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *}
+
+...그리고 **FastAPI**는 무엇을 해야 하는지 알게 됩니다.
/// tip | 팁
-만약 이것이 도움이 되기보다 더 헷갈리게 만든다면, 잊어버리십시오. 이것이 반드시 필요한 것은 아닙니다.
+도움이 되기보다 더 헷갈린다면, 무시하세요. 이건 *필수*가 아닙니다.
-이것은 단지 손쉬운 방법일 뿐입니다. 왜냐하면 **FastAPI**는 코드 반복을 최소화할 수 있는 방법을 고민하기 때문입니다.
+그저 단축 방법일 뿐입니다. **FastAPI**는 코드 반복을 최소화하도록 도와주는 것을 중요하게 생각하기 때문입니다.
///
diff --git a/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
index 4a3854cefa..39c78c0786 100644
--- a/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
+++ b/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
@@ -1,4 +1,4 @@
-# 경로 작동 데코레이터에서의 의존성
+# 경로 작동 데코레이터에서의 의존성 { #dependencies-in-path-operation-decorators }
몇몇 경우에는, *경로 작동 함수* 안에서 의존성의 반환 값이 필요하지 않습니다.
@@ -8,7 +8,7 @@
그런 경우에, `Depends`를 사용하여 *경로 작동 함수*의 매개변수로 선언하는 것보다 *경로 작동 데코레이터*에 `dependencies`의 `list`를 추가할 수 있습니다.
-## *경로 작동 데코레이터*에 `dependencies` 추가하기
+## *경로 작동 데코레이터*에 `dependencies` 추가하기 { #add-dependencies-to-the-path-operation-decorator }
*경로 작동 데코레이터*는 `dependencies`라는 선택적인 인자를 받습니다.
@@ -22,7 +22,7 @@
일부 편집기에서는 사용되지 않는 함수 매개변수를 검사하고 오류로 표시합니다.
-*경로 작동 데코레이터*에서 `dependencies`를 사용하면 편집기/도구 오류를 피하며 실행되도록 할 수 있습니다.
+*경로 작동 데코레이터*에서 이러한 `dependencies`를 사용하면 편집기/도구 오류를 피하면서도 실행되도록 할 수 있습니다.
또한 코드에서 사용되지 않는 매개변수를 보고 불필요하다고 생각할 수 있는 새로운 개발자의 혼란을 방지하는데 도움이 될 수 있습니다.
@@ -36,23 +36,23 @@
///
-## 의존성 오류와 값 반환하기
+## 의존성 오류와 값 반환하기 { #dependencies-errors-and-return-values }
평소에 사용하던대로 같은 의존성 *함수*를 사용할 수 있습니다.
-### 의존성 요구사항
+### 의존성 요구사항 { #dependency-requirements }
(헤더같은) 요청 요구사항이나 하위-의존성을 선언할 수 있습니다:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *}
-### 오류 발생시키기
+### 오류 발생시키기 { #raise-exceptions }
-다음 의존성은 기존 의존성과 동일하게 예외를 `raise`를 일으킬 수 있습니다:
+다음 의존성은 기존 의존성과 동일하게 예외를 `raise`할 수 있습니다:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *}
-### 값 반환하기
+### 값 반환하기 { #return-values }
값을 반환하거나, 그러지 않을 수 있으며 값은 사용되지 않습니다.
@@ -60,10 +60,10 @@
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *}
-## *경로 작동* 모음에 대한 의존성
+## *경로 작동* 모음에 대한 의존성 { #dependencies-for-a-group-of-path-operations }
나중에 여러 파일을 가지고 있을 수 있는 더 큰 애플리케이션을 구조화하는 법([더 큰 애플리케이션 - 여러 파일들](../../tutorial/bigger-applications.md){.internal-link target=_blank})을 읽을 때, *경로 작동* 모음에 대한 단일 `dependencies` 매개변수를 선언하는 법에 대해서 배우게 될 것입니다.
-## 전역 의존성
+## 전역 의존성 { #global-dependencies }
다음으로 각 *경로 작동*에 적용되도록 `FastAPI` 애플리케이션 전체에 의존성을 추가하는 법을 볼 것입니다.
diff --git a/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md
index ff174937de..9bf6c0693a 100644
--- a/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md
+++ b/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md
@@ -1,6 +1,6 @@
-# yield를 사용하는 의존성
+# `yield`를 사용하는 의존성 { #dependencies-with-yield }
-FastAPI는 작업 완료 후 추가 단계를 수행하는 의존성을 지원합니다.
+FastAPI는 작업 완료 후 추가 단계를 수행하는 의존성을 지원합니다.
이를 구현하려면 `return` 대신 `yield`를 사용하고, 추가로 실행할 단계 (코드)를 그 뒤에 작성하세요.
@@ -23,21 +23,21 @@ FastAPI는 하여 추가할 수 있습니다.
+사용자 정의 독점 헤더는 `X-` 접두사를 사용하여 추가할 수 있다는 점을 기억하세요.
-그러나 만약 클라이언트의 브라우저에서 볼 수 있는 사용자 정의 헤더를 가지고 있다면, 그것들을 CORS 설정([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank})에 Starlette CORS 문서에 명시된 `expose_headers` 매개변수를 이용하여 헤더들을 추가하여야합니다.
+하지만 브라우저에서 클라이언트가 볼 수 있게 하려는 사용자 정의 헤더가 있다면, Starlette의 CORS 문서에 문서화된 `expose_headers` 매개변수를 사용해 CORS 설정([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank})에 추가해야 합니다.
///
-/// note | 기술적 세부사항
+/// note | 기술 세부사항
-`from starlette.requests import request`를 사용할 수도 있습니다.
+`from starlette.requests import Request`를 사용할 수도 있습니다.
-**FastAPI**는 개발자에게 편의를 위해 이를 제공합니다. 그러나 Starlette에서 직접 파생되었습니다.
+**FastAPI**는 개발자인 여러분의 편의를 위해 이를 제공합니다. 하지만 이는 Starlette에서 직접 가져온 것입니다.
///
-### `response`의 전과 후
+### `response`의 전과 후 { #before-and-after-the-response }
-*경로 작동*을 받기 전 `request`와 함께 작동할 수 있는 코드를 추가할 수 있습니다.
+어떤 *경로 처리*가 받기 전에, `request`와 함께 실행될 코드를 추가할 수 있습니다.
-그리고 `response` 또한 생성된 후 반환되기 전에 코드를 추가 할 수 있습니다.
+또한 `response`가 생성된 후, 반환하기 전에 코드를 추가할 수도 있습니다.
-예를 들어, 요청을 수행하고 응답을 생성하는데 까지 걸린 시간 값을 가지고 있는 `X-Process-Time` 같은 사용자 정의 헤더를 추가할 수 있습니다.
+예를 들어, 요청을 처리하고 응답을 생성하는 데 걸린 시간을 초 단위로 담는 사용자 정의 헤더 `X-Process-Time`을 추가할 수 있습니다:
-{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
+{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *}
-## 다른 미들웨어
+/// tip | 팁
-미들웨어에 대한 더 많은 정보는 [숙련된 사용자 안내서: 향상된 미들웨어](../advanced/middleware.md){.internal-link target=\_blank}에서 확인할 수 있습니다.
+여기서는 이러한 사용 사례에서 더 정확할 수 있기 때문에 `time.time()` 대신 `time.perf_counter()`를 사용합니다. 🤓
-다음 부분에서 미들웨어와 함께 CORS를 어떻게 다루는지에 대해 확인할 것입니다.
+///
+
+## 여러 미들웨어 실행 순서 { #multiple-middleware-execution-order }
+
+`@app.middleware()` 데코레이터 또는 `app.add_middleware()` 메서드를 사용해 여러 미들웨어를 추가하면, 새로 추가된 각 미들웨어가 애플리케이션을 감싸 스택을 형성합니다. 마지막에 추가된 미들웨어가 *가장 바깥쪽*이고, 처음에 추가된 미들웨어가 *가장 안쪽*입니다.
+
+요청 경로에서는 *가장 바깥쪽* 미들웨어가 먼저 실행됩니다.
+
+응답 경로에서는 마지막에 실행됩니다.
+
+예를 들어:
+
+```Python
+app.add_middleware(MiddlewareA)
+app.add_middleware(MiddlewareB)
+```
+
+이 경우 실행 순서는 다음과 같습니다:
+
+* **요청**: MiddlewareB → MiddlewareA → route
+
+* **응답**: route → MiddlewareA → MiddlewareB
+
+이러한 스태킹 동작은 미들웨어가 예측 가능하고 제어 가능한 순서로 실행되도록 보장합니다.
+
+## 다른 미들웨어 { #other-middlewares }
+
+다른 미들웨어에 대한 더 많은 정보는 나중에 [숙련된 사용자 안내서: 향상된 미들웨어](../advanced/middleware.md){.internal-link target=_blank}에서 확인할 수 있습니다.
+
+다음 섹션에서 미들웨어로 CORS를 처리하는 방법을 보게 될 것입니다.
diff --git a/docs/ko/docs/tutorial/path-operation-configuration.md b/docs/ko/docs/tutorial/path-operation-configuration.md
index 81914182af..b8a87667a0 100644
--- a/docs/ko/docs/tutorial/path-operation-configuration.md
+++ b/docs/ko/docs/tutorial/path-operation-configuration.md
@@ -1,97 +1,107 @@
-# 경로 작동 설정
+# 경로 처리 설정 { #path-operation-configuration }
-*경로 작동 데코레이터*를 설정하기 위해서 전달할수 있는 몇 가지 매개변수가 있습니다.
+*경로 처리 데코레이터*를 설정하기 위해 전달할 수 있는 몇 가지 매개변수가 있습니다.
/// warning | 경고
-아래 매개변수들은 *경로 작동 함수*가 아닌 *경로 작동 데코레이터*에 직접 전달된다는 사실을 기억하십시오.
+아래 매개변수들은 *경로 처리 함수*가 아닌 *경로 처리 데코레이터*에 직접 전달된다는 사실을 기억하세요.
///
-## 응답 상태 코드
+## 응답 상태 코드 { #response-status-code }
-*경로 작동*의 응답에 사용될 (HTTP) `status_code`를 정의할수 있습니다.
+*경로 처리*의 응답에 사용될 (HTTP) `status_code`를 정의할 수 있습니다.
-`404`와 같은 `int`형 코드를 직접 전달할수 있습니다.
+`404`와 같은 `int`형 코드를 직접 전달할 수 있습니다.
-하지만 각 코드의 의미를 모른다면, `status`에 있는 단축 상수들을 사용할수 있습니다:
+하지만 각 숫자 코드가 무엇을 의미하는지 기억하지 못한다면, `status`에 있는 단축 상수들을 사용할 수 있습니다:
-{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *}
+{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *}
-각 상태 코드들은 응답에 사용되며, OpenAPI 스키마에 추가됩니다.
+해당 상태 코드는 응답에 사용되며, OpenAPI 스키마에 추가됩니다.
-/// note | 기술적 세부사항
+/// note | 기술 세부사항
다음과 같이 임포트하셔도 좋습니다. `from starlette import status`.
-**FastAPI**는 개발자 여러분의 편의를 위해서 `starlette.status`와 동일한 `fastapi.status`를 제공합니다. 하지만 Starlette에서 직접 온 것입니다.
+**FastAPI**는 개발자 여러분의 편의를 위해 `starlette.status`와 동일한 `fastapi.status`를 제공합니다. 하지만 이는 Starlette에서 직접 온 것입니다.
///
-## 태그
+## 태그 { #tags }
-(보통 단일 `str`인) `str`로 구성된 `list`와 함께 매개변수 `tags`를 전달하여, `경로 작동`에 태그를 추가할 수 있습니다:
+(보통 단일 `str`인) `str`로 구성된 `list`와 함께 매개변수 `tags`를 전달하여, *경로 처리*에 태그를 추가할 수 있습니다:
-{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *}
+{* ../../docs_src/path_operation_configuration/tutorial002_py310.py hl[15,20,25] *}
전달된 태그들은 OpenAPI의 스키마에 추가되며, 자동 문서 인터페이스에서 사용됩니다:
-## 요약과 기술
+### Enum을 사용한 태그 { #tags-with-enums }
+
+큰 애플리케이션이 있다면, **여러 태그**가 쌓이게 될 수 있고, 관련된 *경로 처리*에 항상 **같은 태그**를 사용하는지 확인하고 싶을 것입니다.
+
+이런 경우에는 태그를 `Enum`에 저장하는 것이 합리적일 수 있습니다.
+
+**FastAPI**는 일반 문자열과 동일한 방식으로 이를 지원합니다:
+
+{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *}
+
+## 요약과 설명 { #summary-and-description }
`summary`와 `description`을 추가할 수 있습니다:
-{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *}
+{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *}
-## 독스트링으로 만든 기술
+## 독스트링으로 만든 설명 { #description-from-docstring }
-설명은 보통 길어지고 여러 줄에 걸쳐있기 때문에, *경로 작동* 기술을 함수 독스트링 에 선언할 수 있습니다, 이를 **FastAPI**가 독스트링으로부터 읽습니다.
+설명은 보통 길어지고 여러 줄에 걸쳐있기 때문에, *경로 처리* 설명을 함수 docstring에 선언할 수 있으며, **FastAPI**는 그곳에서 이를 읽습니다.
-마크다운 문법으로 독스트링을 작성할 수 있습니다, 작성된 마크다운 형식의 독스트링은 (마크다운의 들여쓰기를 고려하여) 올바르게 화면에 출력됩니다.
+독스트링에는 Markdown을 작성할 수 있으며, (독스트링의 들여쓰기를 고려하여) 올바르게 해석되고 표시됩니다.
-{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *}
+{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *}
이는 대화형 문서에서 사용됩니다:
-## 응답 기술
+## 응답 설명 { #response-description }
`response_description` 매개변수로 응답에 관한 설명을 명시할 수 있습니다:
-{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *}
+{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *}
/// info | 정보
-`response_description`은 구체적으로 응답을 지칭하며, `description`은 일반적인 *경로 작동*을 지칭합니다.
+`response_description`은 구체적으로 응답을 지칭하며, `description`은 일반적인 *경로 처리*를 지칭합니다.
///
/// check | 확인
-OpenAPI는 각 *경로 작동*이 응답에 관한 설명을 요구할 것을 명시합니다.
+OpenAPI는 각 *경로 처리*가 응답에 관한 설명을 요구할 것을 명시합니다.
-따라서, 응답에 관한 설명이 없을경우, **FastAPI**가 자동으로 "성공 응답" 중 하나를 생성합니다.
+따라서, 응답에 관한 설명을 제공하지 않으면, **FastAPI**가 "Successful response" 중 하나를 자동으로 생성합니다.
///
-## 단일 *경로 작동* 지원중단
+## *경로 처리* 지원중단하기 { #deprecate-a-path-operation }
-단일 *경로 작동*을 없애지 않고 지원중단을 해야한다면, `deprecated` 매개변수를 전달하면 됩니다.
+*경로 처리*를 제거하지 않고 deprecated로 표시해야 한다면, `deprecated` 매개변수를 전달하면 됩니다:
-{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *}
+{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *}
-대화형 문서에 지원중단이라고 표시됩니다.
+대화형 문서에서 지원중단으로 명확하게 표시됩니다:
-지원중단된 경우와 지원중단 되지 않은 경우에 대한 *경로 작동*이 어떻게 보이는 지 확인하십시오.
+지원중단된 *경로 처리*와 지원중단되지 않은 *경로 처리*가 어떻게 보이는지 확인해 보세요:
-## 정리
+## 정리 { #recap }
-*경로 작동 데코레이터*에 매개변수(들)를 전달함으로 *경로 작동*을 설정하고 메타데이터를 추가할수 있습니다.
+*경로 처리 데코레이터*에 매개변수(들)를 전달하여 *경로 처리*를 설정하고 메타데이터를 쉽게 추가할 수 있습니다.
diff --git a/docs/ko/docs/tutorial/path-params-numeric-validations.md b/docs/ko/docs/tutorial/path-params-numeric-validations.md
index f21c9290ed..f2c52d4aac 100644
--- a/docs/ko/docs/tutorial/path-params-numeric-validations.md
+++ b/docs/ko/docs/tutorial/path-params-numeric-validations.md
@@ -1,116 +1,153 @@
-# 경로 매개변수와 숫자 검증
+# 경로 매개변수와 숫자 검증 { #path-parameters-and-numeric-validations }
`Query`를 사용하여 쿼리 매개변수에 더 많은 검증과 메타데이터를 선언하는 방법과 동일하게 `Path`를 사용하여 경로 매개변수에 검증과 메타데이터를 같은 타입으로 선언할 수 있습니다.
-## 경로 임포트
+## `Path` 임포트 { #import-path }
-먼저 `fastapi`에서 `Path`를 임포트합니다:
+먼저 `fastapi`에서 `Path`를 임포트하고, `Annotated`도 임포트합니다:
-{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[3] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *}
-## 메타데이터 선언
+/// info | 정보
-`Query`에 동일한 매개변수를 선언할 수 있습니다.
+FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이를 권장하기 시작했습니다).
-예를 들어, `title` 메타데이터 값을 경로 매개변수 `item_id`에 선언하려면 다음과 같이 입력할 수 있습니다:
+더 오래된 버전이 있다면 `Annotated`를 사용하려고 할 때 오류가 발생합니다.
-{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[10] *}
-
-/// note | 참고
-
-경로 매개변수는 경로의 일부여야 하므로 언제나 필수적입니다.
-
-즉, `...`로 선언해서 필수임을 나타내는게 좋습니다.
-
-그럼에도 `None`으로 선언하거나 기본값을 지정할지라도 아무 영향을 끼치지 않으며 언제나 필수입니다.
+`Annotated`를 사용하기 전에 최소 0.95.1까지 [FastAPI 버전 업그레이드](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}를 꼭 하세요.
///
-## 필요한 경우 매개변수 정렬하기
+## 메타데이터 선언 { #declare-metadata }
+
+`Query`에 동일한 매개변수를 선언할 수 있습니다.
+
+예를 들어, 경로 매개변수 `item_id`에 `title` 메타데이터 값을 선언하려면 다음과 같이 입력할 수 있습니다:
+
+{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *}
+
+/// note | 참고
+
+경로 매개변수는 경로의 일부여야 하므로 언제나 필수입니다. `None`으로 선언하거나 기본값을 지정하더라도 아무 영향이 없으며, 항상 필수입니다.
+
+///
+
+## 필요한 대로 매개변수 정렬하기 { #order-the-parameters-as-you-need }
+
+/// tip | 팁
+
+`Annotated`를 사용한다면 이것은 아마 그렇게 중요하지 않거나 필요하지 않을 수 있습니다.
+
+///
`str` 형인 쿼리 매개변수 `q`를 필수로 선언하고 싶다고 해봅시다.
-해당 매개변수에 대해 아무런 선언을 할 필요가 없으므로 `Query`를 정말로 써야할 필요는 없습니다.
+해당 매개변수에 대해 아무런 선언을 할 필요가 없으므로 `Query`를 정말로 써야 할 필요는 없습니다.
-하지만 `item_id` 경로 매개변수는 여전히 `Path`를 사용해야 합니다.
+하지만 `item_id` 경로 매개변수는 여전히 `Path`를 사용해야 합니다. 그리고 어떤 이유로 `Annotated`를 사용하고 싶지 않다고 해봅시다.
-파이썬은 "기본값"이 없는 값 앞에 "기본값"이 있는 값을 입력하면 불평합니다.
+파이썬은 "기본값"이 있는 값을 "기본값"이 없는 값 앞에 두면 불평합니다.
-그러나 매개변수들을 재정렬함으로써 기본값(쿼리 매개변수 `q`)이 없는 값을 처음 부분에 위치 할 수 있습니다.
+하지만 순서를 재정렬해서 기본값이 없는 값(쿼리 매개변수 `q`)을 앞에 둘 수 있습니다.
-**FastAPI**에서는 중요하지 않습니다. 이름, 타입 그리고 선언구(`Query`, `Path` 등)로 매개변수를 감지하며 순서는 신경 쓰지 않습니다.
+**FastAPI**에서는 중요하지 않습니다. 이름, 타입 그리고 기본값 선언(`Query`, `Path` 등)로 매개변수를 감지하며 순서는 신경 쓰지 않습니다.
-따라서 함수를 다음과 같이 선언 할 수 있습니다:
+따라서 함수를 다음과 같이 선언할 수 있습니다:
-{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *}
-## 필요한 경우 매개변수 정렬하기, 트릭
+하지만 `Annotated`를 사용하면 이 문제가 없다는 점을 기억하세요. `Query()`나 `Path()`에 함수 매개변수 기본값을 사용하지 않기 때문에, 순서는 중요하지 않습니다.
-`Query`나 아무런 기본값으로도 `q` 경로 매개변수를 선언하고 싶지 않지만 `Path`를 사용하여 경로 매개변수를 `item_id` 다른 순서로 선언하고 싶다면, 파이썬은 이를 위한 작고 특별한 문법이 있습니다.
+{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
-`*`를 함수의 첫 번째 매개변수로 전달하세요.
+## 필요한 대로 매개변수 정렬하기, 트릭 { #order-the-parameters-as-you-need-tricks }
-파이썬은 `*`으로 아무런 행동도 하지 않지만, 따르는 매개변수들은 kwargs로도 알려진 키워드 인자(키-값 쌍)여야 함을 인지합니다. 기본값을 가지고 있지 않더라도 그렇습니다.
+/// tip | 팁
-{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
+`Annotated`를 사용한다면 이것은 아마 그렇게 중요하지 않거나 필요하지 않을 수 있습니다.
-## 숫자 검증: 크거나 같음
+///
-`Query`와 `Path`(나중에 볼 다른 것들도)를 사용하여 문자열 뿐만 아니라 숫자의 제약을 선언할 수 있습니다.
+유용할 수 있는 **작은 트릭**이 하나 있지만, 자주 필요하진 않을 겁니다.
-여기서 `ge=1`인 경우, `item_id`는 `1`보다 "크거나(`g`reater) 같은(`e`qual)" 정수형 숫자여야 합니다.
+만약 다음을 원한다면:
-{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *}
+* `Query`나 어떤 기본값 없이 쿼리 매개변수 `q`를 선언하기
+* `Path`를 사용해서 경로 매개변수 `item_id`를 선언하기
+* 이들을 다른 순서로 두기
+* `Annotated`를 사용하지 않기
-## 숫자 검증: 크거나 같음 및 작거나 같음
+...이를 위해 파이썬에는 작은 특별한 문법이 있습니다.
+
+함수의 첫 번째 매개변수로 `*`를 전달하세요.
+
+파이썬은 `*`으로 아무것도 하지 않지만, 뒤따르는 모든 매개변수는 키워드 인자(키-값 쌍)로 호출되어야 함을 알게 됩니다. 이는 kwargs로도 알려져 있습니다. 기본값이 없더라도 마찬가지입니다.
+
+{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
+
+### `Annotated`를 쓰면 더 좋습니다 { #better-with-annotated }
+
+`Annotated`를 사용하면 함수 매개변수 기본값을 사용하지 않기 때문에 이 문제가 발생하지 않으며, 아마 `*`도 사용할 필요가 없다는 점을 기억하세요.
+
+{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
+
+## 숫자 검증: 크거나 같음 { #number-validations-greater-than-or-equal }
+
+`Query`와 `Path`(그리고 나중에 볼 다른 것들)를 사용하여 숫자 제약을 선언할 수 있습니다.
+
+여기서 `ge=1`인 경우, `item_id`는 `1`보다 "`g`reater than or `e`qual"(크거나 같은) 정수형 숫자여야 합니다.
+
+{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
+
+## 숫자 검증: 크거나 및 작거나 같음 { #number-validations-greater-than-and-less-than-or-equal }
동일하게 적용됩니다:
-* `gt`: 크거나(`g`reater `t`han)
-* `le`: 작거나 같은(`l`ess than or `e`qual)
+* `gt`: `g`reater `t`han
+* `le`: `l`ess than or `e`qual
-{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
-## 숫자 검증: 부동소수, 크거나 및 작거나
+## 숫자 검증: 부동소수, 크거나 및 작거나 { #number-validations-floats-greater-than-and-less-than }
숫자 검증은 `float` 값에도 동작합니다.
-여기에서 ge뿐만 아니라 gt를 선언 할 수있는 것이 중요해집니다. 예를 들어 필요한 경우, 값이 `1`보다 작더라도 반드시 `0`보다 커야합니다.
+여기에서 gt를, ge뿐만 아니라 선언할 수 있다는 점이 중요해집니다. 예를 들어 값이 `1`보다 작더라도, 반드시 `0`보다 커야 한다고 요구할 수 있습니다.
즉, `0.5`는 유효한 값입니다. 그러나 `0.0` 또는 `0`은 그렇지 않습니다.
lt 역시 마찬가지입니다.
-{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *}
-## 요약
+## 요약 { #recap }
`Query`, `Path`(아직 보지 못한 다른 것들도)를 사용하면 [쿼리 매개변수와 문자열 검증](query-params-str-validations.md){.internal-link target=_blank}에서와 마찬가지로 메타데이터와 문자열 검증을 선언할 수 있습니다.
그리고 숫자 검증 또한 선언할 수 있습니다:
-* `gt`: 크거나(`g`reater `t`han)
-* `ge`: 크거나 같은(`g`reater than or `e`qual)
-* `lt`: 작거나(`l`ess `t`han)
-* `le`: 작거나 같은(`l`ess than or `e`qual)
+* `gt`: `g`reater `t`han
+* `ge`: `g`reater than or `e`qual
+* `lt`: `l`ess `t`han
+* `le`: `l`ess than or `e`qual
/// info | 정보
-`Query`, `Path`, 그리고 나중에게 보게될 것들은 (여러분이 사용할 필요가 없는) 공통 `Param` 클래스의 서브 클래스입니다.
+`Query`, `Path`, 그리고 나중에 보게 될 다른 클래스들은 공통 `Param` 클래스의 서브클래스입니다.
-그리고 이들 모두는 여태까지 본 추가 검증과 메타데이터의 동일한 모든 매개변수를 공유합니다.
+이들 모두는 여러분이 본 추가 검증과 메타데이터에 대한 동일한 매개변수를 공유합니다.
///
/// note | 기술 세부사항
-`fastapi`에서 `Query`, `Path` 등을 임포트 할 때, 이것들은 실제로 함수입니다.
+`fastapi`에서 `Query`, `Path` 등을 임포트할 때, 이것들은 실제로 함수입니다.
호출되면 동일한 이름의 클래스의 인스턴스를 반환합니다.
즉, 함수인 `Query`를 임포트한 겁니다. 그리고 호출하면 `Query`라는 이름을 가진 클래스의 인스턴스를 반환합니다.
-편집기에서 타입에 대한 오류를 표시하지 않도록 하기 위해 (클래스를 직접 사용하는 대신) 이러한 함수들이 있습니다.
+이 함수들이 있는 이유는(클래스를 직접 사용하는 대신) 편집기에서 타입에 대한 오류를 표시하지 않도록 하기 위해서입니다.
이렇게 하면 오류를 무시하기 위한 사용자 설정을 추가하지 않고도 일반 편집기와 코딩 도구를 사용할 수 있습니다.
diff --git a/docs/ko/docs/tutorial/path-params.md b/docs/ko/docs/tutorial/path-params.md
index b72787e0b2..ea5170eccb 100644
--- a/docs/ko/docs/tutorial/path-params.md
+++ b/docs/ko/docs/tutorial/path-params.md
@@ -1,8 +1,8 @@
-# 경로 매개변수
+# 경로 매개변수 { #path-parameters }
파이썬의 포맷 문자열 리터럴에서 사용되는 문법을 이용하여 경로 "매개변수" 또는 "변수"를 선언할 수 있습니다:
-{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
+{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
경로 매개변수 `item_id`의 값은 함수의 `item_id` 인자로 전달됩니다.
@@ -12,11 +12,11 @@
{"item_id":"foo"}
```
-## 타입이 있는 매개변수
+## 타입이 있는 경로 매개변수 { #path-parameters-with-types }
파이썬 표준 타입 어노테이션을 사용하여 함수에 있는 경로 매개변수의 타입을 선언할 수 있습니다:
-{* ../../docs_src/path_params/tutorial002.py hl[7] *}
+{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *}
위의 예시에서, `item_id`는 `int`로 선언되었습니다.
@@ -26,7 +26,7 @@
///
-## 데이터 변환
+## 데이터 변환 { #data-conversion }
이 예제를 실행하고 http://127.0.0.1:8000/items/3을 열면, 다음 응답을 볼 수 있습니다:
@@ -42,40 +42,41 @@
///
-## 데이터 검증
+## 데이터 검증 { #data-validation }
-하지만 브라우저에서 http://127.0.0.1:8000/items/foo로 이동하면, HTTP 오류가 잘 뜨는 것을 확인할 수 있습니다:
+하지만 브라우저에서 http://127.0.0.1:8000/items/foo로 이동하면, 다음과 같은 HTTP 오류를 볼 수 있습니다:
```JSON
{
- "detail": [
- {
- "loc": [
- "path",
- "item_id"
- ],
- "msg": "value is not a valid integer",
- "type": "type_error.integer"
- }
- ]
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": [
+ "path",
+ "item_id"
+ ],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo"
+ }
+ ]
}
```
-경로 매개변수 `item_id`는 `int`가 아닌 `"foo"` 값이기 때문입니다.
+경로 매개변수 `item_id`가 `int`가 아닌 `"foo"` 값을 가졌기 때문입니다.
-`int`가 아닌 `float`을 전달하는 경우에도 동일한 오류가 나타납니다: http://127.0.0.1:8000/items/4.2
+`int` 대신 `float`을 제공하면(예: http://127.0.0.1:8000/items/4.2) 동일한 오류가 나타납니다.
/// check | 확인
즉, 파이썬 타입 선언을 하면 **FastAPI**는 데이터 검증을 합니다.
-오류에는 정확히 어느 지점에서 검증을 통과하지 못했는지 명시됩니다.
+또한 오류에는 검증을 통과하지 못한 지점이 정확히 명시됩니다.
이는 API와 상호 작용하는 코드를 개발하고 디버깅하는 데 매우 유용합니다.
///
-## 문서화
+## 문서화 { #documentation }
그리고 브라우저에서 http://127.0.0.1:8000/docs를 열면, 다음과 같이 자동 대화식 API 문서를 볼 수 있습니다:
@@ -83,23 +84,23 @@
/// check | 확인
-그저 파이썬 타입 선언을 하기만 하면 **FastAPI**는 자동 대화형 API 문서(Swagger UI)를 제공합니다.
+다시 한 번, 동일한 파이썬 타입 선언만으로 **FastAPI**는 자동 대화형 문서(Swagger UI 통합)를 제공합니다.
-경로 매개변수가 정수형으로 명시된 것을 확인할 수 있습니다.
+경로 매개변수가 정수형으로 선언된 것을 확인할 수 있습니다.
///
-## 표준 기반의 이점, 대체 문서
+## 표준 기반의 이점, 대체 문서 { #standards-based-benefits-alternative-documentation }
-그리고 생성된 스키마는 OpenAPI 표준에서 나온 것이기 때문에 호환되는 도구가 많이 있습니다.
+그리고 생성된 스키마는 OpenAPI 표준에서 나온 것이기 때문에 호환되는 도구가 많이 있습니다.
-이 덕분에 **FastAPI**는 http://127.0.0.1:8000/redoc로 접속할 수 있는 (ReDoc을 사용하는) 대체 API 문서를 제공합니다:
+이 덕분에 **FastAPI** 자체에서 http://127.0.0.1:8000/redoc로 접속할 수 있는 (ReDoc을 사용하는) 대체 API 문서를 제공합니다:
이와 마찬가지로 다양한 언어에 대한 코드 생성 도구를 포함하여 여러 호환되는 도구가 있습니다.
-## Pydantic
+## Pydantic { #pydantic }
모든 데이터 검증은 Pydantic에 의해 내부적으로 수행되므로 이로 인한 이점을 모두 얻을 수 있습니다. 여러분은 관리를 잘 받고 있음을 느낄 수 있습니다.
@@ -107,25 +108,31 @@
이 중 몇 가지는 자습서의 다음 장에 설명되어 있습니다.
-## 순서 문제
+## 순서 문제 { #order-matters }
-*경로 작동*을 만들때 고정 경로를 갖고 있는 상황들을 맞닥뜨릴 수 있습니다.
+*경로 처리*를 만들 때 고정 경로를 갖고 있는 상황들을 맞닥뜨릴 수 있습니다.
`/users/me`처럼, 현재 사용자의 데이터를 가져온다고 합시다.
사용자 ID를 이용해 특정 사용자의 정보를 가져오는 경로 `/users/{user_id}`도 있습니다.
-*경로 작동*은 순차적으로 실행되기 때문에 `/users/{user_id}` 이전에 `/users/me`를 먼저 선언해야 합니다:
+*경로 처리*는 순차적으로 평가되기 때문에 `/users/{user_id}` 이전에 `/users/me`에 대한 경로가 먼저 선언되었는지 확인해야 합니다:
-{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
+{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *}
-그렇지 않으면 `/users/{user_id}`는 `/users/me` 요청 또한 매개변수 `user_id`의 값이 `"me"`인 것으로 "생각하게" 됩니다.
+그렇지 않으면 `/users/{user_id}`에 대한 경로가 `/users/me`에도 매칭되어, 매개변수 `user_id`에 `"me"` 값이 들어왔다고 "생각하게" 됩니다.
-## 사전정의 값
+마찬가지로, 경로 처리를 재정의할 수는 없습니다:
-만약 *경로 매개변수*를 받는 *경로 작동*이 있지만, *경로 매개변수*로 가능한 값들을 미리 정의하고 싶다면 파이썬 표준 `Enum`을 사용할 수 있습니다.
+{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *}
-### `Enum` 클래스 생성
+경로가 먼저 매칭되기 때문에 첫 번째 것이 항상 사용됩니다.
+
+## 사전정의 값 { #predefined-values }
+
+만약 *경로 매개변수*를 받는 *경로 처리*가 있지만, 가능한 유효한 *경로 매개변수* 값들을 미리 정의하고 싶다면 파이썬 표준 `Enum`을 사용할 수 있습니다.
+
+### `Enum` 클래스 생성 { #create-an-enum-class }
`Enum`을 임포트하고 `str`과 `Enum`을 상속하는 서브 클래스를 만듭니다.
@@ -133,47 +140,41 @@
가능한 값들에 해당하는 고정된 값의 클래스 어트리뷰트들을 만듭니다:
-{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
-
-/// info | 정보
-
-열거형(또는 enums)은 파이썬 버전 3.4 이후로 사용 가능합니다.
-
-///
+{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *}
/// tip | 팁
-혹시 궁금하다면, "AlexNet", "ResNet", 그리고 "LeNet"은 그저 기계 학습 모델들의 이름입니다.
+혹시 궁금하다면, "AlexNet", "ResNet", 그리고 "LeNet"은 그저 머신 러닝 모델들의 이름입니다.
///
-### *경로 매개변수* 선언
+### *경로 매개변수* 선언 { #declare-a-path-parameter }
생성한 열거형 클래스(`ModelName`)를 사용하는 타입 어노테이션으로 *경로 매개변수*를 만듭니다:
-{* ../../docs_src/path_params/tutorial005.py hl[16] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
-### 문서 확인
+### 문서 확인 { #check-the-docs }
*경로 매개변수*에 사용할 수 있는 값은 미리 정의되어 있으므로 대화형 문서에서 잘 표시됩니다:
-### 파이썬 *열거형*으로 작업하기
+### 파이썬 *열거형*으로 작업하기 { #working-with-python-enumerations }
*경로 매개변수*의 값은 *열거형 멤버*가 됩니다.
-#### *열거형 멤버* 비교
+#### *열거형 멤버* 비교 { #compare-enumeration-members }
-열거형 `ModelName`의 *열거형 멤버*를 비교할 수 있습니다:
+생성한 열거형 `ModelName`의 *열거형 멤버*와 비교할 수 있습니다:
-{* ../../docs_src/path_params/tutorial005.py hl[17] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *}
-#### *열거형 값* 가져오기
+#### *열거형 값* 가져오기 { #get-the-enumeration-value }
`model_name.value` 또는 일반적으로 `your_enum_member.value`를 이용하여 실제 값(위 예시의 경우 `str`)을 가져올 수 있습니다:
-{* ../../docs_src/path_params/tutorial005.py hl[20] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *}
/// tip | 팁
@@ -181,15 +182,15 @@
///
-#### *열거형 멤버* 반환
+#### *열거형 멤버* 반환 { #return-enumeration-members }
-*경로 작동*에서 *열거형 멤버*를 반환할 수 있습니다. 이는 중첩 JSON 본문(예: `dict`)내의 값으로도 가능합니다.
+*경로 처리*에서 *enum 멤버*를 반환할 수 있습니다. 이는 JSON 본문(예: `dict`) 내에 중첩된 형태로도 가능합니다.
클라이언트에 반환하기 전에 해당 값(이 경우 문자열)으로 변환됩니다:
-{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *}
-클라이언트는 아래의 JSON 응답을 얻습니다:
+클라이언트는 아래와 같은 JSON 응답을 얻게 됩니다:
```JSON
{
@@ -198,53 +199,53 @@
}
```
-## 경로를 포함하는 경로 매개변수
+## 경로를 포함하는 경로 매개변수 { #path-parameters-containing-paths }
-경로를 포함하는 *경로 작동* `/files/{file_path}`이 있다고 해봅시다.
+경로 `/files/{file_path}`를 가진 *경로 처리*가 있다고 해봅시다.
-그런데 이 경우 `file_path` 자체가 `home/johndoe/myfile.txt`와 같은 경로를 포함해야 합니다.
+하지만 `file_path` 자체가 `home/johndoe/myfile.txt`와 같은 *경로*를 포함해야 합니다.
이때 해당 파일의 URL은 다음처럼 됩니다: `/files/home/johndoe/myfile.txt`.
-### OpenAPI 지원
+### OpenAPI 지원 { #openapi-support }
테스트와 정의가 어려운 시나리오로 이어질 수 있으므로 OpenAPI는 *경로*를 포함하는 *경로 매개변수*를 내부에 선언하는 방법을 지원하지 않습니다.
-그럼에도 Starlette의 내부 도구중 하나를 사용하여 **FastAPI**에서는 이가 가능합니다.
+그럼에도 Starlette의 내부 도구 중 하나를 사용하여 **FastAPI**에서는 이가 가능합니다.
-문서에 매개변수에 경로가 포함되어야 한다는 정보가 명시되지는 않지만 여전히 작동합니다.
+또한 문서가 여전히 동작하긴 하지만, 매개변수에 경로가 포함되어야 한다는 내용을 추가로 문서화하지는 않습니다.
-### 경로 변환기
+### 경로 변환기 { #path-convertor }
-Starlette의 옵션을 직접 이용하여 다음과 같은 URL을 사용함으로써 *path*를 포함하는 *경로 매개변수*를 선언할 수 있습니다:
+Starlette의 옵션을 직접 이용하여 다음과 같은 URL을 사용함으로써 *경로*를 포함하는 *경로 매개변수*를 선언할 수 있습니다:
```
/files/{file_path:path}
```
-이러한 경우 매개변수의 이름은 `file_path`이며, 마지막 부분 `:path`는 매개변수가 *경로*와 일치해야 함을 명시합니다.
+이러한 경우 매개변수의 이름은 `file_path`이며, 마지막 부분 `:path`는 매개변수가 어떤 *경로*와도 매칭되어야 함을 의미합니다.
따라서 다음과 같이 사용할 수 있습니다:
-{* ../../docs_src/path_params/tutorial004.py hl[6] *}
+{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *}
/// tip | 팁
-매개변수가 가져야 하는 값이 `/home/johndoe/myfile.txt`와 같이 슬래시로 시작(`/`)해야 할 수 있습니다.
+매개변수가 선행 슬래시(`/`)가 있는 `/home/johndoe/myfile.txt`를 포함해야 할 수도 있습니다.
-이 경우 URL은: `/files//home/johndoe/myfile.txt`이며 `files`과 `home` 사이에 이중 슬래시(`//`)가 생깁니다.
+그 경우 URL은: `/files//home/johndoe/myfile.txt`이며 `files`와 `home` 사이에 이중 슬래시(`//`)가 생깁니다.
///
-## 요약
+## 요약 { #recap }
**FastAPI**를 이용하면 짧고 직관적인 표준 파이썬 타입 선언을 사용하여 다음을 얻을 수 있습니다:
* 편집기 지원: 오류 검사, 자동완성 등
-* 데이터 "파싱"
+* 데이터 "parsing"
* 데이터 검증
* API 주석(Annotation)과 자동 문서
-단 한번의 선언만으로 위 사항들을 모두 선언할 수 있습니다.
+그리고 한 번만 선언하면 됩니다.
이는 대체 프레임워크와 비교했을 때 (엄청나게 빠른 성능 외에도) **FastAPI**의 주요한 장점일 것입니다.
diff --git a/docs/ko/docs/tutorial/query-param-models.md b/docs/ko/docs/tutorial/query-param-models.md
index 2ca65a3319..d354c56c3d 100644
--- a/docs/ko/docs/tutorial/query-param-models.md
+++ b/docs/ko/docs/tutorial/query-param-models.md
@@ -1,48 +1,48 @@
-# 쿼리 매개변수 모델
+# 쿼리 매개변수 모델 { #query-parameter-models }
-연관된 쿼리 **매개변수** 그룹이 있다면 **Pydantic 모델** 을 사용해 선언할 수 있습니다.
+연관된 **쿼리 매개변수** 그룹이 있다면 이를 선언하기 위해 **Pydantic 모델**을 생성할 수 있습니다.
이렇게 하면 **여러 곳**에서 **모델을 재사용**할 수 있을 뿐만 아니라, 매개변수에 대한 검증 및 메타데이터도 한 번에 선언할 수 있습니다. 😎
/// note | 참고
-이 기능은 FastAPI 버전 `0.115.0`부터 제공됩니다. 🤓
+이 기능은 FastAPI 버전 `0.115.0`부터 지원됩니다. 🤓
///
-## 쿼리 매개변수와 Pydantic 모델
+## Pydantic 모델과 쿼리 매개변수 { #query-parameters-with-a-pydantic-model }
-필요한 **쿼리 매개변수**를 **Pydantic 모델** 안에 선언한 다음, 모델을 `Query`로 선언합니다.
+필요한 **쿼리 매개변수**를 **Pydantic 모델** 안에 선언한 다음, 매개변수를 `Query`로 선언합니다:
{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *}
-**FastAPI**는 요청의 **쿼리 매개변수**에서 **각 필드**의 데이터를 **추출**해 정의한 Pydantic 모델로 제공합니다.
+**FastAPI**는 요청의 **쿼리 매개변수**에서 **각 필드**의 데이터를 **추출**해 정의한 Pydantic 모델을 제공합니다.
-## 문서 확인하기
+## 문서 확인하기 { #check-the-docs }
-`/docs` 경로의 API 문서에서 매개변수를 확인할 수 있습니다.
+`/docs`의 문서 UI에서 쿼리 매개변수를 확인할 수 있습니다:
-### 쿼리 매개변수 리스트 / 기본값을 사용하는 다중값
+### 쿼리 매개변수 리스트 / 기본값이 있는 다중값 { #query-parameter-list-multiple-values-with-defaults }
-그리고 제공된 값이 없으면 기본 `list` 값을 정의할 수도 있습니다:
+제공된 값이 없으면 기본 `list` 값을 정의할 수도 있습니다:
-{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *}
+{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
-아래로 이동한다면:
+다음으로 이동하면:
```
http://localhost:8000/items/
```
-`q`의 기본값은: `["foo", "bar"]`이며 응답은 다음이 됩니다:
+`q`의 기본값은 `["foo", "bar"]`가 되고, 응답은 다음이 됩니다:
```JSON
{
@@ -204,21 +312,21 @@ http://localhost:8000/items/
}
```
-#### `list` 사용하기
+#### `list`만 사용하기 { #using-just-list }
-`List[str]` 대신 `list`를 직접 사용할 수도 있습니다:
+`list[str]` 대신 `list`를 직접 사용할 수도 있습니다:
-{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *}
+{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
/// note | 참고
-이 경우 FastAPI는 리스트의 내용을 검사하지 않음을 명심하기 바랍니다.
+이 경우 FastAPI는 리스트의 내용을 검사하지 않음을 명심하세요.
-예를 들어, `List[int]`는 리스트 내용이 정수인지 검사(및 문서화)합니다. 하지만 `list` 단독일 경우는 아닙니다.
+예를 들어, `list[int]`는 리스트 내용이 정수인지 검사(및 문서화)합니다. 하지만 `list` 단독일 경우는 아닙니다.
///
-## 더 많은 메타데이터 선언
+## 더 많은 메타데이터 선언 { #declare-more-metadata }
매개변수에 대한 정보를 추가할 수 있습니다.
@@ -226,7 +334,7 @@ http://localhost:8000/items/
/// note | 참고
-도구에 따라 OpenAPI 지원 수준이 다를 수 있음을 명심하기 바랍니다.
+도구에 따라 OpenAPI 지원 수준이 다를 수 있음을 명심하세요.
일부는 아직 선언된 추가 정보를 모두 표시하지 않을 수 있지만, 대부분의 경우 누락된 기능은 이미 개발 계획이 있습니다.
@@ -234,13 +342,13 @@ http://localhost:8000/items/
`title`을 추가할 수 있습니다:
-{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *}
+{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *}
그리고 `description`도 추가할 수 있습니다:
-{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *}
+{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
-## 별칭 매개변수
+## 별칭 매개변수 { #alias-parameters }
매개변수가 `item-query`이길 원한다고 가정해 봅시다.
@@ -250,31 +358,99 @@ http://localhost:8000/items/
http://127.0.0.1:8000/items/?item-query=foobaritems
```
-그러나 `item-query`은 유효한 파이썬 변수 이름이 아닙니다.
+그러나 `item-query`는 유효한 파이썬 변수 이름이 아닙니다.
가장 가까운 것은 `item_query`일 겁니다.
-하지만 정확히`item-query`이길 원합니다...
+하지만 정확히 `item-query`이길 원합니다...
이럴 경우 `alias`를 선언할 수 있으며, 해당 별칭은 매개변수 값을 찾는 데 사용됩니다:
-{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *}
+{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
-## 매개변수 사용하지 않게 하기
+## 매개변수 사용 중단하기 { #deprecating-parameters }
-이제는 더이상 이 매개변수를 마음에 들어하지 않는다고 가정해 봅시다.
+이제는 더 이상 이 매개변수를 마음에 들어하지 않는다고 가정해 봅시다.
-이 매개변수를 사용하는 클라이언트가 있기 때문에 한동안은 남겨둬야 하지만, 사용되지 않는다(deprecated)고 확실하게 문서에서 보여주고 싶습니다.
+이 매개변수를 사용하는 클라이언트가 있기 때문에 한동안은 남겨둬야 하지만, 문서에서 deprecated로 명확하게 보여주고 싶습니다.
그렇다면 `deprecated=True` 매개변수를 `Query`로 전달합니다:
-{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *}
+{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *}
문서가 아래와 같이 보일겁니다:
-## 요약
+## OpenAPI에서 매개변수 제외 { #exclude-parameters-from-openapi }
+
+생성된 OpenAPI 스키마(따라서 자동 문서화 시스템)에서 쿼리 매개변수를 제외하려면 `Query`의 `include_in_schema` 매개변수를 `False`로 설정하세요:
+
+{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *}
+
+## 커스텀 검증 { #custom-validation }
+
+위에 나온 매개변수들로는 할 수 없는 **커스텀 검증**이 필요한 경우가 있을 수 있습니다.
+
+그런 경우에는 일반적인 검증(예: 값이 `str`인지 검증한 뒤) 이후에 적용되는 **커스텀 검증 함수**를 사용할 수 있습니다.
+
+`Annotated` 안에서 Pydantic의 `AfterValidator`를 사용하면 이를 구현할 수 있습니다.
+
+/// tip | 팁
+
+Pydantic에는 `BeforeValidator`와 같은 다른 것들도 있습니다. 🤓
+
+///
+
+예를 들어, 이 커스텀 validator는 ISBN 도서 번호의 경우 아이템 ID가 `isbn-`으로 시작하고, IMDB 영화 URL ID의 경우 `imdb-`로 시작하는지 확인합니다:
+
+{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
+
+/// info | 정보
+
+이는 Pydantic 2 이상 버전에서 사용할 수 있습니다. 😎
+
+///
+
+/// tip | 팁
+
+데이터베이스나 다른 API 같은 **외부 구성요소**와 통신이 필요한 어떤 종류의 검증이든 해야 한다면, 대신 **FastAPI Dependencies**를 사용해야 합니다. 이에 대해서는 나중에 배우게 됩니다.
+
+이 커스텀 validator는 요청에서 제공된 **같은 데이터만**으로 확인할 수 있는 것들을 위한 것입니다.
+
+///
+
+### 코드 이해하기 { #understand-that-code }
+
+중요한 부분은 **`Annotated` 안에서 함수와 함께 `AfterValidator`를 사용한다는 것**뿐입니다. 이 부분은 건너뛰셔도 됩니다. 🤸
+
+---
+
+하지만 이 특정 코드 예제가 궁금하고 계속 보고 싶다면, 추가 세부사항은 다음과 같습니다.
+
+#### `value.startswith()`를 사용한 문자열 { #string-with-value-startswith }
+
+알고 계셨나요? `value.startswith()`를 사용하는 문자열은 튜플을 받을 수 있으며, 튜플에 있는 각 값을 확인합니다:
+
+{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *}
+
+#### 임의의 항목 { #a-random-item }
+
+`data.items()`를 사용하면 각 딕셔너리 항목의 키와 값을 담은 튜플로 구성된 iterable object를 얻습니다.
+
+이 iterable object를 `list(data.items())`로 적절한 `list`로 변환합니다.
+
+그 다음 `random.choice()`로 리스트에서 **무작위 값**을 얻어 `(id, name)` 형태의 튜플을 얻습니다. 예를 들어 `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")` 같은 값이 될 것입니다.
+
+그 다음 이 튜플의 **두 값을** 변수 `id`와 `name`에 **할당**합니다.
+
+따라서 사용자가 아이템 ID를 제공하지 않더라도, 무작위 추천을 받게 됩니다.
+
+...이 모든 것을 **단 하나의 간단한 줄**로 합니다. 🤯 Python 정말 좋지 않나요? 🐍
+
+{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *}
+
+## 요약 { #recap }
매개변수에 검증과 메타데이터를 추가 선언할 수 있습니다.
@@ -285,12 +461,14 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
* `description`
* `deprecated`
-특정 문자열 검증:
+문자열에 특화된 검증:
* `min_length`
* `max_length`
-* `regex`
+* `pattern`
+
+`AfterValidator`를 사용하는 커스텀 검증.
예제에서 `str` 값의 검증을 어떻게 추가하는지 살펴보았습니다.
-숫자와 같은 다른 자료형에 대한 검증을 어떻게 선언하는지 확인하려면 다음 장을 확인하기 바랍니다.
+숫자와 같은 다른 타입에 대한 검증을 어떻게 선언하는지 확인하려면 다음 장을 확인하기 바랍니다.
diff --git a/docs/ko/docs/tutorial/query-params.md b/docs/ko/docs/tutorial/query-params.md
index d5b9837c4e..5124f73bf5 100644
--- a/docs/ko/docs/tutorial/query-params.md
+++ b/docs/ko/docs/tutorial/query-params.md
@@ -1,8 +1,8 @@
-# 쿼리 매개변수
+# 쿼리 매개변수 { #query-parameters }
경로 매개변수의 일부가 아닌 다른 함수 매개변수를 선언하면 "쿼리" 매개변수로 자동 해석합니다.
-{* ../../docs_src/query_params/tutorial001.py hl[9] *}
+{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *}
쿼리는 URL에서 `?` 후에 나오고 `&`으로 구분되는 키-값 쌍의 집합입니다.
@@ -24,11 +24,11 @@ URL의 일부이므로 "자연스럽게" 문자열입니다.
경로 매개변수에 적용된 동일한 프로세스가 쿼리 매개변수에도 적용됩니다:
* (당연히) 편집기 지원
-* 데이터 "파싱"
+* 데이터 "파싱"
* 데이터 검증
* 자동 문서화
-## 기본값
+## 기본값 { #defaults }
쿼리 매개변수는 경로에서 고정된 부분이 아니기 때문에 선택적일 수 있고 기본값을 가질 수 있습니다.
@@ -57,33 +57,25 @@ http://127.0.0.1:8000/items/?skip=20
* `skip=20`: URL에서 지정했기 때문입니다
* `limit=10`: 기본값이기 때문입니다
-## 선택적 매개변수
+## 선택적 매개변수 { #optional-parameters }
같은 방법으로 기본값을 `None`으로 설정하여 선택적 매개변수를 선언할 수 있습니다:
-{* ../../docs_src/query_params/tutorial002.py hl[9] *}
+{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *}
이 경우 함수 매개변수 `q`는 선택적이며 기본값으로 `None` 값이 됩니다.
-/// check | 확인
+/// check
-**FastAPI**는 `item_id`가 경로 매개변수이고 `q`는 경로 매개변수가 아닌 쿼리 매개변수라는 것을 알 정도로 충분히 똑똑합니다.
+또한 **FastAPI**는 `item_id`가 경로 매개변수이고 `q`는 경로 매개변수가 아니라서 쿼리 매개변수라는 것을 알 정도로 충분히 똑똑하다는 점도 확인하세요.
///
-/// note | 참고
-
-FastAPI는 `q`가 `= None`이므로 선택적이라는 것을 인지합니다.
-
-`Union[str, None]`에 있는 `Union`은 FastAPI(FastAPI는 `str` 부분만 사용합니다)가 사용하는게 아니지만, `Union[str, None]`은 편집기에게 코드에서 오류를 찾아낼 수 있게 도와줍니다.
-
-///
-
-## 쿼리 매개변수 형변환
+## 쿼리 매개변수 형변환 { #query-parameter-type-conversion }
`bool` 형으로 선언할 수도 있고, 아래처럼 변환됩니다:
-{* ../../docs_src/query_params/tutorial003.py hl[9] *}
+{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *}
이 경우, 아래로 이동하면:
@@ -115,10 +107,10 @@ http://127.0.0.1:8000/items/foo?short=on
http://127.0.0.1:8000/items/foo?short=yes
```
-또는 다른 어떤 변형(대문자, 첫글자만 대문자 등)이더라도 함수는 매개변수 `bool`형을 가진 `short`의 값이 `True`임을 압니다. 그렇지 않은 경우 `False`입니다.
+또는 다른 어떤 변형(대문자, 첫글자만 대문자 등)이더라도 함수는 `bool` 값이 `True`인 매개변수 `short`를 보게 됩니다. 그렇지 않은 경우 `False`입니다.
-## 여러 경로/쿼리 매개변수
+## 여러 경로/쿼리 매개변수 { #multiple-path-and-query-parameters }
여러 경로 매개변수와 쿼리 매개변수를 동시에 선언할 수 있으며 **FastAPI**는 어느 것이 무엇인지 알고 있습니다.
@@ -126,9 +118,9 @@ http://127.0.0.1:8000/items/foo?short=yes
매개변수들은 이름으로 감지됩니다:
-{* ../../docs_src/query_params/tutorial004.py hl[8,10] *}
+{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
-## 필수 쿼리 매개변수
+## 필수 쿼리 매개변수 { #required-query-parameters }
경로가 아닌 매개변수에 대한 기본값을 선언할 때(지금은 쿼리 매개변수만 보았습니다), 해당 매개변수는 필수적(Required)이지 않았습니다.
@@ -136,7 +128,7 @@ http://127.0.0.1:8000/items/foo?short=yes
그러나 쿼리 매개변수를 필수로 만들려면 단순히 기본값을 선언하지 않으면 됩니다:
-{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
+{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
여기 쿼리 매개변수 `needy`는 `str`형인 필수 쿼리 매개변수입니다.
@@ -150,16 +142,17 @@ http://127.0.0.1:8000/items/foo-item
```JSON
{
- "detail": [
- {
- "loc": [
- "query",
- "needy"
- ],
- "msg": "field required",
- "type": "value_error.missing"
- }
- ]
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "query",
+ "needy"
+ ],
+ "msg": "Field required",
+ "input": null
+ }
+ ]
}
```
@@ -180,7 +173,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
그리고 물론, 일부 매개변수는 필수로, 다른 일부는 기본값을, 또 다른 일부는 선택적으로 선언할 수 있습니다:
-{* ../../docs_src/query_params/tutorial006.py hl[10] *}
+{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *}
위 예시에서는 3가지 쿼리 매개변수가 있습니다:
@@ -188,8 +181,8 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
* `skip`, 기본값이 `0`인 `int`.
* `limit`, 선택적인 `int`.
-/// tip | 팁
+/// tip
-[경로 매개변수](path-params.md#_8){.internal-link target=_blank}와 마찬가지로 `Enum`을 사용할 수 있습니다.
+[경로 매개변수](path-params.md#predefined-values){.internal-link target=_blank}와 마찬가지로 `Enum`을 사용할 수 있습니다.
///
diff --git a/docs/ko/docs/tutorial/request-files.md b/docs/ko/docs/tutorial/request-files.md
index 9162b353cd..cc00009211 100644
--- a/docs/ko/docs/tutorial/request-files.md
+++ b/docs/ko/docs/tutorial/request-files.md
@@ -1,4 +1,4 @@
-# 파일 요청
+# 파일 요청 { #request-files }
`File`을 사용하여 클라이언트가 업로드할 파일들을 정의할 수 있습니다.
@@ -6,23 +6,27 @@
업로드된 파일을 전달받기 위해 먼저 `python-multipart`를 설치해야합니다.
-예시) `pip install python-multipart`.
+[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고, 활성화한 다음, 예를 들어 다음과 같이 설치하세요:
+
+```console
+$ pip install python-multipart
+```
업로드된 파일들은 "폼 데이터"의 형태로 전송되기 때문에 이 작업이 필요합니다.
///
-## `File` 임포트
+## `File` 임포트 { #import-file }
`fastapi` 에서 `File` 과 `UploadFile` 을 임포트 합니다:
-{* ../../docs_src/request_files/tutorial001.py hl[1] *}
+{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
-## `File` 매개변수 정의
+## `File` 매개변수 정의 { #define-file-parameters }
`Body` 및 `Form` 과 동일한 방식으로 파일의 매개변수를 생성합니다:
-{* ../../docs_src/request_files/tutorial001.py hl[7] *}
+{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *}
/// info | 정보
@@ -40,20 +44,21 @@ File의 본문을 선언할 때, 매개변수가 쿼리 매개변수 또는 본
파일들은 "폼 데이터"의 형태로 업로드 됩니다.
-*경로 작동 함수*의 매개변수를 `bytes` 로 선언하는 경우 **FastAPI**는 파일을 읽고 `bytes` 형태의 내용을 전달합니다.
+*경로 처리 함수*의 매개변수를 `bytes` 로 선언하는 경우 **FastAPI**는 파일을 읽고 `bytes` 형태의 내용을 전달합니다.
이것은 전체 내용이 메모리에 저장된다는 것을 의미한다는 걸 염두하기 바랍니다. 이는 작은 크기의 파일들에 적합합니다.
어떤 경우에는 `UploadFile` 을 사용하는 것이 더 유리합니다.
-## `File` 매개변수와 `UploadFile`
+## `UploadFile`을 사용하는 `File` 매개변수 { #file-parameters-with-uploadfile }
`File` 매개변수를 `UploadFile` 타입으로 정의합니다:
-{* ../../docs_src/request_files/tutorial001.py hl[12] *}
+{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *}
`UploadFile` 을 사용하는 것은 `bytes` 과 비교해 다음과 같은 장점이 있습니다:
+* 매개변수의 기본값에서 `File()`을 사용할 필요가 없습니다.
* "스풀 파일"을 사용합니다.
* 최대 크기 제한까지만 메모리에 저장되며, 이를 초과하는 경우 디스크에 저장됩니다.
* 따라서 이미지, 동영상, 큰 이진코드와 같은 대용량 파일들을 많은 메모리를 소모하지 않고 처리하기에 적합합니다.
@@ -61,13 +66,13 @@ File의 본문을 선언할 때, 매개변수가 쿼리 매개변수 또는 본
* file-like `async` 인터페이스를 갖고 있습니다.
* file-like object를 필요로하는 다른 라이브러리에 직접적으로 전달할 수 있는 파이썬 `SpooledTemporaryFile` 객체를 반환합니다.
-### `UploadFile`
+### `UploadFile` { #uploadfile }
`UploadFile` 은 다음과 같은 어트리뷰트가 있습니다:
* `filename` : 문자열(`str`)로 된 업로드된 파일의 파일명입니다 (예: `myimage.jpg`).
* `content_type` : 문자열(`str`)로 된 파일 형식(MIME type / media type)입니다 (예: `image/jpeg`).
-* `file` : `SpooledTemporaryFile` (파일류 객체)입니다. 이것은 "파일류" 객체를 필요로하는 다른 라이브러리에 직접적으로 전달할 수 있는 실질적인 파이썬 파일입니다.
+* `file` : `SpooledTemporaryFile` (a file-like object)입니다. 이것은 "file-like" 객체를 필요로하는 다른 함수나 라이브러리에 직접적으로 전달할 수 있는 실질적인 파이썬 파일 객체입니다.
`UploadFile` 에는 다음의 `async` 메소드들이 있습니다. 이들은 내부적인 `SpooledTemporaryFile` 을 사용하여 해당하는 파일 메소드를 호출합니다.
@@ -80,55 +85,67 @@ File의 본문을 선언할 때, 매개변수가 쿼리 매개변수 또는 본
상기 모든 메소드들이 `async` 메소드이기 때문에 “await”을 사용하여야 합니다.
-예를들어, `async` *경로 작동 함수*의 내부에서 다음과 같은 방식으로 내용을 가져올 수 있습니다:
+예를들어, `async` *경로 처리 함수*의 내부에서 다음과 같은 방식으로 내용을 가져올 수 있습니다:
```Python
contents = await myfile.read()
```
-만약 일반적인 `def` *경로 작동 함수*의 내부라면, 다음과 같이 `UploadFile.file` 에 직접 접근할 수 있습니다:
+만약 일반적인 `def` *경로 처리 함수*의 내부라면, 다음과 같이 `UploadFile.file` 에 직접 접근할 수 있습니다:
```Python
contents = myfile.file.read()
```
-/// note | "`async` 기술적 세부사항"
+/// note | `async` 기술 세부사항
`async` 메소드들을 사용할 때 **FastAPI**는 스레드풀에서 파일 메소드들을 실행하고 그들을 기다립니다.
///
-/// note | Starlette 기술적 세부사항
+/// note | Starlette 기술 세부사항
**FastAPI**의 `UploadFile` 은 **Starlette**의 `UploadFile` 을 직접적으로 상속받지만, **Pydantic** 및 FastAPI의 다른 부분들과의 호환성을 위해 필요한 부분들이 추가되었습니다.
///
-## "폼 데이터"란
+## "폼 데이터"란 { #what-is-form-data }
HTML의 폼들(``)이 서버에 데이터를 전송하는 방식은 대개 데이터에 JSON과는 다른 "특별한" 인코딩을 사용합니다.
**FastAPI**는 JSON 대신 올바른 위치에서 데이터를 읽을 수 있도록 합니다.
-/// note | 기술적 세부사항
+/// note | 기술 세부사항
폼의 데이터는 파일이 포함되지 않은 경우 일반적으로 "미디어 유형" `application/x-www-form-urlencoded` 을 사용해 인코딩 됩니다.
하지만 파일이 포함된 경우, `multipart/form-data`로 인코딩됩니다. `File`을 사용하였다면, **FastAPI**는 본문의 적합한 부분에서 파일을 가져와야 한다는 것을 인지합니다.
-인코딩과 폼 필드에 대해 더 알고싶다면, POST에 관한MDN웹 문서 를 참고하기 바랍니다,.
+인코딩과 폼 필드에 대해 더 알고싶다면, MDN web docs for POST를 참고하기 바랍니다.
///
/// warning | 경고
-다수의 `File` 과 `Form` 매개변수를 한 *경로 작동*에 선언하는 것이 가능하지만, 요청의 본문이 `application/json` 가 아닌 `multipart/form-data` 로 인코딩 되기 때문에 JSON으로 받아야하는 `Body` 필드를 함께 선언할 수는 없습니다.
+다수의 `File` 과 `Form` 매개변수를 한 *경로 처리*에 선언하는 것이 가능하지만, 요청의 본문이 `application/json` 가 아닌 `multipart/form-data` 로 인코딩 되기 때문에 JSON으로 받아야하는 `Body` 필드를 함께 선언할 수는 없습니다.
이는 **FastAPI**의 한계가 아니라, HTTP 프로토콜에 의한 것입니다.
///
-## 다중 파일 업로드
+## 선택적 파일 업로드 { #optional-file-upload }
+
+표준 타입 애너테이션을 사용하고 기본값을 `None`으로 설정하여 파일을 선택적으로 만들 수 있습니다:
+
+{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
+
+## 추가 메타데이터를 포함한 `UploadFile` { #uploadfile-with-additional-metadata }
+
+추가 메타데이터를 설정하기 위해 예를 들어 `UploadFile`과 함께 `File()`을 사용할 수도 있습니다:
+
+{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
+
+## 다중 파일 업로드 { #multiple-file-uploads }
여러 파일을 동시에 업로드 할 수 있습니다.
@@ -136,21 +153,11 @@ HTML의 폼들(``)이 서버에 데이터를 전송하는 방식은
이 기능을 사용하기 위해 , `bytes` 의 `List` 또는 `UploadFile` 를 선언하기 바랍니다:
-{* ../../docs_src/request_files/tutorial002.py hl[10,15] *}
+{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *}
선언한대로, `bytes` 의 `list` 또는 `UploadFile` 들을 전송받을 것입니다.
-/// note | 참고
-
-2019년 4월 14일부터 Swagger UI가 하나의 폼 필드로 다수의 파일을 업로드하는 것을 지원하지 않습니다. 더 많은 정보를 원하면, #4276과 #3641을 참고하세요.
-
-그럼에도, **FastAPI**는 표준 Open API를 사용해 이미 호환이 가능합니다.
-
-따라서 Swagger UI 또는 기타 그 외의 OpenAPI를 지원하는 툴이 다중 파일 업로드를 지원하는 경우, 이들은 **FastAPI**와 호환됩니다.
-
-///
-
-/// note | 기술적 세부사항
+/// note | 기술 세부사항
`from starlette.responses import HTMLResponse` 역시 사용할 수 있습니다.
@@ -158,6 +165,12 @@ HTML의 폼들(``)이 서버에 데이터를 전송하는 방식은
///
-## 요약
+### 추가 메타데이터를 포함한 다중 파일 업로드 { #multiple-file-uploads-with-additional-metadata }
-폼 데이터로써 입력 매개변수로 업로드할 파일을 선언할 경우 `File` 을 사용하기 바랍니다.
+이전과 같은 방식으로 `UploadFile`에 대해서도 `File()`을 사용해 추가 매개변수를 설정할 수 있습니다:
+
+{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
+
+## 요약 { #recap }
+
+`File`, `bytes`, `UploadFile`을 사용하여 폼 데이터로 전송되는 요청에서 업로드할 파일을 선언하세요.
diff --git a/docs/ko/docs/tutorial/request-form-models.md b/docs/ko/docs/tutorial/request-form-models.md
index 3316a93d52..b37186dfb8 100644
--- a/docs/ko/docs/tutorial/request-form-models.md
+++ b/docs/ko/docs/tutorial/request-form-models.md
@@ -1,12 +1,12 @@
-# 폼 모델
+# 폼 모델 { #form-models }
FastAPI에서 **Pydantic 모델**을 이용하여 **폼 필드**를 선언할 수 있습니다.
/// info | 정보
-폼(Form)을 사용하려면, 먼저 `python-multipart`를 설치하세요.
+폼을 사용하려면, 먼저 `python-multipart`를 설치하세요.
-[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, 아래와 같이 설치할 수 있습니다:
+[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, 예를 들어 아래와 같이 설치하세요:
```console
$ pip install python-multipart
@@ -20,7 +20,7 @@ $ pip install python-multipart
///
-## Pydantic 모델을 사용한 폼
+## 폼을 위한 Pydantic 모델 { #pydantic-models-for-forms }
**폼 필드**로 받고 싶은 필드를 **Pydantic 모델**로 선언한 다음, 매개변수를 `Form`으로 선언하면 됩니다:
@@ -28,7 +28,7 @@ $ pip install python-multipart
**FastAPI**는 요청에서 받은 **폼 데이터**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다.
-## 문서 확인하기
+## 문서 확인하기 { #check-the-docs }
문서 UI `/docs`에서 확인할 수 있습니다:
@@ -36,9 +36,9 @@ $ pip install python-multipart
POST에 대한 MDN 웹 문서를 참조하세요.
+이러한 인코딩 및 폼 필드에 대해 더 읽고 싶다면, POST에 대한 MDN 웹 문서를 참조하세요.
///
/// warning | 경고
-*경로 작업*에서 여러 `Form` 매개변수를 선언할 수 있지만, JSON으로 수신할 것으로 예상되는 `Body` 필드와 함께 선언할 수 없습니다. 요청 본문은 `application/json` 대신에 `application/x-www-form-urlencoded`를 사용하여 인코딩되기 때문입니다.
+*경로 처리*에서 여러 `Form` 매개변수를 선언할 수 있지만, JSON으로 수신할 것으로 예상되는 `Body` 필드와 함께 선언할 수 없습니다. 요청 본문은 `application/json` 대신에 `application/x-www-form-urlencoded`를 사용하여 인코딩되기 때문입니다.
이는 **FastAPI**의 제한 사항이 아니며 HTTP 프로토콜의 일부입니다.
///
-## 요약
+## 요약 { #recap }
폼 데이터 입력 매개변수를 선언하려면 `Form`을 사용하세요.
diff --git a/docs/ko/docs/tutorial/response-model.md b/docs/ko/docs/tutorial/response-model.md
index a71d649f90..6246ed9ad1 100644
--- a/docs/ko/docs/tutorial/response-model.md
+++ b/docs/ko/docs/tutorial/response-model.md
@@ -1,81 +1,173 @@
-# 응답 모델
+# 응답 모델 - 반환 타입 { #response-model-return-type }
-어떤 *경로 작동*이든 매개변수 `response_model`를 사용하여 응답을 위한 모델을 선언할 수 있습니다:
+*경로 처리 함수*의 **반환 타입**을 어노테이션하여 응답에 사용될 타입을 선언할 수 있습니다.
+
+함수 **매개변수**에서 입력 데이터를 위해 사용하는 것과 동일하게 **타입 어노테이션**을 사용할 수 있으며, Pydantic 모델, 리스트, 딕셔너리, 정수/불리언 같은 스칼라 값 등을 사용할 수 있습니다.
+
+{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
+
+FastAPI는 이 반환 타입을 사용하여:
+
+* 반환된 데이터를 **검증**합니다.
+ * 데이터가 유효하지 않다면(예: 필드가 누락된 경우), 이는 *여러분의* 앱 코드가 깨져서 의도한 값을 반환하지 못한다는 의미이며, 잘못된 데이터를 반환하는 대신 서버 오류를 반환합니다. 이렇게 하면 여러분과 클라이언트는 기대한 데이터와 데이터 형태를 받는다는 것을 확실히 할 수 있습니다.
+* OpenAPI *경로 처리*의 응답에 **JSON Schema**를 추가합니다.
+ * 이는 **자동 문서**에서 사용됩니다.
+ * 또한 자동 클라이언트 코드 생성 도구에서도 사용됩니다.
+
+하지만 가장 중요한 것은:
+
+* 반환 타입에 정의된 내용으로 출력 데이터를 **제한하고 필터링**합니다.
+ * 이는 특히 **보안**에 매우 중요합니다. 아래에서 더 자세히 살펴보겠습니다.
+
+## `response_model` 매개변수 { #response-model-parameter }
+
+타입 선언이 말하는 것과 정확히 일치하지 않는 데이터를 반환해야 하거나 반환하고 싶은 경우가 있습니다.
+
+예를 들어, **딕셔너리**나 데이터베이스 객체를 **반환**하고 싶지만, **Pydantic 모델로 선언**하고 싶을 수 있습니다. 이렇게 하면 Pydantic 모델이 반환한 객체(예: 딕셔너리나 데이터베이스 객체)에 대해 데이터 문서화, 검증 등 모든 작업을 수행합니다.
+
+반환 타입 어노테이션을 추가했다면, 도구와 에디터가 함수가 선언한 타입(예: Pydantic 모델)과 다른 타입(예: dict)을 반환하고 있다는 (올바른) 오류로 불평할 것입니다.
+
+그런 경우에는 반환 타입 대신 *경로 처리 데코레이터*의 매개변수 `response_model`을 사용할 수 있습니다.
+
+`response_model` 매개변수는 모든 *경로 처리*에서 사용할 수 있습니다:
* `@app.get()`
* `@app.post()`
* `@app.put()`
* `@app.delete()`
-* 기타.
+* 등.
-{* ../../docs_src/response_model/tutorial001.py hl[17] *}
+{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *}
/// note | 참고
-`response_model`은 "데코레이터" 메소드(`get`, `post`, 등)의 매개변수입니다. 모든 매개변수들과 본문(body)처럼 *경로 작동 함수*가 아닙니다.
+`response_model`은 "데코레이터" 메서드(`get`, `post` 등)의 매개변수입니다. 모든 매개변수와 body처럼, *경로 처리 함수*의 매개변수가 아닙니다.
///
-Pydantic 모델 어트리뷰트를 선언한 것과 동일한 타입을 수신하므로 Pydantic 모델이 될 수 있지만, `List[Item]`과 같이 Pydantic 모델들의 `list`일 수도 있습니다.
+`response_model`은 Pydantic 모델 필드에 선언하는 것과 동일한 타입을 받습니다. 따라서 Pydantic 모델이 될 수도 있고, `List[Item]`처럼 Pydantic 모델의 `list`가 될 수도 있습니다.
-FastAPI는 이 `response_model`를 사용하여:
+FastAPI는 이 `response_model`을 사용해 데이터 문서화, 검증 등을 수행하고, 또한 출력 데이터를 타입 선언에 맞게 **변환하고 필터링**합니다.
-* 출력 데이터를 타입 선언으로 변환.
-* 데이터 검증.
-* OpenAPI *경로 작동*의 응답에 JSON 스키마 추가.
-* 자동 생성 문서 시스템에 사용.
+/// tip | 팁
-하지만 가장 중요한 것은:
+에디터, mypy 등에서 엄격한 타입 체크를 사용하고 있다면, 함수 반환 타입을 `Any`로 선언할 수 있습니다.
-* 해당 모델의 출력 데이터 제한. 이것이 얼마나 중요한지 아래에서 볼 것입니다.
-
-/// note | 기술 세부사항
-
-응답 모델은 함수의 타입 어노테이션 대신 이 매개변수로 선언하는데, 경로 함수가 실제 응답 모델을 반환하지 않고 `dict`, 데이터베이스 객체나 기타 다른 모델을 `response_model`을 사용하여 필드 제한과 직렬화를 수행하고 반환할 수 있기 때문입니다
+이렇게 하면 에디터에 의도적으로 어떤 값이든 반환한다고 알려줍니다. 하지만 FastAPI는 여전히 `response_model`을 사용하여 데이터 문서화, 검증, 필터링 등을 수행합니다.
///
-## 동일한 입력 데이터 반환
+### `response_model` 우선순위 { #response-model-priority }
-여기서 우리는 평문 비밀번호를 포함하는 `UserIn` 모델을 선언합니다:
+반환 타입과 `response_model`을 둘 다 선언하면, `response_model`이 우선순위를 가지며 FastAPI에서 사용됩니다.
-{* ../../docs_src/response_model/tutorial002.py hl[9,11] *}
+이렇게 하면 응답 모델과 다른 타입을 반환하는 경우에도 에디터와 mypy 같은 도구에서 사용할 올바른 타입 어노테이션을 함수에 추가할 수 있습니다. 그리고 동시에 FastAPI가 `response_model`을 사용하여 데이터 검증, 문서화 등을 수행하게 할 수도 있습니다.
+
+또한 `response_model=None`을 사용해 해당 *경로 처리*에 대한 응답 모델 생성을 비활성화할 수도 있습니다. 이는 유효한 Pydantic 필드가 아닌 것들에 대해 타입 어노테이션을 추가하는 경우에 필요할 수 있으며, 아래 섹션 중 하나에서 예시를 볼 수 있습니다.
+
+## 동일한 입력 데이터 반환 { #return-the-same-input-data }
+
+여기서는 평문 비밀번호를 포함하는 `UserIn` 모델을 선언합니다:
+
+{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *}
+
+/// info | 정보
+
+`EmailStr`을 사용하려면 먼저 `email-validator`를 설치하세요.
+
+[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고, 활성화한 다음 설치해야 합니다. 예를 들어:
+
+```console
+$ pip install email-validator
+```
+
+또는 다음과 같이:
+
+```console
+$ pip install "pydantic[email]"
+```
+
+///
그리고 이 모델을 사용하여 입력을 선언하고 같은 모델로 출력을 선언합니다:
-{* ../../docs_src/response_model/tutorial002.py hl[17:18] *}
+{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *}
이제 브라우저가 비밀번호로 사용자를 만들 때마다 API는 응답으로 동일한 비밀번호를 반환합니다.
-이 경우, 사용자가 스스로 비밀번호를 발신했기 때문에 문제가 되지 않을 수 있습니다.
+이 경우, 동일한 사용자가 비밀번호를 보내는 것이므로 문제가 되지 않을 수도 있습니다.
-그러나 동일한 모델을 다른 *경로 작동*에서 사용할 경우, 모든 클라이언트에게 사용자의 비밀번호를 발신할 수 있습니다.
+하지만 동일한 모델을 다른 *경로 처리*에서 사용하면, 모든 클라이언트에게 사용자의 비밀번호를 보내게 될 수도 있습니다.
/// danger | 위험
-절대로 사용자의 평문 비밀번호를 저장하거나 응답으로 발신하지 마십시오.
+모든 주의사항을 알고 있으며 무엇을 하는지 정확히 알고 있지 않다면, 이런 방식으로 사용자의 평문 비밀번호를 저장하거나 응답으로 보내지 마세요.
///
-## 출력 모델 추가
+## 출력 모델 추가 { #add-an-output-model }
-대신 평문 비밀번호로 입력 모델을 만들고 해당 비밀번호 없이 출력 모델을 만들 수 있습니다:
+대신 평문 비밀번호를 포함하는 입력 모델과, 비밀번호가 없는 출력 모델을 만들 수 있습니다:
-{* ../../docs_src/response_model/tutorial003.py hl[9,11,16] *}
+{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *}
-여기서 *경로 작동 함수*가 비밀번호를 포함하는 동일한 입력 사용자를 반환할지라도:
+여기서 *경로 처리 함수*가 비밀번호를 포함하는 동일한 입력 사용자를 반환하더라도:
-{* ../../docs_src/response_model/tutorial003.py hl[24] *}
+{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *}
-...`response_model`을 `UserOut` 모델로 선언했기 때문에 비밀번호를 포함하지 않습니다:
+...비밀번호를 포함하지 않는 `UserOut` 모델로 `response_model`을 선언했습니다:
-{* ../../docs_src/response_model/tutorial003.py hl[22] *}
+{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *}
-따라서 **FastAPI**는 출력 모델에서 선언하지 않은 모든 데이터를 (Pydantic을 사용하여) 필터링합니다.
+따라서 **FastAPI**는 출력 모델에 선언되지 않은 모든 데이터를 (Pydantic을 사용하여) 필터링합니다.
-## 문서에서 보기
+### `response_model` 또는 반환 타입 { #response-model-or-return-type }
-자동 생성 문서를 보면 입력 모델과 출력 모델이 각자의 JSON 스키마를 가지고 있음을 확인할 수 있습니다:
+이 경우 두 모델이 서로 다르기 때문에, 함수 반환 타입을 `UserOut`으로 어노테이션하면 에디터와 도구는 서로 다른 클래스인데 잘못된 타입을 반환하고 있다고 불평할 것입니다.
+
+그래서 이 예제에서는 `response_model` 매개변수로 선언해야 합니다.
+
+...하지만 아래를 계속 읽으면 이를 극복하는 방법을 볼 수 있습니다.
+
+## 반환 타입과 데이터 필터링 { #return-type-and-data-filtering }
+
+이전 예제에서 계속해 봅시다. 함수에 **하나의 타입으로 어노테이션**을 하고 싶지만, 함수에서 실제로는 **더 많은 데이터**를 포함하는 것을 반환할 수 있길 원했습니다.
+
+FastAPI가 응답 모델을 사용해 데이터를 계속 **필터링**하길 원합니다. 그래서 함수가 더 많은 데이터를 반환하더라도, 응답에는 응답 모델에 선언된 필드만 포함되게 합니다.
+
+이전 예제에서는 클래스가 달랐기 때문에 `response_model` 매개변수를 써야 했습니다. 하지만 이는 에디터와 도구가 함수 반환 타입을 체크해 주는 지원을 받지 못한다는 뜻이기도 합니다.
+
+하지만 대부분 이런 작업이 필요한 경우에는, 이 예제처럼 모델로 일부 데이터를 **필터링/제거**하길 원하는 경우가 많습니다.
+
+그리고 그런 경우에는 클래스와 상속을 사용하여 함수 **타입 어노테이션**을 활용해 에디터/도구에서 더 나은 지원을 받으면서도 FastAPI의 **데이터 필터링**을 유지할 수 있습니다.
+
+{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *}
+
+이를 통해 이 코드는 타입 관점에서 올바르므로 에디터와 mypy 등의 도구 지원을 받을 수 있고, 동시에 FastAPI의 데이터 필터링도 받을 수 있습니다.
+
+이게 어떻게 동작할까요? 확인해 봅시다. 🤓
+
+### 타입 어노테이션과 도구 지원 { #type-annotations-and-tooling }
+
+먼저 에디터, mypy 및 기타 도구가 이를 어떻게 보는지 살펴봅시다.
+
+`BaseUser`는 기본 필드를 가집니다. 그리고 `UserIn`은 `BaseUser`를 상속하고 `password` 필드를 추가하므로, 두 모델의 모든 필드를 포함하게 됩니다.
+
+함수 반환 타입을 `BaseUser`로 어노테이션하지만, 실제로는 `UserIn` 인스턴스를 반환합니다.
+
+에디터, mypy 및 기타 도구는 이에 대해 불평하지 않습니다. 타이핑 관점에서 `UserIn`은 `BaseUser`의 서브클래스이므로, `BaseUser`인 어떤 것이 기대되는 곳에서는 *유효한* 타입이기 때문입니다.
+
+### FastAPI 데이터 필터링 { #fastapi-data-filtering }
+
+이제 FastAPI는 반환 타입을 보고, 여러분이 반환하는 값이 해당 타입에 선언된 필드 **만** 포함하도록 보장합니다.
+
+FastAPI는 Pydantic을 내부적으로 여러 방식으로 사용하여, 클래스 상속의 동일한 규칙이 반환 데이터 필터링에는 적용되지 않도록 합니다. 그렇지 않으면 기대한 것보다 훨씬 더 많은 데이터를 반환하게 될 수도 있습니다.
+
+이렇게 하면 **도구 지원**이 있는 타입 어노테이션과 **데이터 필터링**이라는 두 가지 장점을 모두 얻을 수 있습니다.
+
+## 문서에서 보기 { #see-it-in-the-docs }
+
+자동 생성 문서를 보면 입력 모델과 출력 모델이 각자의 JSON Schema를 가지고 있음을 확인할 수 있습니다:
@@ -83,29 +175,73 @@ FastAPI는 이 `response_model`를 사용하여:
-## 응답 모델 인코딩 매개변수
+## 기타 반환 타입 어노테이션 { #other-return-type-annotations }
+
+유효한 Pydantic 필드가 아닌 것을 반환하면서도, 도구(에디터, mypy 등)가 제공하는 지원을 받기 위해 함수에 어노테이션을 달아두는 경우가 있을 수 있습니다.
+
+### 응답을 직접 반환하기 { #return-a-response-directly }
+
+가장 흔한 경우는 [고급 문서에서 나중에 설명하는 대로 Response를 직접 반환하는 것](../advanced/response-directly.md){.internal-link target=_blank}입니다.
+
+{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
+
+이 간단한 경우는 반환 타입 어노테이션이 `Response` 클래스(또는 그 서브클래스)이기 때문에 FastAPI에서 자동으로 처리됩니다.
+
+그리고 `RedirectResponse`와 `JSONResponse`는 모두 `Response`의 서브클래스이므로, 타입 어노테이션이 올바르기 때문에 도구들도 만족합니다.
+
+### Response 서브클래스 어노테이션 { #annotate-a-response-subclass }
+
+타입 어노테이션에 `Response`의 서브클래스를 사용할 수도 있습니다:
+
+{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
+
+이는 `RedirectResponse`가 `Response`의 서브클래스이기 때문에 동작하며, FastAPI가 이 간단한 경우를 자동으로 처리합니다.
+
+### 유효하지 않은 반환 타입 어노테이션 { #invalid-return-type-annotations }
+
+하지만 유효한 Pydantic 타입이 아닌 다른 임의의 객체(예: 데이터베이스 객체)를 반환하고, 함수에서 그렇게 어노테이션하면, FastAPI는 그 타입 어노테이션으로부터 Pydantic 응답 모델을 만들려고 시도하다가 실패합니다.
+
+또한, 유효한 Pydantic 타입이 아닌 타입이 하나 이상 포함된 여러 타입 간의 union이 있는 경우에도 동일합니다. 예를 들어, 아래는 실패합니다 💥:
+
+{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *}
+
+...이는 타입 어노테이션이 Pydantic 타입이 아니고, 단일 `Response` 클래스/서브클래스도 아니며, `Response`와 `dict` 간 union(둘 중 아무거나)이기 때문에 실패합니다.
+
+### 응답 모델 비활성화 { #disable-response-model }
+
+위 예제에서 이어서, FastAPI가 수행하는 기본 데이터 검증, 문서화, 필터링 등을 원하지 않을 수 있습니다.
+
+하지만 에디터나 타입 체커(예: mypy) 같은 도구 지원을 받기 위해 함수에 반환 타입 어노테이션은 유지하고 싶을 수도 있습니다.
+
+이 경우 `response_model=None`으로 설정하여 응답 모델 생성을 비활성화할 수 있습니다:
+
+{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *}
+
+그러면 FastAPI는 응답 모델 생성을 건너뛰며, FastAPI 애플리케이션에 영향을 주지 않고 필요한 반환 타입 어노테이션을 어떤 것이든 사용할 수 있습니다. 🤓
+
+## 응답 모델 인코딩 매개변수 { #response-model-encoding-parameters }
응답 모델은 아래와 같이 기본값을 가질 수 있습니다:
-{* ../../docs_src/response_model/tutorial004.py hl[11,13:14] *}
+{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *}
-* `description: Optional[str] = None`은 기본값으로 `None`을 갖습니다.
+* `description: Union[str, None] = None` (또는 Python 3.10에서 `str | None = None`)은 기본값으로 `None`을 갖습니다.
* `tax: float = 10.5`는 기본값으로 `10.5`를 갖습니다.
-* `tags: List[str] = []` 빈 리스트의 기본값으로: `[]`.
+* `tags: List[str] = []`는 기본값으로 빈 리스트 `[]`를 갖습니다.
-그러나 실제로 저장되지 않았을 경우 결과에서 값을 생략하고 싶을 수 있습니다.
+하지만 실제로 저장되지 않았을 경우 결과에서 이를 생략하고 싶을 수 있습니다.
예를 들어, NoSQL 데이터베이스에 많은 선택적 속성이 있는 모델이 있지만, 기본값으로 가득 찬 매우 긴 JSON 응답을 보내고 싶지 않습니다.
-### `response_model_exclude_unset` 매개변수 사용
+### `response_model_exclude_unset` 매개변수 사용 { #use-the-response-model-exclude-unset-parameter }
-*경로 작동 데코레이터* 매개변수를 `response_model_exclude_unset=True`로 설정 할 수 있습니다:
+*경로 처리 데코레이터* 매개변수 `response_model_exclude_unset=True`로 설정할 수 있습니다:
-{* ../../docs_src/response_model/tutorial004.py hl[24] *}
+{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *}
-이러한 기본값은 응답에 포함되지 않고 실제로 설정된 값만 포함됩니다.
+그러면 이러한 기본값은 응답에 포함되지 않고, 실제로 설정된 값만 포함됩니다.
-따라서 해당 *경로 작동*에 ID가 `foo`인 항목(items)을 요청으로 보내면 (기본값을 제외한) 응답은 다음과 같습니다:
+따라서 ID가 `foo`인 항목에 대해 해당 *경로 처리*로 요청을 보내면, (기본값을 제외한) 응답은 다음과 같습니다:
```JSON
{
@@ -116,24 +252,18 @@ FastAPI는 이 `response_model`를 사용하여:
/// info | 정보
-FastAPI는 이를 위해 Pydantic 모델의 `.dict()`의 `exclude_unset` 매개변수를 사용합니다.
-
-///
-
-/// info | 정보
-
-아래 또한 사용할 수 있습니다:
+다음도 사용할 수 있습니다:
* `response_model_exclude_defaults=True`
* `response_model_exclude_none=True`
-Pydantic 문서에서 `exclude_defaults` 및 `exclude_none`에 대해 설명한 대로 사용할 수 있습니다.
+`exclude_defaults` 및 `exclude_none`에 대해 Pydantic 문서에 설명된 대로 사용할 수 있습니다.
///
-#### 기본값이 있는 필드를 갖는 값의 데이터
+#### 기본값이 있는 필드에 값이 있는 데이터 { #data-with-values-for-fields-with-defaults }
-하지만 모델의 필드가 기본값이 있어도 ID가 `bar`인 항목(items)처럼 데이터가 값을 갖는다면:
+하지만 ID가 `bar`인 항목처럼, 기본값이 있는 모델의 필드에 값이 있다면:
```Python hl_lines="3 5"
{
@@ -144,12 +274,11 @@ FastAPI는 이를 위해 Pydantic 모델의 `.dict()`의 `http.HTTPStatus` 와 같은 `IntEnum` 을 입력받을 수도 있습니다.
///
`status_code` 매개변수는:
* 응답에서 해당 상태 코드를 반환합니다.
-* 상태 코드를 OpenAPI 스키마(및 사용자 인터페이스)에 문서화 합니다.
+* 상태 코드를 OpenAPI 스키마(따라서, 사용자 인터페이스에도)에 문서화합니다:
-
+
/// note | 참고
-어떤 응답 코드들은 해당 응답에 본문이 없다는 것을 의미하기도 합니다 (다음 항목 참고).
+일부 응답 코드(다음 섹션 참고)는 응답에 본문이 없다는 것을 나타냅니다.
-이에 따라 FastAPI는 응답 본문이 없음을 명시하는 OpenAPI를 생성합니다.
+FastAPI는 이를 알고 있으며, 응답 본문이 없다고 명시하는 OpenAPI 문서를 생성합니다.
///
-## HTTP 상태 코드에 대하여
+## HTTP 상태 코드에 대하여 { #about-http-status-codes }
/// note | 참고
-만약 HTTP 상태 코드에 대하여 이미 알고있다면, 다음 항목으로 넘어가십시오.
+만약 HTTP 상태 코드가 무엇인지 이미 알고 있다면, 다음 섹션으로 넘어가세요.
///
-HTTP는 세자리의 숫자 상태 코드를 응답의 일부로 전송합니다.
+HTTP에서는 응답의 일부로 3자리 숫자 상태 코드를 보냅니다.
-이 상태 코드들은 각자를 식별할 수 있도록 지정된 이름이 있으나, 중요한 것은 숫자 코드입니다.
+이 상태 코드들은 이를 식별할 수 있도록 이름이 연결되어 있지만, 중요한 부분은 숫자입니다.
요약하자면:
-* `1xx` 상태 코드는 "정보"용입니다. 이들은 직접적으로는 잘 사용되지는 않습니다. 이 상태 코드를 갖는 응답들은 본문을 가질 수 없습니다.
-* **`2xx`** 상태 코드는 "성공적인" 응답을 위해 사용됩니다. 가장 많이 사용되는 유형입니다.
- * `200` 은 디폴트 상태 코드로, 모든 것이 "성공적임"을 의미합니다.
- * 다른 예로는 `201` "생성됨"이 있습니다. 일반적으로 데이터베이스에 새로운 레코드를 생성한 후 사용합니다.
- * 단, `204` "내용 없음"은 특별한 경우입니다. 이것은 클라이언트에게 반환할 내용이 없는 경우 사용합니다. 따라서 응답은 본문을 가질 수 없습니다.
-* **`3xx`** 상태 코드는 "리다이렉션"용입니다. 본문을 가질 수 없는 `304` "수정되지 않음"을 제외하고, 이 상태 코드를 갖는 응답에는 본문이 있을 수도, 없을 수도 있습니다.
-* **`4xx`** 상태 코드는 "클라이언트 오류" 응답을 위해 사용됩니다. 이것은 아마 가장 많이 사용하게 될 두번째 유형입니다.
- * 일례로 `404` 는 "찾을 수 없음" 응답을 위해 사용합니다.
- * 일반적인 클라이언트 오류의 경우 `400` 을 사용할 수 있습니다.
-* `5xx` 상태 코드는 서버 오류에 사용됩니다. 이것들을 직접 사용할 일은 거의 없습니다. 응용 프로그램 코드나 서버의 일부에서 문제가 발생하면 자동으로 이들 상태 코드 중 하나를 반환합니다.
+* `100 - 199` 는 "정보"용입니다. 직접 사용할 일은 거의 없습니다. 이 상태 코드를 갖는 응답은 본문을 가질 수 없습니다.
+* **`200 - 299`** 는 "성공적인" 응답을 위한 것입니다. 가장 많이 사용하게 될 유형입니다.
+ * `200` 은 기본 상태 코드로, 모든 것이 "OK"임을 의미합니다.
+ * 다른 예로는 `201` "생성됨"이 있습니다. 일반적으로 데이터베이스에 새 레코드를 생성한 후 사용합니다.
+ * 특별한 경우로 `204` "내용 없음"이 있습니다. 이 응답은 클라이언트에게 반환할 내용이 없을 때 사용되며, 따라서 응답은 본문을 가지면 안 됩니다.
+* **`300 - 399`** 는 "리다이렉션"용입니다. 이 상태 코드를 갖는 응답은 본문이 있을 수도 없을 수도 있으며, 본문이 없어야 하는 `304` "수정되지 않음"을 제외합니다.
+* **`400 - 499`** 는 "클라이언트 오류" 응답을 위한 것입니다. 아마 두 번째로 가장 많이 사용하게 될 유형입니다.
+ * 예를 들어 `404` 는 "찾을 수 없음" 응답을 위해 사용합니다.
+ * 클라이언트의 일반적인 오류에는 `400` 을 그냥 사용할 수 있습니다.
+* `500 - 599` 는 서버 오류에 사용됩니다. 직접 사용할 일은 거의 없습니다. 애플리케이션 코드의 일부나 서버에서 문제가 발생하면 자동으로 이들 상태 코드 중 하나를 반환합니다.
/// tip | 팁
-각각의 상태 코드와 이들이 의미하는 내용에 대해 더 알고싶다면 MDN HTTP 상태 코드에 관한 문서 를 확인하십시오.
+각 상태 코드와 어떤 코드가 어떤 용도인지 더 알고 싶다면 MDN의 HTTP 상태 코드에 관한 문서를 확인하세요.
///
-## 이름을 기억하는 쉬운 방법
+## 이름을 기억하는 쉬운 방법 { #shortcut-to-remember-the-names }
-상기 예시 참고:
+이전 예시를 다시 확인해보겠습니다:
-{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
+{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
-`201` 은 "생성됨"를 의미하는 상태 코드입니다.
+`201` 은 "생성됨"을 위한 상태 코드입니다.
-하지만 모든 상태 코드들이 무엇을 의미하는지 외울 필요는 없습니다.
+하지만 각각의 코드가 무엇을 의미하는지 외울 필요는 없습니다.
`fastapi.status` 의 편의 변수를 사용할 수 있습니다.
-{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
+{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *}
-이것은 단순히 작업을 편리하게 하기 위한 것으로, HTTP 상태 코드와 동일한 번호를 갖고있지만, 이를 사용하면 편집기의 자동완성 기능을 사용할 수 있습니다:
+이것들은 단지 편의를 위한 것으로, 동일한 숫자를 갖고 있지만, 이를 통해 편집기의 자동완성 기능으로 찾을 수 있습니다:
-
+
-/// note | 기술적 세부사항
+/// note | 기술 세부사항
`from starlette import status` 역시 사용할 수 있습니다.
@@ -96,6 +96,6 @@ HTTP는 세자리의 숫자 상태 코드를 응답의 일부로 전송합니다
///
-## 기본값 변경
+## 기본값 변경 { #changing-the-default }
-추후 여기서 선언하는 기본 상태 코드가 아닌 다른 상태 코드를 반환하는 방법을 [숙련된 사용자 지침서](https://fastapi.tiangolo.com/ko/advanced/response-change-status-code/){.internal-link target=_blank}에서 확인할 수 있습니다.
+나중에 [고급 사용자 지침서](../advanced/response-change-status-code.md){.internal-link target=_blank}에서, 여기서 선언하는 기본값과 다른 상태 코드를 반환하는 방법을 확인할 수 있습니다.
diff --git a/docs/ko/docs/tutorial/schema-extra-example.md b/docs/ko/docs/tutorial/schema-extra-example.md
index 77e94db720..b2b54836ac 100644
--- a/docs/ko/docs/tutorial/schema-extra-example.md
+++ b/docs/ko/docs/tutorial/schema-extra-example.md
@@ -1,43 +1,21 @@
-# 요청 예제 데이터 선언
+# 요청 예제 데이터 선언 { #declare-request-example-data }
여러분의 앱이 받을 수 있는 데이터 예제를 선언할 수 있습니다.
여기 이를 위한 몇가지 방식이 있습니다.
-## Pydantic 모델 속 추가 JSON 스키마 데이터
+## Pydantic 모델 속 추가 JSON 스키마 데이터 { #extra-json-schema-data-in-pydantic-models }
생성된 JSON 스키마에 추가될 Pydantic 모델을 위한 `examples`을 선언할 수 있습니다.
-//// tab | Pydantic v2
-
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
-////
-
-//// tab | Pydantic v1
-
-{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
-
-////
-
추가 정보는 있는 그대로 해당 모델의 **JSON 스키마** 결과에 추가되고, API 문서에서 사용합니다.
-//// tab | Pydantic v2
-
-Pydantic 버전 2에서 Pydantic 공식 문서: Model Config에 나와 있는 것처럼 `dict`를 받는 `model_config` 어트리뷰트를 사용할 것입니다.
+Pydantic 문서: Configuration에 설명된 것처럼 `dict`를 받는 `model_config` 어트리뷰트를 사용할 수 있습니다.
`"json_schema_extra"`를 생성된 JSON 스키마에서 보여주고 싶은 별도의 데이터와 `examples`를 포함하는 `dict`으로 설정할 수 있습니다.
-////
-
-//// tab | Pydantic v1
-
-Pydantic v1에서 Pydantic 공식 문서: Schema customization에서 설명하는 것처럼, 내부 클래스인 `Config`와 `schema_extra`를 사용할 것입니다.
-
-`schema_extra`를 생성된 JSON 스키마에서 보여주고 싶은 별도의 데이터와 `examples`를 포함하는 `dict`으로 설정할 수 있습니다.
-
-////
-
/// tip | 팁
JSON 스키마를 확장하고 여러분의 별도의 자체 데이터를 추가하기 위해 같은 기술을 사용할 수 있습니다.
@@ -52,19 +30,19 @@ JSON 스키마를 확장하고 여러분의 별도의 자체 데이터를 추가
그 전에는, 하나의 예제만 가능한 `example` 키워드만 지원했습니다. 이는 아직 OpenAPI 3.1.0에서 지원하지만, 지원이 종료될 것이며 JSON 스키마 표준에 포함되지 않습니다. 그렇기에 `example`을 `examples`으로 이전하는 것을 추천합니다. 🤓
-이 문서 끝에 더 많은 읽을거리가 있습니다.
+이 페이지 끝에서 더 많은 내용을 읽을 수 있습니다.
///
-## `Field` 추가 인자
+## `Field` 추가 인자 { #field-additional-arguments }
Pydantic 모델과 같이 `Field()`를 사용할 때 추가적인 `examples`를 선언할 수 있습니다:
{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *}
-## JSON Schema에서의 `examples` - OpenAPI
+## JSON Schema에서의 `examples` - OpenAPI { #examples-in-json-schema-openapi }
-이들 중에서 사용합니다:
+다음 중 하나를 사용할 때:
* `Path()`
* `Query()`
@@ -74,45 +52,45 @@ Pydantic 모델과 같이 `Field()`를 사용할 때 추가적인 `examples`를
* `Form()`
* `File()`
-**OpenAPI**의 **JSON 스키마**에 추가될 부가적인 정보를 포함한 `examples` 모음을 선언할 수 있습니다.
+**OpenAPI** 안의 **JSON 스키마**에 추가될 부가적인 정보를 포함한 `examples` 모음을 선언할 수도 있습니다.
-### `examples`를 포함한 `Body`
+### `examples`를 포함한 `Body` { #body-with-examples }
여기, `Body()`에 예상되는 예제 데이터 하나를 포함한 `examples`를 넘겼습니다:
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *}
-### 문서 UI 예시
+### 문서 UI 예시 { #example-in-the-docs-ui }
위의 어느 방법과 함께라면 `/docs`에서 다음과 같이 보일 것입니다:
-### 다중 `examples`를 포함한 `Body`
+### 다중 `examples`를 포함한 `Body` { #body-with-multiple-examples }
물론 여러 `examples`를 넘길 수 있습니다:
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *}
-이와 같이 하면 이 예제는 그 본문 데이터를 위한 내부 **JSON 스키마**의 일부가 될 것입니다.
+이와 같이 하면 예제들은 그 본문 데이터의 내부 **JSON 스키마**의 일부가 될 것입니다.
그럼에도 불구하고, 지금 이 문서를 작성하는 시간에, 문서 UI를 보여주는 역할을 맡은 Swagger UI는 **JSON 스키마** 속 데이터를 위한 여러 예제의 표현을 지원하지 않습니다. 하지만 해결 방안을 밑에서 읽어보세요.
-### OpenAPI-특화 `examples`
+### OpenAPI-특화 `examples` { #openapi-specific-examples }
-**JSON 스키마**가 `examples`를 지원하기 전 부터, OpenAPI는 `examples`이라 불리는 다른 필드를 지원해 왔습니다.
+**JSON 스키마**가 `examples`를 지원하기 전부터 OpenAPI는 `examples`이라 불리는 다른 필드를 지원해 왔습니다.
-이 **OpenAPI-특화** `examples`는 OpenAPI 명세서의 다른 구역으로 들어갑니다. 각 JSON 스키마 내부가 아니라 **각 *경로 작동* 세부 정보**에 포함됩니다.
+이 **OpenAPI-특화** `examples`는 OpenAPI 명세서의 다른 구역으로 들어갑니다. 각 JSON 스키마 내부가 아니라 **각 *경로 처리* 세부 정보**에 포함됩니다.
그리고 Swagger UI는 이 특정한 `examples` 필드를 한동안 지원했습니다. 그래서, 이를 다른 **문서 UI에 있는 예제**를 **표시**하기 위해 사용할 수 있습니다.
-이 OpenAPI-특화 필드인 `examples`의 형태는 (`list`대신에) **다중 예제**가 포함된 `dict`이며, 각각의 별도 정보 또한 **OpenAPI**에 추가될 것입니다.
+이 OpenAPI-특화 필드인 `examples`의 형태는 (`list` 대신에) **다중 예제**가 포함된 `dict`이며, 각각의 별도 정보 또한 **OpenAPI**에 추가될 것입니다.
-이는 OpenAPI에 포함된 JSON 스키마 안으로 포함되지 않으며, *경로 작동*에 직접적으로 포함됩니다.
+이는 OpenAPI에 포함된 각 JSON 스키마 안으로 포함되지 않으며, *경로 처리*에 직접적으로 포함됩니다.
-### `openapi_examples` 매개변수 사용하기
+### `openapi_examples` 매개변수 사용하기 { #using-the-openapi-examples-parameter }
-다음 예시 속에 OpenAPI-특화 `examples`를 FastAPI 안에서 매개변수 `openapi_examples` 매개변수와 함께 선언할 수 있습니다:
+다음에 대해 FastAPI에서 매개변수 `openapi_examples`로 OpenAPI-특화 `examples`를 선언할 수 있습니다:
* `Path()`
* `Query()`
@@ -122,26 +100,26 @@ Pydantic 모델과 같이 `Field()`를 사용할 때 추가적인 `examples`를
* `Form()`
* `File()`
-`dict`의 키가 또 다른 `dict`인 각 예제와 값을 구별합니다.
+`dict`의 키는 각 예제를 식별하고, 각 값은 또 다른 `dict`입니다.
-각각의 특정 `examples` 속 `dict` 예제는 다음을 포함할 수 있습니다:
+`examples` 안의 각 특정 예제 `dict`에는 다음이 포함될 수 있습니다:
* `summary`: 예제에 대한 짧은 설명문.
* `description`: 마크다운 텍스트를 포함할 수 있는 긴 설명문.
* `value`: 실제로 보여지는 예시, 예를 들면 `dict`.
-* `externalValue`: `value`의 대안이며 예제를 가르키는 URL. 비록 `value`처럼 많은 도구를 지원하지 못할 수 있습니다.
+* `externalValue`: `value`의 대안이며 예제를 가리키는 URL. 비록 `value`처럼 많은 도구를 지원하지 못할 수 있습니다.
이를 다음과 같이 사용할 수 있습니다:
{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *}
-### 문서 UI에서의 OpenAPI 예시
+### 문서 UI에서의 OpenAPI 예시 { #openapi-examples-in-the-docs-ui }
-`Body()`에 추가된 `openapi_examples`를 포함한 `/docs`는 다음과 같이 보일 것입니다:
+`Body()`에 `openapi_examples`가 추가되면 `/docs`는 다음과 같이 보일 것입니다:
-## 기술적 세부 사항
+## 기술적 세부 사항 { #technical-details }
/// tip | 팁
@@ -167,12 +145,12 @@ JSON 스키마는 `examples`를 가지고 있지 않았고, 따라서 OpenAPI는
OpenAPI는 또한 `example`과 `examples` 필드를 명세서의 다른 부분에 추가했습니다:
-* `(명세서에 있는) Parameter Object`는 FastAPI의 다음 기능에서 쓰였습니다:
+* `Parameter Object` (명세서에 있는)는 FastAPI의 다음 기능에서 쓰였습니다:
* `Path()`
* `Query()`
* `Header()`
* `Cookie()`
-* (명세서에 있는)`Media Type Object`속 `content`에 있는 `Request Body Object`는 FastAPI의 다음 기능에서 쓰였습니다:
+* `Request Body Object`, `Media Type Object` (명세서에 있는)의 `content` 필드에 있는는 FastAPI의 다음 기능에서 쓰였습니다:
* `Body()`
* `File()`
* `Form()`
@@ -183,15 +161,15 @@ OpenAPI는 또한 `example`과 `examples` 필드를 명세서의 다른 부분
///
-### JSON 스키마의 `examples` 필드
+### JSON 스키마의 `examples` 필드 { #json-schemas-examples-field }
-하지만, 후에 JSON 스키마는 `examples`필드를 명세서의 새 버전에 추가했습니다.
+하지만, 후에 JSON 스키마는 `examples` 필드를 명세서의 새 버전에 추가했습니다.
그리고 새로운 OpenAPI 3.1.0은 이 새로운 `examples` 필드가 포함된 최신 버전 (JSON 스키마 2020-12)을 기반으로 했습니다.
-이제 새로운 `examples` 필드는 이전의 단일 (그리고 커스텀) `example` 필드보다 우선되며, `example`은 사용하지 않는 것이 좋습니다.
+그리고 이제 이 새로운 `examples` 필드는 이제 지원 중단된, 예전의 단일 (그리고 커스텀) `example` 필드보다 우선됩니다.
-JSON 스키마의 새로운 `examples` 필드는 예제 속 **단순한 `list`**이며, (위에서 상술한 것처럼) OpenAPI의 다른 곳에 존재하는 dict으로 된 추가적인 메타데이터가 아닙니다.
+JSON 스키마의 새로운 `examples` 필드는 예제의 **단순한 `list`**일 뿐이며, (위에서 상술한 것처럼) OpenAPI의 다른 곳에 존재하는 추가 메타데이터가 있는 dict가 아닙니다.
/// info | 정보
@@ -201,24 +179,24 @@ JSON 스키마의 새로운 `examples` 필드는 예제 속 **단순한 `list`**
///
-### Pydantic과 FastAPI `examples`
+### Pydantic과 FastAPI `examples` { #pydantic-and-fastapi-examples }
-`examples`를 Pydantic 모델 속에 추가할 때, `schema_extra` 혹은 `Field(examples=["something"])`를 사용하면 Pydantic 모델의 **JSON 스키마**에 해당 예시가 추가됩니다.
+Pydantic 모델 안에 `examples`를 추가할 때, `schema_extra` 또는 `Field(examples=["something"])`를 사용하면 그 예제는 해당 Pydantic 모델의 **JSON 스키마**에 추가됩니다.
그리고 Pydantic 모델의 **JSON 스키마**는 API의 **OpenAPI**에 포함되고, 그 후 문서 UI 속에서 사용됩니다.
-FastAPI 0.99.0 이전 버전에서 (0.99.0 이상 버전은 새로운 OpenAPI 3.1.0을 사용합니다), `example` 혹은 `examples`를 다른 유틸리티(`Query()`, `Body()` 등)와 함께 사용했을 때, 저러한 예시는 데이터를 설명하는 JSON 스키마에 추가되지 않으며 (심지어 OpenAPI의 자체 JSON 스키마에도 포함되지 않습니다), OpenAPI의 *경로 작동* 선언에 직접적으로 추가됩니다 (JSON 스키마를 사용하는 OpenAPI 부분 외에도).
+FastAPI 0.99.0 이전 버전에서 (0.99.0 이상 버전은 새로운 OpenAPI 3.1.0을 사용합니다), 다른 유틸리티(`Query()`, `Body()` 등)와 함께 `example` 또는 `examples`를 사용했을 때, 그러한 예제는 그 데이터를 설명하는 JSON 스키마에 추가되지 않고 (OpenAPI 자체의 JSON 스키마에도 포함되지 않습니다), OpenAPI의 *경로 처리* 선언에 직접적으로 추가됩니다 (JSON 스키마를 사용하는 OpenAPI 부분 밖에서).
-하지만 지금은 FastAPI 0.99.0 및 이후 버전에서는 JSON 스키마 2020-12를 사용하는 OpenAPI 3.1.0과 Swagger UI 5.0.0 및 이후 버전을 사용하며, 모든 것이 더 일관성을 띄고 예시는 JSON 스키마에 포함됩니다.
+하지만 이제 FastAPI 0.99.0 및 이후 버전에서는 JSON 스키마 2020-12를 사용하는 OpenAPI 3.1.0과 Swagger UI 5.0.0 및 이후 버전을 사용하기 때문에, 모든 것이 더 일관성을 띄고 예제도 JSON 스키마에 포함됩니다.
-### Swagger UI와 OpenAPI-특화 `examples`
+### Swagger UI와 OpenAPI-특화 `examples` { #swagger-ui-and-openapi-specific-examples }
-현재 (2023-08-26), Swagger UI가 다중 JSON 스키마 예시를 지원하지 않으며, 사용자는 다중 예시를 문서에 표시하는 방법이 없었습니다.
+Swagger UI는 다중 JSON 스키마 예제를 지원하지 않았기 때문에(2023-08-26 기준), 사용자는 문서에 여러 예제를 표시할 방법이 없었습니다.
-이를 해결하기 위해, FastAPI `0.103.0`은 새로운 매개변수인 `openapi_examples`를 포함하는 예전 **OpenAPI-특화** `examples` 필드를 선언하기 위한 **지원을 추가**했습니다. 🤓
+이를 해결하기 위해, FastAPI `0.103.0`은 새로운 매개변수인 `openapi_examples`로 동일한 예전 **OpenAPI-특화** `examples` 필드를 선언하는 **지원**을 추가했습니다. 🤓
-### 요약
+### 요약 { #summary }
-저는 역사를 그다지 좋아하는 편이 아니라고 말하고는 했지만... "기술 역사" 강의를 가르치는 지금의 저를 보세요.
+저는 역사를 그다지 좋아하는 편이 아니라고 말하고는 했지만... "기술 역사" 강의를 하는 지금의 저를 보세요. 😅
-요약하자면 **FastAPI 0.99.0 혹은 그 이상의 버전**으로 업그레이드하는 것은 많은 것들이 더 **쉽고, 일관적이며 직관적이게** 되며, 여러분은 이 모든 역사적 세부 사항을 알 필요가 없습니다. 😎
+요약하자면 **FastAPI 0.99.0 혹은 그 이상의 버전**으로 업그레이드하면, 많은 것들이 훨씬 더 **단순하고, 일관적이며 직관적**이 되며, 여러분은 이 모든 역사적 세부 사항을 알 필요가 없습니다. 😎
diff --git a/docs/ko/docs/tutorial/security/get-current-user.md b/docs/ko/docs/tutorial/security/get-current-user.md
index 98ef3885e4..f21a22b7a0 100644
--- a/docs/ko/docs/tutorial/security/get-current-user.md
+++ b/docs/ko/docs/tutorial/security/get-current-user.md
@@ -1,105 +1,105 @@
-# 현재 사용자 가져오기
+# 현재 사용자 가져오기 { #get-current-user }
-이전 장에서 (의존성 주입 시스템을 기반으로 한)보안 시스템은 *경로 작동 함수*에서 `str`로 `token`을 제공했습니다:
+이전 장에서 (의존성 주입 시스템을 기반으로 한) 보안 시스템은 *경로 처리 함수*에 `str`로 `token`을 제공했습니다:
-{* ../../docs_src/security/tutorial001.py hl[10] *}
+{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
-그러나 아직도 유용하지 않습니다.
+하지만 이는 여전히 그다지 유용하지 않습니다.
-현재 사용자를 제공하도록 합시다.
+현재 사용자를 제공하도록 해봅시다.
-## 유저 모델 생성하기
+## 사용자 모델 생성하기 { #create-a-user-model }
-먼저 Pydantic 유저 모델을 만들어 보겠습니다.
+먼저 Pydantic 사용자 모델을 만들어 봅시다.
-Pydantic을 사용하여 본문을 선언하는 것과 같은 방식으로 다른 곳에서 사용할 수 있습니다.
+Pydantic을 사용해 본문을 선언하는 것과 같은 방식으로, 다른 곳에서도 어디서든 사용할 수 있습니다:
-{* ../../docs_src/security/tutorial002.py hl[5,12:16] *}
+{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *}
-## `get_current_user` 의존성 생성하기
+## `get_current_user` 의존성 생성하기 { #create-a-get-current-user-dependency }
의존성 `get_current_user`를 만들어 봅시다.
-의존성이 하위 의존성을 가질 수 있다는 것을 기억하십니까?
+의존성이 하위 의존성을 가질 수 있다는 것을 기억하시나요?
-`get_current_user`는 이전에 생성한 것과 동일한 `oauth2_scheme`과 종속성을 갖게 됩니다.
+`get_current_user`는 이전에 생성한 것과 동일한 `oauth2_scheme`에 대한 의존성을 갖게 됩니다.
-이전에 *경로 작동*에서 직접 수행했던 것과 동일하게 새 종속성 `get_current_user`는 하위 종속성 `oauth2_scheme`에서 `str`로 `token`을 수신합니다.
+이전에 *경로 처리*에서 직접 수행했던 것과 동일하게, 새 의존성 `get_current_user`는 하위 의존성 `oauth2_scheme`로부터 `str`로 `token`을 받게 됩니다:
-{* ../../docs_src/security/tutorial002.py hl[25] *}
+{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *}
-## 유저 가져오기
+## 사용자 가져오기 { #get-the-user }
-`get_current_user`는 토큰을 `str`로 취하고 Pydantic `User` 모델을 반환하는 우리가 만든 (가짜) 유틸리티 함수를 사용합니다.
+`get_current_user`는 우리가 만든 (가짜) 유틸리티 함수를 사용합니다. 이 함수는 `str`로 토큰을 받아 Pydantic `User` 모델을 반환합니다:
-{* ../../docs_src/security/tutorial002.py hl[19:22,26:27] *}
+{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *}
-## 현재 유저 주입하기
+## 현재 사용자 주입하기 { #inject-the-current-user }
-이제 *경로 작동*에서 `get_current_user`와 동일한 `Depends`를 사용할 수 있습니다.
+이제 *경로 처리*에서 `get_current_user`와 함께 같은 `Depends`를 사용할 수 있습니다:
-{* ../../docs_src/security/tutorial002.py hl[31] *}
+{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *}
-Pydantic 모델인 `User`로 `current_user`의 타입을 선언하는 것을 알아야 합니다.
+`current_user`의 타입을 Pydantic 모델 `User`로 선언한다는 점에 주목하세요.
-이것은 모든 완료 및 타입 검사를 통해 함수 내부에서 우리를 도울 것입니다.
+이는 함수 내부에서 자동 완성과 타입 체크에 도움을 줍니다.
/// tip | 팁
-요청 본문도 Pydantic 모델로 선언된다는 것을 기억할 것입니다.
+요청 본문도 Pydantic 모델로 선언된다는 것을 기억하실지도 모릅니다.
-여기서 **FastAPI**는 `Depends`를 사용하고 있기 때문에 혼동되지 않습니다.
+여기서 **FastAPI**는 `Depends`를 사용하고 있기 때문에 혼동하지 않습니다.
///
/// check | 확인
-이 의존성 시스템이 설계된 방식은 모두 `User` 모델을 반환하는 다양한 의존성(다른 "의존적인")을 가질 수 있도록 합니다.
+이 의존성 시스템이 설계된 방식은 모두 `User` 모델을 반환하는 서로 다른 의존성(서로 다른 "dependables")을 가질 수 있도록 합니다.
-해당 타입의 데이터를 반환할 수 있는 의존성이 하나만 있는 것으로 제한되지 않습니다.
+해당 타입의 데이터를 반환할 수 있는 의존성이 하나만 있어야 하는 것으로 제한되지 않습니다.
///
-## 다른 모델
+## 다른 모델 { #other-models }
-이제 *경로 작동 함수*에서 현재 사용자를 직접 가져올 수 있으며 `Depends`를 사용하여 **의존성 주입** 수준에서 보안 메커니즘을 처리할 수 있습니다.
+이제 *경로 처리 함수*에서 현재 사용자를 직접 가져올 수 있으며, `Depends`를 사용해 **의존성 주입** 수준에서 보안 메커니즘을 처리할 수 있습니다.
-그리고 보안 요구 사항에 대한 모든 모델 또는 데이터를 사용할 수 있습니다(이 경우 Pydantic 모델 `User`).
+그리고 보안 요구 사항을 위해 어떤 모델이나 데이터든 사용할 수 있습니다(이 경우 Pydantic 모델 `User`).
-그러나 일부 특정 데이터 모델, 클래스 또는 타입을 사용하도록 제한되지 않습니다.
+하지만 특정 데이터 모델, 클래스 또는 타입만 사용해야 하는 것은 아닙니다.
-모델에 `id`와 `email`이 있고 `username`이 없길 원하십니까? 맞습니다. 이들은 동일한 도구를 사용할 수 있습니다.
+모델에 `id`와 `email`이 있고 `username`은 없게 하고 싶으신가요? 물론입니다. 같은 도구를 사용할 수 있습니다.
-`str`만 갖고 싶습니까? 아니면 그냥 `dict`를 갖고 싶습니까? 아니면 데이터베이스 클래스 모델 인스턴스를 직접 갖고 싶습니까? 그들은 모두 같은 방식으로 작동합니다.
+`str`만 갖고 싶으신가요? 아니면 `dict`만요? 또는 데이터베이스 클래스 모델 인스턴스를 직접 쓰고 싶으신가요? 모두 같은 방식으로 동작합니다.
-실제로 애플리케이션에 로그인하는 사용자가 없지만 액세스 토큰만 있는 로봇, 봇 또는 기타 시스템이 있습니까? 다시 말하지만 모두 동일하게 작동합니다.
+애플리케이션에 로그인하는 사용자는 없고, 액세스 토큰만 가진 로봇, 봇 또는 다른 시스템만 있나요? 이것도 마찬가지로 모두 동일하게 동작합니다.
-애플리케이션에 필요한 모든 종류의 모델, 모든 종류의 클래스, 모든 종류의 데이터베이스를 사용하십시오. **FastAPI**는 의존성 주입 시스템을 다루었습니다.
+애플리케이션에 필요한 어떤 종류의 모델, 어떤 종류의 클래스, 어떤 종류의 데이터베이스든 사용하세요. **FastAPI**는 의존성 주입 시스템으로 이를 지원합니다.
-## 코드 사이즈
+## 코드 크기 { #code-size }
-이 예는 장황해 보일 수 있습니다. 동일한 파일에서 보안, 데이터 모델, 유틸리티 기능 및 *경로 작동*을 혼합하고 있음을 염두에 두십시오.
+이 예시는 장황해 보일 수 있습니다. 동일한 파일에서 보안, 데이터 모델, 유틸리티 함수 및 *경로 처리*를 섞어서 사용하고 있다는 점을 기억하세요.
-그러나 이게 키포인트입니다.
+하지만 여기 핵심이 있습니다.
-보안과 종속성 주입 항목을 한 번만 작성하면 됩니다.
+보안과 의존성 주입 관련 코드는 한 번만 작성합니다.
-그리고 원하는 만큼 복잡하게 만들 수 있습니다. 그래도 유연성과 함께 한 곳에 한 번에 작성할 수 있습니다.
+그리고 원하는 만큼 복잡하게 만들 수 있습니다. 그럼에도 여전히 한 번만, 한 곳에만 작성하면 됩니다. 유연성을 모두 유지하면서요.
-그러나 동일한 보안 시스템을 사용하여 수천 개의 엔드포인트(*경로 작동*)를 가질 수 있습니다.
+하지만 같은 보안 시스템을 사용해 수천 개의 엔드포인트(*경로 처리*)를 가질 수 있습니다.
-그리고 그들 모두(또는 원하는 부분)는 이러한 의존성 또는 생성한 다른 의존성을 재사용하는 이점을 얻을 수 있습니다.
+그리고 그들 모두(또는 원하는 일부)는 이러한 의존성 또는 여러분이 생성한 다른 의존성을 재사용하는 이점을 얻을 수 있습니다.
-그리고 이 수천 개의 *경로 작동*은 모두 3줄 정도로 줄일 수 있습니다.
+그리고 이 수천 개의 *경로 처리*는 3줄 정도로도 만들 수 있습니다:
-{* ../../docs_src/security/tutorial002.py hl[30:32] *}
+{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
-## 요약
+## 요약 { #recap }
-이제 *경로 작동 함수*에서 현재 사용자를 직접 가져올 수 있습니다.
+이제 *경로 처리 함수*에서 현재 사용자를 직접 가져올 수 있습니다.
-우리는 이미 이들 사이에 있습니다.
+우리는 이미 절반은 왔습니다.
-사용자/클라이언트가 실제로 `username`과 `password`를 보내려면 *경로 작동*을 추가하기만 하면 됩니다.
+사용자/클라이언트가 실제로 `username`과 `password`를 보내도록 하는 *경로 처리*만 추가하면 됩니다.
-다음 장을 확인해 봅시다.
+다음에 이어집니다.
diff --git a/docs/ko/docs/tutorial/security/oauth2-jwt.md b/docs/ko/docs/tutorial/security/oauth2-jwt.md
index 8d27856e84..907795ca4b 100644
--- a/docs/ko/docs/tutorial/security/oauth2-jwt.md
+++ b/docs/ko/docs/tutorial/security/oauth2-jwt.md
@@ -1,36 +1,36 @@
-# 패스워드 해싱을 이용한 OAuth2, JWT 토큰을 사용하는 Bearer 인증
+# 패스워드(해싱 포함)를 사용하는 OAuth2, JWT 토큰을 사용하는 Bearer { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens }
-모든 보안 흐름을 구성했으므로, 이제 JWT 토큰과 패스워드 해싱을 사용해 애플리케이션을 안전하게 만들 것입니다.
+모든 보안 흐름을 구성했으므로, 이제 JWT 토큰과 안전한 패스워드 해싱을 사용해 애플리케이션을 실제로 안전하게 만들겠습니다.
-이 코드는 실제로 애플리케이션에서 패스워드를 해싱하여 DB에 저장하는 등의 작업에 활용할 수 있습니다.
+이 코드는 실제로 애플리케이션에서 사용할 수 있으며, 패스워드 해시를 데이터베이스에 저장하는 등의 작업에 활용할 수 있습니다.
-이전 장에 이어서 시작해 봅시다.
+이전 장에서 멈춘 지점부터 시작해 내용을 확장해 나가겠습니다.
-## JWT
+## JWT 알아보기 { #about-jwt }
-JWT 는 "JSON Web Tokens" 을 의미합니다.
+JWT는 "JSON Web Tokens"를 의미합니다.
-JSON 객체를 공백이 없는 긴 문자열로 인코딩하는 표준이며, 다음과 같은 형태입니다:
+JSON 객체를 공백이 없는 길고 밀집된 문자열로 부호화하는 표준입니다. 다음과 같은 형태입니다:
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```
-JWT는 암호화되지 않아 누구든지 토큰에서 정보를 복원할 수 있습니다.
+암호화된 것이 아니므로, 누구나 내용에서 정보를 복원할 수 있습니다.
-하지만 JWT는 서명되어 있습니다. 그래서 자신이 발급한 토큰을 받았을 때, 실제로 자신이 발급한게 맞는지 검증할 수 있습니다.
+하지만 서명되어 있습니다. 따라서 자신이 발급한 토큰을 받았을 때, 실제로 자신이 발급한 것이 맞는지 검증할 수 있습니다.
-만료 기간이 일주일인 토큰을 발행했다고 가정해 봅시다. 다음 날 사용자가 토큰을 가져왔을 때, 그 사용자가 시스템에 여전히 로그인되어 있다는 것을 알 수 있습니다.
+예를 들어 만료 기간이 1주일인 토큰을 생성할 수 있습니다. 그리고 사용자가 다음 날 토큰을 가지고 돌아오면, 그 사용자가 시스템에 여전히 로그인되어 있다는 것을 알 수 있습니다.
-일주일 뒤에는 토큰이 만료될 것이고, 사용자는 인가되지 않아 새 토큰을 받기 위해 다시 로그인해야 할 것입니다. 만약 사용자(또는 제3자)가 토큰을 수정하거나 만료일을 변경하면, 서명이 일치하지 않기 때문에 알아챌 수 있을 것입니다.
+1주일 뒤에는 토큰이 만료되고 사용자는 인가되지 않으므로 새 토큰을 받기 위해 다시 로그인해야 합니다. 그리고 사용자(또는 제3자)가 만료 시간을 바꾸기 위해 토큰을 수정하려고 하면, 서명이 일치하지 않기 때문에 이를 알아챌 수 있습니다.
-만약 JWT 토큰을 다뤄보고, 작동 방식도 알아보고 싶다면 https://jwt.io 을 확인하십시오.
+JWT 토큰을 직접 다뤄보고 동작 방식을 확인해보고 싶다면 https://jwt.io를 확인하십시오.
-## `PyJWT` 설치
+## `PyJWT` 설치 { #install-pyjwt }
-파이썬으로 JWT 토큰을 생성하고 검증하려면 `PyJWT` 를 설치해야 합니다.
+Python에서 JWT 토큰을 생성하고 검증하려면 `PyJWT`를 설치해야 합니다.
-[가상환경](../../virtual-environments.md){.internal-link target=_blank} 을 만들고 활성화한 다음 `pyjwt` 를 설치하십시오:
+[가상환경](../../virtual-environments.md){.internal-link target=_blank}을 만들고 활성화한 다음 `pyjwt`를 설치하십시오:
-이전과 같은 방법으로 애플리케이션에 인증하십시오.
+이전과 같은 방법으로 애플리케이션을 인가하십시오.
다음 인증 정보를 사용하십시오:
@@ -211,13 +211,13 @@ Password: `secret`
/// check
-코드 어디에도 평문 패스워드 "`secret`" 이 없다는 점에 유의하십시오. 해시된 버전만 있습니다.
+코드 어디에도 평문 패스워드 "`secret`"은 없고, 해시된 버전만 있다는 점에 유의하십시오.
///
-`/users/me/` 를 호출하면 다음과 같은 응답을 얻을 수 있습니다:
+엔드포인트 `/users/me/`를 호출하면 다음과 같은 응답을 받게 됩니다:
```JSON
{
@@ -230,44 +230,44 @@ Password: `secret`
-개발자 도구를 열어보면 전송된 데이터에 토큰만 포함된 것을 확인할 수 있습니다. 패스워드는 사용자를 인증하고 액세스 토큰을 받기 위한 첫 번째 요청에만 전송되며, 이후에는 전송되지 않습니다:
+개발자 도구를 열어보면 전송된 데이터에는 토큰만 포함되어 있고, 패스워드는 사용자를 인증하고 해당 액세스 토큰을 얻기 위한 첫 번째 요청에서만 전송되며 이후에는 전송되지 않는 것을 확인할 수 있습니다:
/// note
-`Bearer `로 시작하는 `Authorization` 헤더에 주목하십시오.
+`Bearer `로 시작하는 값을 가진 `Authorization` 헤더에 주목하십시오.
///
-## `scopes` 의 고급 사용법
+## `scopes`의 고급 사용법 { #advanced-usage-with-scopes }
-OAuth2는 "스코프(scopes)" 라는 개념을 갖고 있습니다.
+OAuth2에는 "scopes"라는 개념이 있습니다.
-이를 사용하여 JWT 토큰에 특정 권한 집합을 추가할 수 있습니다.
+이를 사용해 JWT 토큰에 특정 권한 집합을 추가할 수 있습니다.
-그 후 이 토큰을 사용자에게 직접 제공하거나 제3자에게 제공하여, 특정 제한사항 하에있는 API와 통신하도록 할 수 있습니다.
+그런 다음 이 토큰을 사용자에게 직접 제공하거나 제3자에게 제공하여, 특정 제한사항 하에서 API와 상호작용하도록 할 수 있습니다.
-**FastAPI** 에서의 사용 방법과 통합 방식은 **심화 사용자 안내서** 에서 자세히 배울 수 있습니다.
+어떻게 사용하는지, 그리고 **FastAPI**에 어떻게 통합되는지는 이후 **심화 사용자 안내서**에서 배울 수 있습니다.
-## 요약
+## 요약 { #recap }
-지금까지 살펴본 내용을 바탕으로, OAuth2와 JWT 같은 표준을 사용하여 안전한 **FastAPI** 애플리케이션을 만들 수 있습니다.
+지금까지 살펴본 내용을 바탕으로, OAuth2와 JWT 같은 표준을 사용해 안전한 **FastAPI** 애플리케이션을 설정할 수 있습니다.
-거의 모든 프레임워크에서 보안 처리는 상당히 복잡한 주제입니다.
+거의 모든 프레임워크에서 보안 처리는 꽤 빠르게 복잡한 주제가 됩니다.
-이를 단순화하는 많은 패키지는 데이터 모델, 데이터베이스, 사용 가능한 기능들에 대해 여러 제약이 있습니다. 그리고 지나치게 단순화하는 일부 패키지들은 심각한 보안 결함을 가질 수도 있습니다.
+이를 크게 단순화하는 많은 패키지들은 데이터 모델, 데이터베이스, 사용 가능한 기능들에 대해 많은 타협을 해야 합니다. 그리고 지나치게 단순화하는 일부 패키지들은 실제로 내부에 보안 결함이 있기도 합니다.
---
-**FastAPI** 는 어떤 데이터베이스, 데이터 모델, 도구도 강요하지 않습니다.
+**FastAPI**는 어떤 데이터베이스, 데이터 모델, 도구에도 타협하지 않습니다.
-프로젝트에 가장 적합한 것을 선택할 수 있는 유연성을 제공합니다.
+프로젝트에 가장 잘 맞는 것들을 선택할 수 있는 모든 유연성을 제공합니다.
-그리고 `passlib` 와 `PyJWT` 처럼 잘 관리되고 널리 사용되는 패키지들을 바로 사용할 수 있습니다. **FastAPI** 는 외부 패키지 통합을 위해 복잡한 메커니즘이 필요하지 않기 때문입니다.
+그리고 **FastAPI**는 외부 패키지를 통합하기 위해 복잡한 메커니즘을 요구하지 않기 때문에 `pwdlib`와 `PyJWT` 같은 잘 관리되고 널리 사용되는 패키지들을 바로 사용할 수 있습니다.
-그러나 유연성, 견고성, 보안성을 해치지 않으면서 과정을 단순화할 수 있는 도구들을 제공합니다.
+하지만 유연성, 견고성, 보안성을 해치지 않으면서 과정을 가능한 한 단순화할 수 있도록 도구들을 제공합니다.
-그리고 OAuth2와 같은 표준 프로토콜을 비교적 간단한 방법으로 구현하고 사용할 수 있습니다.
+또한 OAuth2 같은 안전한 표준 프로토콜을 비교적 간단한 방식으로 사용하고 구현할 수 있습니다.
-더 세분화된 권한 체계를 위해 OAuth2의 "스코프"를 사용하는 방법은 **심화 사용자 안내서**에서 더 자세히 배울 수 있습니다. OAuth2의 스코프는 제3자 애플리케이션이 사용자를 대신해 그들의 API와 상호작용하도록 권한을 부여하기 위해, Facebook, Google, GitHub, Microsoft, X (Twitter) 등의 많은 대형 인증 제공업체들이 사용하는 메커니즘입니다.
+더 세분화된 권한 시스템을 위해 OAuth2 "scopes"를 사용하는 방법은, 같은 표준을 따르는 방식으로 **심화 사용자 안내서**에서 더 자세히 배울 수 있습니다. 스코프를 사용하는 OAuth2는 Facebook, Google, GitHub, Microsoft, X (Twitter) 등 많은 대형 인증 제공업체들이 제3자 애플리케이션이 사용자 대신 그들의 API와 상호작용할 수 있도록 인가하는 데 사용하는 메커니즘입니다.
diff --git a/docs/ko/docs/tutorial/security/simple-oauth2.md b/docs/ko/docs/tutorial/security/simple-oauth2.md
index f10c4f588d..189dd89f2a 100644
--- a/docs/ko/docs/tutorial/security/simple-oauth2.md
+++ b/docs/ko/docs/tutorial/security/simple-oauth2.md
@@ -1,8 +1,8 @@
-# 패스워드와 Bearer를 이용한 간단한 OAuth2
+# 패스워드와 Bearer를 이용한 간단한 OAuth2 { #simple-oauth2-with-password-and-bearer }
이제 이전 장에서 빌드하고 누락된 부분을 추가하여 완전한 보안 흐름을 갖도록 하겠습니다.
-## `username`와 `password` 얻기
+## `username`와 `password` 얻기 { #get-the-username-and-password }
**FastAPI** 보안 유틸리티를 사용하여 `username` 및 `password`를 가져올 것입니다.
@@ -14,11 +14,11 @@ OAuth2는 (우리가 사용하고 있는) "패스워드 플로우"을 사용할
그리고 데이터베이스 모델은 원하는 다른 이름을 사용할 수 있습니다.
-그러나 로그인 *경로 작동*의 경우 사양과 호환되도록 이러한 이름을 사용해야 합니다(예를 들어 통합 API 문서 시스템을 사용할 수 있어야 합니다).
+그러나 로그인 *경로 처리*의 경우 사양과 호환되도록 이러한 이름을 사용해야 합니다(예를 들어 통합 API 문서 시스템을 사용할 수 있어야 합니다).
사양에는 또한 `username`과 `password`가 폼 데이터로 전송되어야 한다고 명시되어 있습니다(따라서 여기에는 JSON이 없습니다).
-### `scope`
+### `scope` { #scope }
사양에는 클라이언트가 다른 폼 필드 "`scope`"를 보낼 수 있다고 나와 있습니다.
@@ -44,15 +44,15 @@ OAuth2의 경우 문자열일 뿐입니다.
///
-## `username`과 `password`를 가져오는 코드
+## `username`과 `password`를 가져오는 코드 { #code-to-get-the-username-and-password }
이제 **FastAPI**에서 제공하는 유틸리티를 사용하여 이를 처리해 보겠습니다.
-### `OAuth2PasswordRequestForm`
+### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform }
-먼저 `OAuth2PasswordRequestForm`을 가져와 `/token`에 대한 *경로 작동*에서 `Depends`의 의존성으로 사용합니다.
+먼저 `OAuth2PasswordRequestForm`을 가져와 `/token`에 대한 *경로 처리*에서 `Depends`의 의존성으로 사용합니다.
-{* ../../docs_src/security/tutorial003.py hl[4,76] *}
+{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *}
`OAuth2PasswordRequestForm`은 다음을 사용하여 폼 본문을 선언하는 클래스 의존성입니다:
@@ -84,7 +84,7 @@ OAuth2 사양은 실제로 `password`라는 고정 값이 있는 `grant_type`
///
-### 폼 데이터 사용하기
+### 폼 데이터 사용하기 { #use-the-form-data }
/// tip | 팁
@@ -100,9 +100,9 @@ OAuth2 사양은 실제로 `password`라는 고정 값이 있는 `grant_type`
오류의 경우 `HTTPException` 예외를 사용합니다:
-{* ../../docs_src/security/tutorial003.py hl[3,77:79] *}
+{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *}
-### 패스워드 확인하기
+### 패스워드 확인하기 { #check-the-password }
이 시점에서 데이터베이스의 사용자 데이터 형식을 확인했지만 암호를 확인하지 않았습니다.
@@ -112,7 +112,7 @@ OAuth2 사양은 실제로 `password`라는 고정 값이 있는 `grant_type`
두 패스워드가 일치하지 않으면 동일한 오류가 반환됩니다.
-#### 패스워드 해싱
+#### 패스워드 해싱 { #password-hashing }
"해싱"은 일부 콘텐츠(이 경우 패스워드)를 횡설수설하는 것처럼 보이는 일련의 바이트(문자열)로 변환하는 것을 의미합니다.
@@ -120,21 +120,15 @@ OAuth2 사양은 실제로 `password`라는 고정 값이 있는 `grant_type`
그러나 횡설수설에서 암호로 다시 변환할 수는 없습니다.
-##### 패스워드 해싱을 사용해야 하는 이유
+##### 패스워드 해싱을 사용해야 하는 이유 { #why-use-password-hashing }
데이터베이스가 유출된 경우 해커는 사용자의 일반 텍스트 암호가 아니라 해시만 갖게 됩니다.
따라서 해커는 다른 시스템에서 동일한 암호를 사용하려고 시도할 수 없습니다(많은 사용자가 모든 곳에서 동일한 암호를 사용하므로 이는 위험할 수 있습니다).
-//// tab | 파이썬 3.7 이상
+{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *}
-{* ../../docs_src/security/tutorial003.py hl[80:83] *}
-
-////
-
-{* ../../docs_src/security/tutorial003_py310.py hl[78:81] *}
-
-#### `**user_dict`에 대해
+#### `**user_dict`에 대해 { #about-user-dict }
`UserInDB(**user_dict)`는 다음을 의미한다:
@@ -152,11 +146,11 @@ UserInDB(
/// info | 정보
-`**user_dict`에 대한 자세한 설명은 [**추가 모델** 문서](../extra-models.md#about-user_indict){.internal-link target=_blank}를 다시 읽어봅시다.
+`**user_dict`에 대한 자세한 설명은 [**추가 모델** 문서](../extra-models.md#about-user-in-dict){.internal-link target=_blank}를 다시 확인해보세요.
///
-## 토큰 반환하기
+## 토큰 반환하기 { #return-the-token }
`token` 엔드포인트의 응답은 JSON 객체여야 합니다.
@@ -174,7 +168,7 @@ UserInDB(
///
-{* ../../docs_src/security/tutorial003.py hl[85] *}
+{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *}
/// tip | 팁
@@ -188,19 +182,19 @@ UserInDB(
///
-## 의존성 업데이트하기
+## 의존성 업데이트하기 { #update-the-dependencies }
이제 의존성을 업데이트를 할 겁니다.
이 사용자가 활성화되어 있는 *경우에만* `current_user`를 가져올 겁니다.
-따라서 `get_current_user`를 의존성으로 사용하는 추가 종속성 `get_current_active_user`를 만듭니다.
+따라서 `get_current_user`를 의존성으로 사용하는 추가 의존성 `get_current_active_user`를 만듭니다.
이러한 의존성 모두, 사용자가 존재하지 않거나 비활성인 경우 HTTP 오류를 반환합니다.
따라서 엔드포인트에서는 사용자가 존재하고 올바르게 인증되었으며 활성 상태인 경우에만 사용자를 얻습니다:
-{* ../../docs_src/security/tutorial003.py hl[58:66,69:72,90] *}
+{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *}
/// info | 정보
@@ -220,11 +214,11 @@ UserInDB(
///
-## 확인하기
+## 확인하기 { #see-it-in-action }
대화형 문서 열기: http://127.0.0.1:8000/docs.
-### 인증하기
+### 인증하기 { #authenticate }
"Authorize" 버튼을 눌러봅시다.
@@ -240,7 +234,7 @@ UserInDB(
-### 자신의 유저 데이터 가져오기
+### 자신의 유저 데이터 가져오기 { #get-your-own-user-data }
이제 `/users/me` 경로에 `GET` 작업을 진행합시다.
@@ -266,7 +260,7 @@ UserInDB(
}
```
-### 비활성된 유저
+### 비활성된 유저 { #inactive-user }
이제 비활성된 사용자로 시도하고, 인증해봅시다:
@@ -284,7 +278,7 @@ UserInDB(
}
```
-## 요약
+## 요약 { #recap }
이제 API에 대한 `username` 및 `password`를 기반으로 완전한 보안 시스템을 구현할 수 있는 도구가 있습니다.
diff --git a/docs/ko/docs/tutorial/sql-databases.md b/docs/ko/docs/tutorial/sql-databases.md
index 58c7017d6a..3d64cf627a 100644
--- a/docs/ko/docs/tutorial/sql-databases.md
+++ b/docs/ko/docs/tutorial/sql-databases.md
@@ -1,18 +1,18 @@
-# SQL (관계형) 데이터베이스
+# SQL (관계형) 데이터베이스 { #sql-relational-databases }
-**FastAPI**에서 SQL(관계형) 데이터베이스 사용은 필수가 아닙니다. 여러분이 원하는 **어떤 데이터베이스든** 사용할 수 있습니다.
+**FastAPI**에서 SQL(관계형) 데이터베이스 사용은 필수가 아닙니다. 하지만 여러분이 원하는 **어떤 데이터베이스든** 사용할 수 있습니다.
여기서는 SQLModel을 사용하는 예제를 살펴보겠습니다.
-**SQLModel**은 SQLAlchemy와 Pydantic을 기반으로 구축되었습니다.SQLModel은 **SQL 데이터베이스**를 사용하는 FastAPI 애플리케이션에 완벽히 어울리도록 **FastAPI**의 제작자가 설계한 도구입니다.
+**SQLModel**은 SQLAlchemy와 Pydantic을 기반으로 구축되었습니다. **SQL 데이터베이스**를 사용해야 하는 FastAPI 애플리케이션에 완벽히 어울리도록 **FastAPI**와 같은 제작자가 만든 도구입니다.
/// tip | 팁
-다른 SQL 또는 NoSQL 데이터베이스 라이브러리를 사용할 수도 있습니다 (일부는 "ORM"이라고도 불립니다), FastAPI는 특정 라이브러리의 사용을 강요하지 않습니다. 😎
+다른 SQL 또는 NoSQL 데이터베이스 라이브러리를 사용할 수도 있습니다 (일부는 "ORMs"이라고도 불립니다), FastAPI는 특정 라이브러리의 사용을 강요하지 않습니다. 😎
///
-SQLModel은 SQLAlchemy를 기반으로 하므로, SQLAlchemy에서 **지원하는 모든 데이터베이스**를 손쉽게 사용할 수 있습니다(SQLModel에서도 동일하게 지원됩니다). 예를 들면:
+SQLModel은 SQLAlchemy를 기반으로 하므로, SQLAlchemy에서 **지원하는 모든 데이터베이스**를 손쉽게 사용할 수 있습니다(이것들은 SQLModel에서도 지원됩니다). 예를 들면:
* PostgreSQL
* MySQL
@@ -20,19 +20,19 @@ SQLModel은 SQLAlchemy를 기반으로 하므로, SQLAlchemy에서 **지원하
* Oracle
* Microsoft SQL Server 등.
-이 예제에서는 **SQLite**를 사용합니다. SQLite는 단일 파일을 사용하고 파이썬에서 기본적으로 지원하기 때문입니다. 따라서 이 예제를 그대로 복사하여 실행할 수 있습니다.
+이 예제에서는 **SQLite**를 사용합니다. SQLite는 단일 파일을 사용하고 Python에서 통합 지원하기 때문입니다. 따라서 이 예제를 그대로 복사하여 실행할 수 있습니다.
-나중에 실제 프로덕션 애플리케이션에서는 **PostgreSQL**과 같은 데이터베이스 서버를 사용하는 것이 좋습니다.
+나중에 프로덕션 애플리케이션에서는 **PostgreSQL**과 같은 데이터베이스 서버를 사용하는 것이 좋습니다.
/// tip | 팁
-**FastAPI**와 **PostgreSQL**를 포함하여 프론트엔드와 다양한 도구를 제공하는 공식 프로젝트 생성기가 있습니다: https://github.com/fastapi/full-stack-fastapi-template
+프론트엔드와 더 많은 도구를 포함하여 **FastAPI**와 **PostgreSQL**을 포함한 공식 프로젝트 생성기가 있습니다: https://github.com/fastapi/full-stack-fastapi-template
///
-이 튜토리얼은 매우 간단하고 짧습니다. 데이터베이스 기본 개념, SQL, 또는 더 복잡한 기능에 대해 배우고 싶다면, SQLModel 문서를 참고하세요.
+이 튜토리얼은 매우 간단하고 짧습니다. 데이터베이스 기본 개념, SQL, 또는 더 고급 기능에 대해 배우고 싶다면, SQLModel 문서를 참고하세요.
-## `SQLModel` 설치하기
+## `SQLModel` 설치하기 { #install-sqlmodel }
먼저, [가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, `sqlmodel`을 설치하세요:
@@ -45,13 +45,13 @@ $ pip install sqlmodel
-## 단일 모델로 애플리케이션 생성하기
+## 단일 모델로 애플리케이션 생성하기 { #create-the-app-with-a-single-model }
우선 단일 **SQLModel** 모델을 사용하여 애플리케이션의 가장 간단한 첫 번째 버전을 생성해보겠습니다.
-이후 **다중 모델**을 추가하여 보안과 유연성을 강화할 것입니다. 🤓
+이후 아래에서 **여러 모델**로 보안과 유연성을 강화하며 개선하겠습니다. 🤓
-### 모델 생성하기
+### 모델 생성하기 { #create-models }
`SQLModel`을 가져오고 데이터베이스 모델을 생성합니다:
@@ -61,45 +61,45 @@ $ pip install sqlmodel
몇 가지 차이점이 있습니다:
-* `table=True`는 SQLModel에 이 모델이 *테이블 모델*이며, 단순한 데이터 모델이 아니라 SQL 데이터베이스의 **테이블**을 나타낸다는 것을 알려줍니다. (다른 일반적인 Pydantic 클래스처럼) 단순한 *데이터 모델*이 아닙니다.
+* `table=True`는 SQLModel에 이 모델이 *테이블 모델*이며, SQL 데이터베이스의 **테이블**을 나타내야 한다는 것을 알려줍니다. (다른 일반적인 Pydantic 클래스처럼) 단순한 *데이터 모델*이 아닙니다.
* `Field(primary_key=True)`는 SQLModel에 `id`가 SQL 데이터베이스의 **기본 키**임을 알려줍니다 (SQL 기본 키에 대한 자세한 내용은 SQLModel 문서를 참고하세요).
- `int | None` 유형으로 설정하면, SQLModel은 해당 열이 SQL 데이터베이스에서 `INTEGER` 유형이며 `NULLABLE` 값이어야 한다는 것을 알 수 있습니다.
+ **참고:** 기본 키 필드에 `int | None`을 사용하는 이유는, Python 코드에서 *`id` 없이 객체를 생성*할 수 있게 하기 위해서입니다(`id=None`). 데이터베이스가 *저장할 때 생성해 줄 것*이라고 가정합니다. SQLModel은 데이터베이스가 `id`를 제공한다는 것을 이해하고, 데이터베이스 스키마에서 *해당 열을 null이 아닌 `INTEGER`*로 정의합니다. 자세한 내용은 기본 키에 대한 SQLModel 문서를 참고하세요.
-* `Field(index=True)`는 SQLModel에 해당 열에 대해 **SQL 인덱스**를 생성하도록 지시합니다. 이를 통해 데이터베이스에서 이 열으로 필터링된 데이터를 읽을 때 더 빠르게 조회할 수 있습니다.
+* `Field(index=True)`는 SQLModel에 해당 열에 대해 **SQL 인덱스**를 생성하도록 지시합니다. 이를 통해 데이터베이스에서 이 열로 필터링된 데이터를 읽을 때 더 빠르게 조회할 수 있습니다.
SQLModel은 `str`으로 선언된 항목이 SQL 데이터베이스에서 `TEXT` (또는 데이터베이스에 따라 `VARCHAR`) 유형의 열로 저장된다는 것을 인식합니다.
-### 엔진 생성하기
+### 엔진 생성하기 { #create-an-engine }
SQLModel의 `engine` (내부적으로는 SQLAlchemy `engine`)은 데이터베이스에 대한 **연결을 유지**하는 역할을 합니다.
-**하나의 단일 engine 객체**를 통해 코드 전체에서 동일한 데이터베이스에 연결할 수 있습니다.
+코드 전체에서 동일한 데이터베이스에 연결하기 위해 **하나의 단일 `engine` 객체**를 사용합니다.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *}
-`check_same_thread=False`를 사용하면 FastAPI에서 여러 스레드에서 동일한 SQLite 데이터베이스를 사용할 수 있습니다. 이는 **하나의 단일 요청**이 **여러 스레드**를 사용할 수 있기 때문에 필요합니다(예: 의존성에서 사용되는 경우).
+`check_same_thread=False`를 사용하면 FastAPI에서 여러 스레드에서 동일한 SQLite 데이터베이스를 사용할 수 있습니다. 이는 **하나의 단일 요청**이 **둘 이상의 스레드**를 사용할 수 있기 때문에 필요합니다(예: 의존성에서 사용되는 경우).
-걱정하지 마세요. 코드가 구조화된 방식으로 인해, 이후에 **각 요청마다 단일 SQLModel *세션*을 사용**하도록 보장할 것입니다. 실제로 그것이 `check_same_thread`가 하려는 것입니다.
+걱정하지 마세요. 코드가 구조화된 방식으로 인해, 이후에 **각 요청마다 단일 SQLModel *세션*을 사용**하도록 보장할 것입니다. 실제로 이것이 `check_same_thread`가 하려는 것입니다.
-### 테이블 생성하기
+### 테이블 생성하기 { #create-the-tables }
그 다음 `SQLModel.metadata.create_all(engine)`을 사용하여 모든 *테이블 모델*의 **테이블을 생성**하는 함수를 추가합니다.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *}
-### 세션 의존성 생성하기
+### 세션 의존성 생성하기 { #create-a-session-dependency }
**`Session`**은 **메모리에 객체**를 저장하고 데이터에 필요한 모든 변경 사항을 추적한 후, **`engine`을 통해** 데이터베이스와 통신합니다.
`yield`를 사용해 FastAPI의 **의존성**을 생성하여 각 요청마다 새로운 `Session`을 제공합니다. 이는 요청당 하나의 세션만 사용되도록 보장합니다. 🤓
-그런 다음 이 의존성을 사용하는 코드를 간소화하기 위해 `Annotated` 의존성 `SessionDep`을 생성합니다.
+그런 다음 이 의존성을 사용하는 나머지 코드를 간소화하기 위해 `Annotated` 의존성 `SessionDep`을 생성합니다.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *}
-### 시작 시 데이터베이스 테이블 생성하기
+### 시작 시 데이터베이스 테이블 생성하기 { #create-database-tables-on-startup }
애플리케이션 시작 시 데이터베이스 테이블을 생성합니다.
@@ -115,9 +115,9 @@ SQLModel은 Alembic을 감싸는 마이그레이션 유틸리티를 제공할
///
-### Hero 생성하기
+### Hero 생성하기 { #create-a-hero }
-각 SQLModel 모델은 Pydantic 모델이기도 하므로, Pydantic 모델을 사용할 수 있는 **타입 어노테이**션에서 동일하게 사용할 수 있습니다.
+각 SQLModel 모델은 Pydantic 모델이기도 하므로, Pydantic 모델을 사용할 수 있는 동일한 **타입 어노테이션**에서 사용할 수 있습니다.
예를 들어, 파라미터를 `Hero` 타입으로 선언하면 **JSON 본문**에서 값을 읽어옵니다.
@@ -125,31 +125,29 @@ SQLModel은 Alembic을 감싸는 마이그레이션 유틸리티를 제공할
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *}
-
+여기서는 `SessionDep` 의존성(`Session`)을 사용하여 새로운 `Hero`를 `Session` 인스턴스에 추가하고, 데이터베이스에 변경 사항을 커밋하고, `hero` 데이터의 최신 상태를 갱신한 다음 이를 반환합니다.
-여기서 `SessionDep` 의존성 (즉, `Session`)을 사용하여 새로운 `Hero`를 `Session` 인스턴스에 추가하고, 데이터베이스에 변경 사항을 커밋하고, `hero` 데이터의 최신 상태를 갱신한 다음 이를 반환합니다.
-
-### Heroes 조회하기
+### Heroes 조회하기 { #read-heroes }
`select()`를 사용하여 데이터베이스에서 `Hero`를 **조회**할 수 있습니다. 결과에 페이지네이션을 적용하기 위해 `limit`와 `offset`을 포함할 수 있습니다.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}
-### 단일 Hero 조회하기
+### 단일 Hero 조회하기 { #read-one-hero }
단일 `Hero`를 **조회**할 수도 있습니다.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}
-### Hero 삭제하기
+### Hero 삭제하기 { #delete-a-hero }
`Hero`를 **삭제**하는 것도 가능합니다.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *}
-### 애플리케이션 실행하기
+### 애플리케이션 실행하기 { #run-the-app }
-애플리케이션을 실행하려면 다음 명령을 사용합니다:
+애플리케이션을 실행할 수 있습니다:
lt
+* XWT
+* PSGI
+
+### O abbr fornece uma explicação { #the-abbr-gives-an-explanation }
+
+* cluster
+* Deep Learning
+
+### O abbr fornece uma frase completa e uma explicação { #the-abbr-gives-a-full-phrase-and-an-explanation }
+
+* MDN
+* I/O.
+
+////
+
+//// tab | Informações
+
+Os atributos "title" dos elementos "abbr" são traduzidos seguindo algumas instruções específicas.
+
+As traduções podem adicionar seus próprios elementos "abbr" que o LLM não deve remover. Por exemplo, para explicar palavras em inglês.
+
+Veja a seção `### HTML abbr elements` no prompt geral em `scripts/translate.py`.
+
+////
+
+## Títulos { #headings }
+
+//// tab | Teste
+
+### Desenvolver uma aplicação web - um tutorial { #develop-a-webapp-a-tutorial }
+
+Olá.
+
+### Anotações de tipo e -anotações { #type-hints-and-annotations }
+
+Olá novamente.
+
+### Super- e subclasses { #super-and-subclasses }
+
+Olá novamente.
+
+////
+
+//// tab | Informações
+
+A única regra rígida para títulos é que o LLM deixe a parte do hash dentro de chaves inalterada, o que garante que os links não quebrem.
+
+Veja a seção `### Headings` no prompt geral em `scripts/translate.py`.
+
+Para algumas instruções específicas do idioma, veja, por exemplo, a seção `### Headings` em `docs/de/llm-prompt.md`.
+
+////
+
+## Termos usados na documentação { #terms-used-in-the-docs }
+
+//// tab | Teste
+
+* você
+* seu
+
+* por exemplo
+* etc.
+
+* `foo` como um `int`
+* `bar` como uma `str`
+* `baz` como uma `list`
+
+* o Tutorial - Guia do Usuário
+* o Guia do Usuário Avançado
+* a documentação do SQLModel
+* a documentação da API
+* a documentação automática
+
+* Ciência de Dados
+* Deep Learning
+* Aprendizado de Máquina
+* Injeção de Dependências
+* autenticação HTTP Basic
+* HTTP Digest
+* formato ISO
+* o padrão JSON Schema
+* o JSON schema
+* a definição do schema
+* Fluxo de Senha
+* Mobile
+
+* descontinuado
+* projetado
+* inválido
+* dinamicamente
+* padrão
+* padrão predefinido
+* sensível a maiúsculas e minúsculas
+* não sensível a maiúsculas e minúsculas
+
+* servir a aplicação
+* servir a página
+
+* o app
+* a aplicação
+
+* a requisição
+* a resposta
+* a resposta de erro
+
+* a operação de rota
+* o decorador de operação de rota
+* a função de operação de rota
+
+* o corpo
+* o corpo da requisição
+* o corpo da resposta
+* o corpo JSON
+* o corpo do formulário
+* o corpo do arquivo
+* o corpo da função
+
+* o parâmetro
+* o parâmetro de corpo
+* o parâmetro de path
+* o parâmetro de query
+* o parâmetro de cookie
+* o parâmetro de header
+* o parâmetro de formulário
+* o parâmetro da função
+
+* o evento
+* o evento de inicialização
+* a inicialização do servidor
+* o evento de encerramento
+* o evento de lifespan
+
+* o manipulador
+* o manipulador de eventos
+* o manipulador de exceções
+* tratar
+
+* o modelo
+* o modelo Pydantic
+* o modelo de dados
+* o modelo de banco de dados
+* o modelo de formulário
+* o objeto de modelo
+
+* a classe
+* a classe base
+* a classe pai
+* a subclasse
+* a classe filha
+* a classe irmã
+* o método de classe
+
+* o cabeçalho
+* os cabeçalhos
+* o cabeçalho de autorização
+* o cabeçalho `Authorization`
+* o cabeçalho encaminhado
+
+* o sistema de injeção de dependências
+* a dependência
+* o dependable
+* o dependant
+
+* limitado por I/O
+* limitado por CPU
+* concorrência
+* paralelismo
+* multiprocessamento
+
+* a env var
+* a variável de ambiente
+* o `PATH`
+* a variável `PATH`
+
+* a autenticação
+* o provedor de autenticação
+* a autorização
+* o formulário de autorização
+* o provedor de autorização
+* o usuário se autentica
+* o sistema autentica o usuário
+
+* a CLI
+* a interface de linha de comando
+
+* o servidor
+* o cliente
+
+* o provedor de nuvem
+* o serviço de nuvem
+
+* o desenvolvimento
+* as etapas de desenvolvimento
+
+* o dict
+* o dicionário
+* a enumeração
+* o enum
+* o membro do enum
+
+* o codificador
+* o decodificador
+* codificar
+* decodificar
+
+* a exceção
+* lançar
+
+* a expressão
+* a instrução
+
+* o frontend
+* o backend
+
+* a discussão do GitHub
+* a issue do GitHub
+
+* o desempenho
+* a otimização de desempenho
+
+* o tipo de retorno
+* o valor de retorno
+
+* a segurança
+* o esquema de segurança
+
+* a tarefa
+* a tarefa em segundo plano
+* a função da tarefa
+
+* o template
+* o mecanismo de template
+
+* a anotação de tipo
+* a anotação de tipo
+
+* o worker de servidor
+* o worker do Uvicorn
+* o Worker do Gunicorn
+* o processo worker
+* a classe de worker
+* a carga de trabalho
+
+* a implantação
+* implantar
+
+* o SDK
+* o kit de desenvolvimento de software
+
+* o `APIRouter`
+* o `requirements.txt`
+* o Bearer Token
+* a alteração com quebra de compatibilidade
+* o bug
+* o botão
+* o chamável
+* o código
+* o commit
+* o gerenciador de contexto
+* a corrotina
+* a sessão do banco de dados
+* o disco
+* o domínio
+* o mecanismo
+* o X falso
+* o método HTTP GET
+* o item
+* a biblioteca
+* o lifespan
+* o bloqueio
+* o middleware
+* a aplicação mobile
+* o módulo
+* a montagem
+* a rede
+* a origem
+* a sobrescrita
+* a carga útil
+* o processador
+* a propriedade
+* o proxy
+* o pull request
+* a consulta
+* a RAM
+* a máquina remota
+* o código de status
+* a string
+* a tag
+* o framework web
+* o curinga
+* retornar
+* validar
+
+////
+
+//// tab | Informações
+
+Esta é uma lista não completa e não normativa de termos (principalmente) técnicos vistos na documentação. Pode ser útil para o autor do prompt descobrir para quais termos o LLM precisa de uma ajudinha. Por exemplo, quando ele continua revertendo uma boa tradução para uma tradução subótima. Ou quando tem problemas para conjugar/declinar um termo no seu idioma.
+
+Veja, por exemplo, a seção `### List of English terms and their preferred German translations` em `docs/de/llm-prompt.md`.
+
+////
diff --git a/docs/pt/docs/about/index.md b/docs/pt/docs/about/index.md
index 1f42e88318..39e160741b 100644
--- a/docs/pt/docs/about/index.md
+++ b/docs/pt/docs/about/index.md
@@ -1,3 +1,3 @@
-# Sobre
+# Sobre { #about }
-Sobre o FastAPI, seus padrões, inspirações e muito mais. 🤓
+Sobre o FastAPI, seu design, inspiração e mais. 🤓
diff --git a/docs/pt/docs/advanced/additional-responses.md b/docs/pt/docs/advanced/additional-responses.md
index 1060d18afc..688bc16967 100644
--- a/docs/pt/docs/advanced/additional-responses.md
+++ b/docs/pt/docs/advanced/additional-responses.md
@@ -1,6 +1,6 @@
-# Retornos Adicionais no OpenAPI
+# Retornos Adicionais no OpenAPI { #additional-responses-in-openapi }
-/// warning | Aviso
+/// warning | Atenção
Este é um tema bem avançado.
@@ -14,7 +14,7 @@ Essas respostas adicionais serão incluídas no esquema do OpenAPI, e também ap
Porém para as respostas adicionais, você deve garantir que está retornando um `Response` como por exemplo o `JSONResponse` diretamente, junto com o código de status e o conteúdo.
-## Retorno Adicional com `model`
+## Retorno Adicional com `model` { #additional-response-with-model }
Você pode fornecer o parâmetro `responses` aos seus *decoradores de caminho*.
@@ -26,7 +26,7 @@ O **FastAPI** pegará este modelo, gerará o esquema JSON dele e incluirá no lo
Por exemplo, para declarar um outro retorno com o status code `404` e um modelo do Pydantic chamado `Message`, você pode escrever:
-{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *}
+{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *}
/// note | Nota
@@ -49,7 +49,7 @@ O local correto é:
///
-O retorno gerado no OpenAI para esta *operação de caminho* será:
+O retorno gerado no OpenAPI para esta *operação de rota* será:
```JSON hl_lines="3-12"
{
@@ -169,13 +169,13 @@ Os esquemas são referenciados em outro local dentro do esquema OpenAPI:
}
```
-## Media types adicionais para o retorno principal
+## Media types adicionais para o retorno principal { #additional-media-types-for-the-main-response }
Você pode utilizar o mesmo parâmetro `responses` para adicionar diferentes media types para o mesmo retorno principal.
-Por exemplo, você pode adicionar um media type adicional de `image/png`, declarando que a sua *operação de caminho* pode retornar um objeto JSON (com o media type `application/json`) ou uma imagem PNG:
+Por exemplo, você pode adicionar um media type adicional de `image/png`, declarando que a sua *operação de rota* pode retornar um objeto JSON (com o media type `application/json`) ou uma imagem PNG:
-{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *}
+{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
/// note | Nota
@@ -191,7 +191,7 @@ Porém se você especificou uma classe de retorno com o valor `None` como media
///
-## Combinando informações
+## Combinando informações { #combining-information }
Você também pode combinar informações de diferentes lugares, incluindo os parâmetros `response_model`, `status_code`, e `responses`.
@@ -203,15 +203,15 @@ Por exemplo, você pode declarar um retorno com o código de status `404` que ut
E um retorno com o código de status `200` que utiliza o seu `response_model`, porém inclui um `example` customizado:
-{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *}
+{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *}
Isso será combinado e incluído em seu OpenAPI, e disponibilizado na documentação da sua API:
-## Combinar retornos predefinidos e personalizados
+## Combinar retornos predefinidos e personalizados { #combine-predefined-responses-and-custom-ones }
-Você pode querer possuir alguns retornos predefinidos que são aplicados para diversas *operações de caminho*, porém você deseja combinar com retornos personalizados que são necessários para cada *operação de caminho*.
+Você pode querer possuir alguns retornos predefinidos que são aplicados para diversas *operações de rota*, porém você deseja combinar com retornos personalizados que são necessários para cada *operação de rota*.
Para estes casos, você pode utilizar a técnica do Python de "desempacotamento" de um `dict` utilizando `**dict_to_unpack`:
@@ -233,15 +233,15 @@ Aqui, o `new_dict` terá todos os pares de chave-valor do `old_dict` mais o novo
}
```
-Você pode utilizar essa técnica para reutilizar alguns retornos predefinidos nas suas *operações de caminho* e combiná-las com personalizações adicionais.
+Você pode utilizar essa técnica para reutilizar alguns retornos predefinidos nas suas *operações de rota* e combiná-las com personalizações adicionais.
Por exemplo:
-{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
+{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *}
-## Mais informações sobre retornos OpenAPI
+## Mais informações sobre retornos OpenAPI { #more-information-about-openapi-responses }
Para verificar exatamente o que você pode incluir nos retornos, você pode conferir estas seções na especificação do OpenAPI:
-* Objeto de Retorno OpenAPI, inclui o `Response Object`.
-* Objeto de Retorno OpenAPI, você pode incluir qualquer coisa dele diretamente em cada retorno dentro do seu parâmetro `responses`. Incluindo `description`, `headers`, `content` (dentro dele que você declara diferentes media types e esquemas JSON), e `links`.
+* Objeto de Retorno OpenAPI, inclui o `Response Object`.
+* Objeto de Retorno OpenAPI, você pode incluir qualquer coisa dele diretamente em cada retorno dentro do seu parâmetro `responses`. Incluindo `description`, `headers`, `content` (dentro dele que você declara diferentes media types e esquemas JSON), e `links`.
diff --git a/docs/pt/docs/advanced/additional-status-codes.md b/docs/pt/docs/advanced/additional-status-codes.md
index 06d6191519..fd90b1795d 100644
--- a/docs/pt/docs/advanced/additional-status-codes.md
+++ b/docs/pt/docs/advanced/additional-status-codes.md
@@ -1,22 +1,22 @@
-# Códigos de status adicionais
+# Códigos de status adicionais { #additional-status-codes }
-Por padrão, o **FastAPI** retornará as respostas utilizando o `JSONResponse`, adicionando o conteúdo do retorno da sua *operação de caminho* dentro do `JSONResponse`.
+Por padrão, o **FastAPI** retornará as respostas utilizando o `JSONResponse`, adicionando o conteúdo do retorno da sua *operação de rota* dentro do `JSONResponse`.
-Ele usará o código de status padrão ou o que você definir na sua *operação de caminho*.
+Ele usará o código de status padrão ou o que você definir na sua *operação de rota*.
-## Códigos de status adicionais
+## Códigos de status adicionais { #additional-status-codes_1 }
Caso você queira retornar códigos de status adicionais além do código principal, você pode fazer isso retornando um `Response` diretamente, como por exemplo um `JSONResponse`, e definir os códigos de status adicionais diretamente.
-Por exemplo, vamos dizer que você deseja ter uma *operação de caminho* que permita atualizar itens, e retornar um código de status HTTP 200 "OK" quando for bem sucedido.
+Por exemplo, vamos dizer que você deseja ter uma *operação de rota* que permita atualizar itens, e retornar um código de status HTTP 200 "OK" quando for bem sucedido.
-Mas você também deseja aceitar novos itens. E quando os itens não existiam, ele os cria, e retorna o código de status HTTP 201 "Created.
+Mas você também deseja aceitar novos itens. E quando os itens não existiam, ele os cria, e retorna o código de status HTTP 201 "Created".
Para conseguir isso, importe `JSONResponse` e retorne o seu conteúdo diretamente, definindo o `status_code` que você deseja:
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
-/// warning | Aviso
+/// warning | Atenção
Quando você retorna um `Response` diretamente, como no exemplo acima, ele será retornado diretamente.
@@ -26,7 +26,7 @@ Garanta que ele tenha toda informação que você deseja, e que os valores sejam
///
-/// note | Detalhes técnicos
+/// note | Detalhes Técnicos
Você também pode utilizar `from starlette.responses import JSONResponse`.
@@ -34,7 +34,7 @@ O **FastAPI** disponibiliza o `starlette.responses` como `fastapi.responses` ape
///
-## OpenAPI e documentação da API
+## OpenAPI e documentação da API { #openapi-and-api-docs }
Se você retorna códigos de status adicionais e retornos diretamente, eles não serão incluídos no esquema do OpenAPI (a documentação da API), porque o FastAPI não tem como saber de antemão o que será retornado.
diff --git a/docs/pt/docs/advanced/advanced-dependencies.md b/docs/pt/docs/advanced/advanced-dependencies.md
index f57abba618..1ad9ea617f 100644
--- a/docs/pt/docs/advanced/advanced-dependencies.md
+++ b/docs/pt/docs/advanced/advanced-dependencies.md
@@ -1,6 +1,6 @@
-# Dependências avançadas
+# Dependências avançadas { #advanced-dependencies }
-## Dependências parametrizadas
+## Dependências parametrizadas { #parameterized-dependencies }
Todas as dependências que vimos até agora são funções ou classes fixas.
@@ -10,7 +10,7 @@ Vamos imaginar que queremos ter uma dependência que verifica se o parâmetro de
Porém nós queremos poder parametrizar o conteúdo fixo.
-## Uma instância "chamável"
+## Uma instância "chamável" { #a-callable-instance }
Em Python existe uma maneira de fazer com que uma instância de uma classe seja um "chamável".
@@ -22,7 +22,7 @@ Para fazer isso, nós declaramos o método `__call__`:
Neste caso, o `__call__` é o que o **FastAPI** utilizará para verificar parâmetros adicionais e sub dependências, e isso é o que será chamado para passar o valor ao parâmetro na sua *função de operação de rota* posteriormente.
-## Parametrizar a instância
+## Parametrizar a instância { #parameterize-the-instance }
E agora, nós podemos utilizar o `__init__` para declarar os parâmetros da instância que podemos utilizar para "parametrizar" a dependência:
@@ -30,7 +30,7 @@ E agora, nós podemos utilizar o `__init__` para declarar os parâmetros da inst
Neste caso, o **FastAPI** nunca tocará ou se importará com o `__init__`, nós vamos utilizar diretamente em nosso código.
-## Crie uma instância
+## Crie uma instância { #create-an-instance }
Nós poderíamos criar uma instância desta classe com:
@@ -38,7 +38,7 @@ Nós poderíamos criar uma instância desta classe com:
E deste modo nós podemos "parametrizar" a nossa dependência, que agora possui `"bar"` dentro dele, como o atributo `checker.fixed_content`.
-## Utilize a instância como dependência
+## Utilize a instância como dependência { #use-the-instance-as-a-dependency }
Então, nós podemos utilizar este `checker` em um `Depends(checker)`, no lugar de `Depends(FixedContentQueryChecker)`, porque a dependência é a instância, `checker`, e não a própria classe.
@@ -63,3 +63,101 @@ Nos capítulos sobre segurança, existem funções utilitárias que são impleme
Se você entendeu tudo isso, você já sabe como essas funções utilitárias para segurança funcionam por debaixo dos panos.
///
+
+## Dependências com `yield`, `HTTPException`, `except` e Tarefas em Segundo Plano { #dependencies-with-yield-httpexception-except-and-background-tasks }
+
+/// warning | Atenção
+
+Muito provavelmente você não precisa desses detalhes técnicos.
+
+Esses detalhes são úteis principalmente se você tinha uma aplicação FastAPI anterior à versão 0.121.0 e está enfrentando problemas com dependências com `yield`.
+
+///
+
+Dependências com `yield` evoluíram ao longo do tempo para contemplar diferentes casos de uso e corrigir alguns problemas, aqui está um resumo do que mudou.
+
+### Dependências com `yield` e `scope` { #dependencies-with-yield-and-scope }
+
+Na versão 0.121.0, o FastAPI adicionou suporte a `Depends(scope="function")` para dependências com `yield`.
+
+Usando `Depends(scope="function")`, o código de saída após o `yield` é executado logo depois que a *função de operação de rota* termina, antes de a response ser enviada de volta ao cliente.
+
+E ao usar `Depends(scope="request")` (o padrão), o código de saída após o `yield` é executado depois que a response é enviada.
+
+Você pode ler mais na documentação em [Dependências com `yield` - Saída antecipada e `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope).
+
+### Dependências com `yield` e `StreamingResponse`, Detalhes Técnicos { #dependencies-with-yield-and-streamingresponse-technical-details }
+
+Antes do FastAPI 0.118.0, se você usasse uma dependência com `yield`, o código de saída (após o `yield`) rodaria depois que a *função de operação de rota* retornasse, mas logo antes de enviar a resposta.
+
+A intenção era evitar manter recursos por mais tempo que o necessário, esperando a resposta percorrer a rede.
+
+Essa mudança também significava que, se você retornasse um `StreamingResponse`, o código de saída da dependência com `yield` já teria sido executado.
+
+Por exemplo, se você tivesse uma sessão de banco de dados em uma dependência com `yield`, o `StreamingResponse` não conseguiria usar essa sessão enquanto transmite dados, porque a sessão já teria sido fechada no código de saída após o `yield`.
+
+Esse comportamento foi revertido na versão 0.118.0, para que o código de saída após o `yield` seja executado depois que a resposta for enviada.
+
+/// info | Informação
+
+Como você verá abaixo, isso é muito semelhante ao comportamento antes da versão 0.106.0, mas com várias melhorias e correções de bugs para casos extremos.
+
+///
+
+#### Casos de uso com código de saída antecipado { #use-cases-with-early-exit-code }
+
+Há alguns casos de uso, com condições específicas, que poderiam se beneficiar do comportamento antigo de executar o código de saída das dependências com `yield` antes de enviar a resposta.
+
+Por exemplo, imagine que você tem código que usa uma sessão de banco de dados em uma dependência com `yield` apenas para verificar um usuário, mas a sessão de banco de dados nunca é usada novamente na *função de operação de rota*, somente na dependência, e a resposta demora a ser enviada, como um `StreamingResponse` que envia dados lentamente, mas por algum motivo não usa o banco de dados.
+
+Nesse caso, a sessão de banco de dados seria mantida até que a resposta termine de ser enviada, mas se você não a usa, então não seria necessário mantê-la.
+
+Veja como poderia ser:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py *}
+
+O código de saída, o fechamento automático da `Session` em:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+...seria executado depois que a resposta terminar de enviar os dados lentos:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
+
+Mas como `generate_stream()` não usa a sessão do banco de dados, não é realmente necessário manter a sessão aberta enquanto envia a resposta.
+
+Se você tiver esse caso específico usando SQLModel (ou SQLAlchemy), você poderia fechar explicitamente a sessão depois que não precisar mais dela:
+
+{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *}
+
+Dessa forma a sessão liberaria a conexão com o banco de dados, para que outras requisições pudessem usá-la.
+
+Se você tiver um caso diferente que precise sair antecipadamente de uma dependência com `yield`, por favor crie uma Pergunta no GitHub Discussions com o seu caso específico e por que você se beneficiaria de ter o fechamento antecipado para dependências com `yield`.
+
+Se houver casos de uso convincentes para fechamento antecipado em dependências com `yield`, considerarei adicionar uma nova forma de optar por esse fechamento antecipado.
+
+### Dependências com `yield` e `except`, Detalhes Técnicos { #dependencies-with-yield-and-except-technical-details }
+
+Antes do FastAPI 0.110.0, se você usasse uma dependência com `yield`, e então capturasse uma exceção com `except` nessa dependência, e você não relançasse a exceção, a exceção seria automaticamente levantada/encaminhada para quaisquer tratadores de exceção ou para o tratador de erro interno do servidor.
+
+Isso foi alterado na versão 0.110.0 para corrigir consumo de memória não tratado decorrente de exceções encaminhadas sem um tratador (erros internos do servidor), e para torná-lo consistente com o comportamento do código Python regular.
+
+### Tarefas em Segundo Plano e Dependências com `yield`, Detalhes Técnicos { #background-tasks-and-dependencies-with-yield-technical-details }
+
+Antes do FastAPI 0.106.0, lançar exceções após o `yield` não era possível, o código de saída em dependências com `yield` era executado depois que a resposta era enviada, então [Tratadores de Exceções](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} já teriam sido executados.
+
+Isso foi projetado assim principalmente para permitir o uso dos mesmos objetos "yielded" por dependências dentro de tarefas em segundo plano, porque o código de saída seria executado depois que as tarefas em segundo plano fossem concluídas.
+
+Isso foi alterado no FastAPI 0.106.0 com a intenção de não manter recursos enquanto se espera a resposta percorrer a rede.
+
+/// tip | Dica
+
+Além disso, uma tarefa em segundo plano normalmente é um conjunto de lógica independente que deve ser tratado separadamente, com seus próprios recursos (por exemplo, sua própria conexão de banco de dados).
+
+Assim, desta forma você provavelmente terá um código mais limpo.
+
+///
+
+Se você costumava depender desse comportamento, agora você deve criar os recursos para tarefas em segundo plano dentro da própria tarefa em segundo plano, e usar internamente apenas dados que não dependam dos recursos de dependências com `yield`.
+
+Por exemplo, em vez de usar a mesma sessão de banco de dados, você criaria uma nova sessão de banco de dados dentro da tarefa em segundo plano, e obteria os objetos do banco de dados usando essa nova sessão. E então, em vez de passar o objeto do banco de dados como parâmetro para a função da tarefa em segundo plano, você passaria o ID desse objeto e então obteria o objeto novamente dentro da função da tarefa em segundo plano.
diff --git a/docs/pt/docs/advanced/async-tests.md b/docs/pt/docs/advanced/async-tests.md
index a2b79426c6..953ebedd9c 100644
--- a/docs/pt/docs/advanced/async-tests.md
+++ b/docs/pt/docs/advanced/async-tests.md
@@ -1,4 +1,4 @@
-# Testes Assíncronos
+# Testes Assíncronos { #async-tests }
Você já viu como testar as suas aplicações **FastAPI** utilizando o `TestClient` que é fornecido. Até agora, você viu apenas como escrever testes síncronos, sem utilizar funções `async`.
@@ -6,11 +6,11 @@ Ser capaz de utilizar funções assíncronas em seus testes pode ser útil, por
Vamos ver como nós podemos fazer isso funcionar.
-## pytest.mark.anyio
+## pytest.mark.anyio { #pytest-mark-anyio }
Se quisermos chamar funções assíncronas em nossos testes, as nossas funções de teste precisam ser assíncronas. O AnyIO oferece um plugin bem legal para isso, que nos permite especificar que algumas das nossas funções de teste precisam ser chamadas de forma assíncrona.
-## HTTPX
+## HTTPX { #httpx }
Mesmo que a sua aplicação **FastAPI** utilize funções normais com `def` no lugar de `async def`, ela ainda é uma aplicação `async` por baixo dos panos.
@@ -18,7 +18,7 @@ O `TestClient` faz algumas mágicas para invocar a aplicação FastAPI assíncro
O `TestClient` é baseado no HTTPX, e felizmente nós podemos utilizá-lo diretamente para testar a API.
-## Exemplo
+## Exemplo { #example }
Para um exemplos simples, vamos considerar uma estrutura de arquivos semelhante ao descrito em [Bigger Applications](../tutorial/bigger-applications.md){.internal-link target=_blank} e [Testing](../tutorial/testing.md){.internal-link target=_blank}:
@@ -32,13 +32,13 @@ Para um exemplos simples, vamos considerar uma estrutura de arquivos semelhante
O arquivo `main.py` teria:
-{* ../../docs_src/async_tests/main.py *}
+{* ../../docs_src/async_tests/app_a_py39/main.py *}
O arquivo `test_main.py` teria os testes para para o arquivo `main.py`, ele poderia ficar assim:
-{* ../../docs_src/async_tests/test_main.py *}
+{* ../../docs_src/async_tests/app_a_py39/test_main.py *}
-## Executá-lo
+## Executá-lo { #run-it }
Você pode executar os seus testes normalmente via:
@@ -52,11 +52,11 @@ $ pytest
-## Respostas disponíveis
+## Respostas disponíveis { #available-responses }
Aqui estão algumas dos tipos de resposta disponíveis.
@@ -121,7 +121,7 @@ O **FastAPI** provê a mesma `starlette.responses` como `fastapi.responses` apen
///
-### `Response`
+### `Response` { #response }
A classe principal de respostas, todas as outras respostas herdam dela.
@@ -136,25 +136,25 @@ Ela aceita os seguintes parâmetros:
O FastAPI (Starlette, na verdade) irá incluir o cabeçalho Content-Length automaticamente. Ele também irá incluir o cabeçalho Content-Type, baseado no `media_type` e acrescentando uma codificação para tipos textuais.
-{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
+{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
-### `HTMLResponse`
+### `HTMLResponse` { #htmlresponse }
Usa algum texto ou sequência de bytes e retorna uma resposta HTML. Como você leu acima.
-### `PlainTextResponse`
+### `PlainTextResponse` { #plaintextresponse }
Usa algum texto ou sequência de bytes para retornar uma resposta de texto não formatado.
-{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *}
-### `JSONResponse`
+### `JSONResponse` { #jsonresponse }
Pega alguns dados e retorna uma resposta com codificação `application/json`.
É a resposta padrão utilizada no **FastAPI**, como você leu acima.
-### `ORJSONResponse`
+### `ORJSONResponse` { #orjsonresponse }
Uma alternativa mais rápida de resposta JSON utilizando o `orjson`, como você leu acima.
@@ -164,7 +164,7 @@ Essa resposta requer a instalação do pacote `orjson`, com o comando `pip insta
///
-### `UJSONResponse`
+### `UJSONResponse` { #ujsonresponse }
Uma alternativa de resposta JSON utilizando a biblioteca `ujson`.
@@ -174,13 +174,13 @@ Essa resposta requer a instalação do pacote `ujson`, com o comando `pip instal
///
-/// warning | Aviso
+/// warning | Atenção
`ujson` é menos cauteloso que a implementação nativa do Python na forma que os casos especiais são tratados
///
-{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
+{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *}
/// tip | Dica
@@ -188,19 +188,19 @@ Essa resposta requer a instalação do pacote `ujson`, com o comando `pip instal
///
-### `RedirectResponse`
+### `RedirectResponse` { #redirectresponse }
Retorna um redirecionamento HTTP. Utiliza o código de status 307 (Redirecionamento Temporário) por padrão.
Você pode retornar uma `RedirectResponse` diretamente:
-{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
+{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *}
---
Ou você pode utilizá-la no parâmetro `response_class`:
-{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *}
Se você fizer isso, então você pode retornar a URL diretamente da sua *função de operação de rota*
@@ -210,28 +210,26 @@ Neste caso, o `status_code` utilizada será o padrão de `RedirectResponse`, que
Você também pode utilizar o parâmetro `status_code` combinado com o parâmetro `response_class`:
-{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *}
-### `StreamingResponse`
+### `StreamingResponse` { #streamingresponse }
-Recebe uma gerador assíncrono ou um gerador/iterador comum e retorna o corpo da requisição continuamente (stream).
+Recebe um gerador assíncrono ou um gerador/iterador comum e retorna o corpo da resposta de forma contínua (stream).
-{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
+{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *}
-#### Utilizando `StreamingResponse` com objetos semelhantes a arquivos
+#### Utilizando `StreamingResponse` com objetos semelhantes a arquivos { #using-streamingresponse-with-file-like-objects }
-Se você tiver um objeto semelhante a um arquivo (e.g. o objeto retornado por `open()`), você pode criar uma função geradora para iterar sobre esse objeto.
+Se você tiver um objeto semelhante a um arquivo (e.g. o objeto retornado por `open()`), você pode criar uma função geradora para iterar sobre esse objeto.
Dessa forma, você não precisa ler todo o arquivo na memória primeiro, e você pode passar essa função geradora para `StreamingResponse` e retorná-la.
Isso inclui muitas bibliotecas que interagem com armazenamento em nuvem, processamento de vídeos, entre outras.
-```{ .python .annotate hl_lines="2 10-12 14" }
-{!../../docs_src/custom_response/tutorial008.py!}
-```
+{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *}
1. Essa é a função geradora. É definida como "função geradora" porque contém declarações `yield` nela.
-2. Ao utilizar o bloco `with`, nós garantimos que o objeto semelhante a um arquivo é fechado após a função geradora ser finalizada. Isto é, após a resposta terminar de ser enivada.
+2. Ao utilizar o bloco `with`, nós garantimos que o objeto semelhante a um arquivo é fechado após a função geradora ser finalizada. Isto é, após a resposta terminar de ser enviada.
3. Essa declaração `yield from` informa a função para iterar sobre essa coisa nomeada de `file_like`. E então, para cada parte iterada, fornece essa parte como se viesse dessa função geradora (`iterfile`).
Então, é uma função geradora que transfere o trabalho de "geração" para alguma outra coisa interna.
@@ -244,10 +242,10 @@ Perceba que aqui estamos utilizando o `open()` da biblioteca padrão que não su
///
-### `FileResponse`
+### `FileResponse` { #fileresponse }
Envia um arquivo de forma assíncrona e contínua (stream).
-*
+
Recebe um conjunto de argumentos do construtor diferente dos outros tipos de resposta:
* `path` - O caminho do arquivo que será transmitido
@@ -257,15 +255,15 @@ Recebe um conjunto de argumentos do construtor diferente dos outros tipos de res
Respostas de Arquivos incluem o tamanho do arquivo, data da última modificação e ETags apropriados, nos cabeçalhos `Content-Length`, `Last-Modified` e `ETag`, respectivamente.
-{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
+{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *}
Você também pode usar o parâmetro `response_class`:
-{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *}
+{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *}
Nesse caso, você pode retornar o caminho do arquivo diretamente da sua *função de operação de rota*.
-## Classe de resposta personalizada
+## Classe de resposta personalizada { #custom-response-class }
Você pode criar sua própria classe de resposta, herdando de `Response` e usando essa nova classe.
@@ -273,9 +271,9 @@ Por exemplo, vamos supor que você queira utilizar o `dataclasses` da mesma forma:
-{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *}
+{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
Isso ainda é suportado graças ao **Pydantic**, pois ele tem suporte interno para `dataclasses`.
@@ -28,11 +28,11 @@ Mas se você tem um monte de dataclasses por aí, este é um truque legal para u
///
-## Dataclasses em `response_model`
+## Dataclasses em `response_model` { #dataclasses-in-response-model }
Você também pode usar `dataclasses` no parâmetro `response_model`:
-{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
+{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
A dataclass será automaticamente convertida para uma dataclass Pydantic.
@@ -40,7 +40,7 @@ Dessa forma, seu esquema aparecerá na interface de documentação da API:
-## Dataclasses em Estruturas de Dados Aninhadas
+## Dataclasses em Estruturas de Dados Aninhadas { #dataclasses-in-nested-data-structures }
Você também pode combinar `dataclasses` com outras anotações de tipo para criar estruturas de dados aninhadas.
@@ -48,9 +48,7 @@ Em alguns casos, você ainda pode ter que usar a versão do Pydantic das `datacl
Nesse caso, você pode simplesmente trocar as `dataclasses` padrão por `pydantic.dataclasses`, que é um substituto direto:
-```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" }
-{!../../docs_src/dataclasses/tutorial003.py!}
-```
+{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. Ainda importamos `field` das `dataclasses` padrão.
@@ -86,12 +84,12 @@ Você pode combinar `dataclasses` com outras anotações de tipo em muitas combi
Confira as dicas de anotação no código acima para ver mais detalhes específicos.
-## Saiba Mais
+## Saiba Mais { #learn-more }
Você também pode combinar `dataclasses` com outros modelos Pydantic, herdar deles, incluí-los em seus próprios modelos, etc.
Para saber mais, confira a documentação do Pydantic sobre dataclasses.
-## Versão
+## Versão { #version }
Isso está disponível desde a versão `0.67.0` do FastAPI. 🔖
diff --git a/docs/pt/docs/advanced/events.md b/docs/pt/docs/advanced/events.md
index 2d38e0899c..8cdc358289 100644
--- a/docs/pt/docs/advanced/events.md
+++ b/docs/pt/docs/advanced/events.md
@@ -1,67 +1,66 @@
-# Eventos de vida útil
+# Eventos de lifespan { #lifespan-events }
-Você pode definir a lógica (código) que poderia ser executada antes da aplicação **inicializar**. Isso significa que esse código será executado **uma vez**, **antes** da aplicação **começar a receber requisições**.
+Você pode definir a lógica (código) que deve ser executada antes da aplicação **inicializar**. Isso significa que esse código será executado **uma vez**, **antes** de a aplicação **começar a receber requisições**.
-Do mesmo modo, você pode definir a lógica (código) que será executada quando a aplicação estiver sendo **encerrada**. Nesse caso, este código será executado **uma vez**, **depois** de ter possivelmente tratado **várias requisições**.
+Da mesma forma, você pode definir a lógica (código) que deve ser executada quando a aplicação estiver **encerrando**. Nesse caso, esse código será executado **uma vez**, **depois** de possivelmente ter tratado **várias requisições**.
-Por conta desse código ser executado antes da aplicação **começar** a receber requisições, e logo após **terminar** de lidar com as requisições, ele cobre toda a **vida útil** (_lifespan_) da aplicação (o termo "vida útil" será importante em um segundo 😉).
+Como esse código é executado antes de a aplicação **começar** a receber requisições e logo depois que ela **termina** de lidar com as requisições, ele cobre todo o **lifespan** da aplicação (a palavra "lifespan" será importante em um segundo 😉).
-Pode ser muito útil para configurar **recursos** que você precisa usar por toda aplicação, e que são **compartilhados** entre as requisições, e/ou que você precisa **limpar** depois. Por exemplo, o pool de uma conexão com o banco de dados ou carregamento de um modelo compartilhado de aprendizado de máquina (_machine learning_).
+Isso pode ser muito útil para configurar **recursos** que você precisa usar por toda a aplicação, e que são **compartilhados** entre as requisições e/ou que você precisa **limpar** depois. Por exemplo, um pool de conexões com o banco de dados ou o carregamento de um modelo de machine learning compartilhado.
-## Caso de uso
+## Caso de uso { #use-case }
-Vamos iniciar com um exemplo de **caso de uso** e então ver como resolvê-lo com isso.
+Vamos começar com um exemplo de **caso de uso** e então ver como resolvê-lo com isso.
-Vamos imaginar que você tem alguns **modelos de _machine learning_** que deseja usar para lidar com as requisições. 🤖
+Vamos imaginar que você tem alguns **modelos de machine learning** que deseja usar para lidar com as requisições. 🤖
-Os mesmos modelos são compartilhados entre as requisições, então não é um modelo por requisição, ou um por usuário ou algo parecido.
+Os mesmos modelos são compartilhados entre as requisições, então não é um modelo por requisição, ou um por usuário, ou algo parecido.
-Vamos imaginar que o carregamento do modelo pode **demorar bastante tempo**, porque ele tem que ler muitos **dados do disco**. Então você não quer fazer isso a cada requisição.
+Vamos imaginar que o carregamento do modelo pode **demorar bastante tempo**, porque ele precisa ler muitos **dados do disco**. Então você não quer fazer isso a cada requisição.
-Você poderia carregá-lo no nível mais alto do módulo/arquivo, mas isso também poderia significaria **carregar o modelo** mesmo se você estiver executando um simples teste automatizado, então esse teste poderia ser **lento** porque teria que esperar o carregamento do modelo antes de ser capaz de executar uma parte independente do código.
+Você poderia carregá-lo no nível mais alto do módulo/arquivo, mas isso também significaria **carregar o modelo** mesmo se você estivesse executando um teste automatizado simples; então esse teste poderia ser **lento** porque teria que esperar o carregamento do modelo antes de conseguir executar uma parte independente do código.
+É isso que vamos resolver: vamos carregar o modelo antes de as requisições serem tratadas, mas apenas um pouco antes de a aplicação começar a receber requisições, não enquanto o código estiver sendo carregado.
-Isso é que nós iremos resolver, vamos carregar o modelo antes das requisições serem manuseadas, mas apenas um pouco antes da aplicação começar a receber requisições, não enquanto o código estiver sendo carregado.
+## Lifespan { #lifespan }
-## Vida útil (_Lifespan_)
+Você pode definir essa lógica de *inicialização* e *encerramento* usando o parâmetro `lifespan` da aplicação `FastAPI`, e um "gerenciador de contexto" (vou mostrar o que é isso em um segundo).
-Você pode definir essa lógica de *inicialização* e *encerramento* usando os parâmetros de `lifespan` da aplicação `FastAPI`, e um "gerenciador de contexto" (te mostrarei o que é isso a seguir).
+Vamos começar com um exemplo e depois ver em detalhes.
-Vamos iniciar com um exemplo e ver isso detalhadamente.
+Nós criamos uma função assíncrona `lifespan()` com `yield` assim:
-Nós criamos uma função assíncrona chamada `lifespan()` com `yield` como este:
+{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *}
-{* ../../docs_src/events/tutorial003.py hl[16,19] *}
+Aqui estamos simulando a operação de *inicialização* custosa de carregar o modelo, colocando a (falsa) função do modelo no dicionário com modelos de machine learning antes do `yield`. Esse código será executado **antes** de a aplicação **começar a receber requisições**, durante a *inicialização*.
-Aqui nós estamos simulando a *inicialização* custosa do carregamento do modelo colocando a (falsa) função de modelo no dicionário com modelos de _machine learning_ antes do `yield`. Este código será executado **antes** da aplicação **começar a receber requisições**, durante a *inicialização*.
-
-E então, logo após o `yield`, descarregaremos o modelo. Esse código será executado **após** a aplicação **terminar de lidar com as requisições**, pouco antes do *encerramento*. Isso poderia, por exemplo, liberar recursos como memória ou GPU.
+E então, logo após o `yield`, descarregamos o modelo. Esse código será executado **depois** de a aplicação **terminar de lidar com as requisições**, pouco antes do *encerramento*. Isso poderia, por exemplo, liberar recursos como memória ou uma GPU.
/// tip | Dica
O `shutdown` aconteceria quando você estivesse **encerrando** a aplicação.
-Talvez você precise inicializar uma nova versão, ou apenas cansou de executá-la. 🤷
+Talvez você precise iniciar uma nova versão, ou apenas cansou de executá-la. 🤷
///
-### Função _lifespan_
+### Função lifespan { #lifespan-function }
-A primeira coisa a notar, é que estamos definindo uma função assíncrona com `yield`. Isso é muito semelhante à Dependências com `yield`.
+A primeira coisa a notar é que estamos definindo uma função assíncrona com `yield`. Isso é muito semelhante a Dependências com `yield`.
-{* ../../docs_src/events/tutorial003.py hl[14:19] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *}
-A primeira parte da função, antes do `yield`, será executada **antes** da aplicação inicializar.
+A primeira parte da função, antes do `yield`, será executada **antes** de a aplicação iniciar.
-E a parte posterior do `yield` irá executar **após** a aplicação ser encerrada.
+E a parte posterior ao `yield` será executada **depois** de a aplicação ter terminado.
-### Gerenciador de Contexto Assíncrono
+### Gerenciador de contexto assíncrono { #async-context-manager }
Se você verificar, a função está decorada com um `@asynccontextmanager`.
-Que converte a função em algo chamado de "**Gerenciador de Contexto Assíncrono**".
+Isso converte a função em algo chamado "**gerenciador de contexto assíncrono**".
-{* ../../docs_src/events/tutorial003.py hl[1,13] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *}
Um **gerenciador de contexto** em Python é algo que você pode usar em uma declaração `with`, por exemplo, `open()` pode ser usado como um gerenciador de contexto:
@@ -70,97 +69,97 @@ with open("file.txt") as file:
file.read()
```
-Nas versões mais recentes de Python, há também um **gerenciador de contexto assíncrono**. Você o usaria com `async with`:
+Em versões mais recentes do Python, há também um **gerenciador de contexto assíncrono**. Você o usaria com `async with`:
```Python
async with lifespan(app):
await do_stuff()
```
-Quando você cria um gerenciador de contexto ou um gerenciador de contexto assíncrono como mencionado acima, o que ele faz é que, antes de entrar no bloco `with`, ele irá executar o código anterior ao `yield`, e depois de sair do bloco `with`, ele irá executar o código depois do `yield`.
+Quando você cria um gerenciador de contexto ou um gerenciador de contexto assíncrono como acima, o que ele faz é: antes de entrar no bloco `with`, ele executa o código antes do `yield`, e após sair do bloco `with`, ele executa o código depois do `yield`.
-No nosso exemplo de código acima, nós não usamos ele diretamente, mas nós passamos para o FastAPI para ele usá-lo.
+No nosso exemplo de código acima, não o usamos diretamente, mas passamos para o FastAPI para que ele o use.
-O parâmetro `lifespan` da aplicação `FastAPI` usa um **Gerenciador de Contexto Assíncrono**, então nós podemos passar nosso novo gerenciador de contexto assíncrono do `lifespan` para ele.
+O parâmetro `lifespan` da aplicação `FastAPI` aceita um **gerenciador de contexto assíncrono**, então podemos passar para ele nosso novo gerenciador de contexto assíncrono `lifespan`.
-{* ../../docs_src/events/tutorial003.py hl[22] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[22] *}
-## Eventos alternativos (deprecados)
+## Eventos alternativos (descontinuados) { #alternative-events-deprecated }
-/// warning | Aviso
+/// warning | Atenção
-A maneira recomendada para lidar com a *inicialização* e o *encerramento* é usando o parâmetro `lifespan` da aplicação `FastAPI` como descrito acima.
+A forma recomendada de lidar com a *inicialização* e o *encerramento* é usando o parâmetro `lifespan` da aplicação `FastAPI`, como descrito acima. Se você fornecer um parâmetro `lifespan`, os manipuladores de eventos `startup` e `shutdown` não serão mais chamados. É tudo `lifespan` ou tudo por eventos, não ambos.
-Você provavelmente pode pular essa parte.
+Você provavelmente pode pular esta parte.
///
-Existe uma forma alternativa para definir a execução dessa lógica durante *inicialização* e durante *encerramento*.
+Existe uma forma alternativa de definir essa lógica para ser executada durante a *inicialização* e durante o *encerramento*.
-Você pode definir manipuladores de eventos (funções) que precisam ser executadas antes da aplicação inicializar, ou quando a aplicação estiver encerrando.
+Você pode definir manipuladores de eventos (funções) que precisam ser executados antes de a aplicação iniciar ou quando a aplicação estiver encerrando.
Essas funções podem ser declaradas com `async def` ou `def` normal.
-### Evento `startup`
+### Evento `startup` { #startup-event }
-Para adicionar uma função que deve rodar antes da aplicação iniciar, declare-a com o evento `"startup"`:
+Para adicionar uma função que deve rodar antes de a aplicação iniciar, declare-a com o evento `"startup"`:
-{* ../../docs_src/events/tutorial001.py hl[8] *}
+{* ../../docs_src/events/tutorial001_py39.py hl[8] *}
-Nesse caso, a função de manipulação de evento `startup` irá inicializar os itens do "banco de dados" (só um `dict`) com alguns valores.
+Nesse caso, a função de manipulador do evento `startup` inicializará os itens do "banco de dados" (apenas um `dict`) com alguns valores.
-Você pode adicionar mais que uma função de manipulação de evento.
+Você pode adicionar mais de uma função de manipulador de eventos.
-E sua aplicação não irá começar a receber requisições até que todos os manipuladores de eventos de `startup` sejam concluídos.
+E sua aplicação não começará a receber requisições até que todos os manipuladores de eventos `startup` sejam concluídos.
-### Evento `shutdown`
+### Evento `shutdown` { #shutdown-event }
-Para adicionar uma função que deve ser executada quando a aplicação estiver encerrando, declare ela com o evento `"shutdown"`:
+Para adicionar uma função que deve ser executada quando a aplicação estiver encerrando, declare-a com o evento `"shutdown"`:
-{* ../../docs_src/events/tutorial002.py hl[6] *}
+{* ../../docs_src/events/tutorial002_py39.py hl[6] *}
-Aqui, a função de manipulação de evento `shutdown` irá escrever uma linha de texto `"Application shutdown"` no arquivo `log.txt`.
+Aqui, a função de manipulador do evento `shutdown` escreverá uma linha de texto `"Application shutdown"` no arquivo `log.txt`.
/// info | Informação
-Na função `open()`, o `mode="a"` significa "acrescentar", então, a linha irá ser adicionada depois de qualquer coisa que esteja naquele arquivo, sem sobrescrever o conteúdo anterior.
+Na função `open()`, o `mode="a"` significa "acrescentar", então a linha será adicionada depois do que já estiver naquele arquivo, sem sobrescrever o conteúdo anterior.
///
/// tip | Dica
-Perceba que nesse caso nós estamos usando a função padrão do Python `open()` que interage com um arquivo.
+Perceba que, nesse caso, estamos usando a função padrão do Python `open()` que interage com um arquivo.
-Então, isso envolve I/O (input/output), que exige "esperar" que coisas sejam escritas em disco.
+Então, isso envolve I/O (input/output), que requer "esperar" que as coisas sejam escritas em disco.
Mas `open()` não usa `async` e `await`.
-Então, nós declaramos uma função de manipulação de evento com o padrão `def` ao invés de `async def`.
+Assim, declaramos a função de manipulador de evento com `def` padrão em vez de `async def`.
///
-### `startup` e `shutdown` juntos
+### `startup` e `shutdown` juntos { #startup-and-shutdown-together }
-Há uma grande chance que a lógica para sua *inicialização* e *encerramento* esteja conectada, você pode querer iniciar alguma coisa e então finalizá-la, adquirir um recurso e então liberá-lo, etc.
+Há uma grande chance de que a lógica para sua *inicialização* e *encerramento* esteja conectada, você pode querer iniciar alguma coisa e então finalizá-la, adquirir um recurso e então liberá-lo, etc.
-Fazendo isso em funções separadas que não compartilham lógica ou variáveis entre elas é mais difícil já que você precisa armazenar os valores em variáveis globais ou truques parecidos.
+Fazer isso em funções separadas que não compartilham lógica ou variáveis entre si é mais difícil, pois você precisaria armazenar valores em variáveis globais ou truques semelhantes.
-Por causa disso, agora é recomendado em vez disso usar o `lifespan` como explicado acima.
+Por causa disso, agora é recomendado usar o `lifespan`, como explicado acima.
-## Detalhes técnicos
+## Detalhes técnicos { #technical-details }
-Só um detalhe técnico para nerds curiosos. 🤓
+Apenas um detalhe técnico para nerds curiosos. 🤓
-Por baixo, na especificação técnica ASGI, essa é a parte do Protocolo Lifespan, e define eventos chamados `startup` e `shutdown`.
+Por baixo, na especificação técnica do ASGI, isso é parte do Protocolo Lifespan, e define eventos chamados `startup` e `shutdown`.
/// info | Informação
-Você pode ler mais sobre o manipulador `lifespan` do Starlette na Documentação do Lifespan Starlette.
+Você pode ler mais sobre os manipuladores de `lifespan` do Starlette na Documentação do Lifespan do Starlette.
-Incluindo como manipular estado do lifespan que pode ser usado em outras áreas do seu código.
+Incluindo como lidar com estado do lifespan que pode ser usado em outras áreas do seu código.
///
-## Sub Aplicações
+## Sub Aplicações { #sub-applications }
-🚨 Tenha em mente que esses eventos de lifespan (de inicialização e desligamento) irão somente ser executados para a aplicação principal, não para [Sub Aplicações - Montagem](sub-applications.md){.internal-link target=_blank}.
+🚨 Tenha em mente que esses eventos de lifespan (inicialização e encerramento) serão executados apenas para a aplicação principal, não para [Sub Aplicações - Montagem](sub-applications.md){.internal-link target=_blank}.
diff --git a/docs/pt/docs/advanced/generate-clients.md b/docs/pt/docs/advanced/generate-clients.md
index dc6b29511a..5134bc7cb5 100644
--- a/docs/pt/docs/advanced/generate-clients.md
+++ b/docs/pt/docs/advanced/generate-clients.md
@@ -1,115 +1,76 @@
-# Generate Clients
+# Gerando SDKs { #generating-sdks }
-Como o **FastAPI** é baseado na especificação **OpenAPI**, você obtém compatibilidade automática com muitas ferramentas, incluindo a documentação automática da API (fornecida pelo Swagger UI).
+Como o **FastAPI** é baseado na especificação **OpenAPI**, suas APIs podem ser descritas em um formato padrão que muitas ferramentas entendem.
-Uma vantagem particular que nem sempre é óbvia é que você pode **gerar clientes** (às vezes chamados de **SDKs**) para a sua API, para muitas **linguagens de programação** diferentes.
+Isso facilita gerar **documentação** atualizada, bibliotecas clientes (**SDKs**) em várias linguagens e **testes** ou **fluxos de trabalho de automação** que permanecem em sincronia com o seu código.
-## Geradores de Clientes OpenAPI
+Neste guia, você aprenderá como gerar um **SDK em TypeScript** para o seu backend FastAPI.
-Existem muitas ferramentas para gerar clientes a partir do **OpenAPI**.
+## Geradores de SDK de código aberto { #open-source-sdk-generators }
-Uma ferramenta comum é o OpenAPI Generator.
+Uma opção versátil é o OpenAPI Generator, que suporta **muitas linguagens de programação** e pode gerar SDKs a partir da sua especificação OpenAPI.
-Se voce está construindo um **frontend**, uma alternativa muito interessante é o openapi-ts.
+Para **clientes TypeScript**, o Hey API é uma solução feita sob medida, oferecendo uma experiência otimizada para o ecossistema TypeScript.
-## Geradores de Clientes e SDKs - Patrocinadores
+Você pode descobrir mais geradores de SDK em OpenAPI.Tools.
-Existem também alguns geradores de clientes e SDKs baseados na OpenAPI (FastAPI) **patrocinados por empresas**, em alguns casos eles podem oferecer **recursos adicionais** além de SDKs/clientes gerados de alta qualidade.
+/// tip | Dica
-Alguns deles também ✨ [**patrocinam o FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, isso garante o **desenvolvimento** contínuo e saudável do FastAPI e seu **ecossistema**.
+O FastAPI gera automaticamente especificações **OpenAPI 3.1**, então qualquer ferramenta que você usar deve suportar essa versão.
-E isso mostra o verdadeiro compromisso deles com o FastAPI e sua **comunidade** (você), pois eles não apenas querem fornecer um **bom serviço**, mas também querem garantir que você tenha um **framework bom e saudável**, o FastAPI. 🙇
+///
+
+## Geradores de SDK dos patrocinadores do FastAPI { #sdk-generators-from-fastapi-sponsors }
+
+Esta seção destaca soluções **financiadas por investimento** e **com suporte de empresas** que patrocinam o FastAPI. Esses produtos fornecem **funcionalidades adicionais** e **integrações** além de SDKs gerados com alta qualidade.
+
+Ao ✨ [**patrocinar o FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, essas empresas ajudam a garantir que o framework e seu **ecossistema** continuem saudáveis e **sustentáveis**.
+
+O patrocínio também demonstra um forte compromisso com a **comunidade** FastAPI (você), mostrando que elas se importam não apenas em oferecer um **ótimo serviço**, mas também em apoiar um **framework robusto e próspero**, o FastAPI. 🙇
Por exemplo, você pode querer experimentar:
* Speakeasy
-* Stainless
-* liblab
+* Stainless
+* liblab
-Existem também várias outras empresas que oferecem serviços semelhantes que você pode pesquisar e encontrar online. 🤓
+Algumas dessas soluções também podem ser open source ou oferecer planos gratuitos, para que você possa testá-las sem compromisso financeiro. Outros geradores comerciais de SDK estão disponíveis e podem ser encontrados online. 🤓
-## Gerar um Cliente Frontend TypeScript
+## Crie um SDK em TypeScript { #create-a-typescript-sdk }
-Vamos começar com um aplicativo **FastAPI** simples:
+Vamos começar com uma aplicação FastAPI simples:
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
Note que as *operações de rota* definem os modelos que usam para o corpo da requisição e o corpo da resposta, usando os modelos `Item` e `ResponseMessage`.
-### Documentação da API
+### Documentação da API { #api-docs }
-Se você acessar a documentação da API, verá que ela tem os **schemas** para os dados a serem enviados nas requisições e recebidos nas respostas:
+Se você for para `/docs`, verá que ela tem os **schemas** para os dados a serem enviados nas requisições e recebidos nas respostas:
Você pode ver esses schemas porque eles foram declarados com os modelos no app.
-Essas informações estão disponíveis no **OpenAPI schema** do app e são mostradas na documentação da API (pelo Swagger UI).
+Essas informações estão disponíveis no **schema OpenAPI** do app e são mostradas na documentação da API.
E essas mesmas informações dos modelos que estão incluídas no OpenAPI são o que pode ser usado para **gerar o código do cliente**.
-### Gerar um Cliente TypeScript
+### Hey API { #hey-api }
-Agora que temos o app com os modelos, podemos gerar o código do cliente para o frontend.
+Depois que tivermos uma aplicação FastAPI com os modelos, podemos usar o Hey API para gerar um cliente TypeScript. A forma mais rápida é via npx.
-#### Instalar o `openapi-ts`
-
-Você pode instalar o `openapi-ts` no seu código frontend com:
-
-
@@ -119,7 +80,7 @@ Você também obterá preenchimento automático para o corpo a ser enviado:
/// tip | Dica
-Observe o preenchimento automático para `name` e `price`, que foi definido no aplicativo FastAPI, no modelo `Item`.
+Observe o preenchimento automático para `name` e `price`, que foi definido na aplicação FastAPI, no modelo `Item`.
///
@@ -131,17 +92,17 @@ O objeto de resposta também terá preenchimento automático:
-## App FastAPI com Tags
+## Aplicação FastAPI com Tags { #fastapi-app-with-tags }
-Em muitos casos seu app FastAPI será maior, e você provavelmente usará tags para separar diferentes grupos de *operações de rota*.
+Em muitos casos, sua aplicação FastAPI será maior, e você provavelmente usará tags para separar diferentes grupos de *operações de rota*.
Por exemplo, você poderia ter uma seção para **items** e outra seção para **users**, e elas poderiam ser separadas por tags:
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *}
-### Gerar um Cliente TypeScript com Tags
+### Gere um cliente TypeScript com Tags { #generate-a-typescript-client-with-tags }
-Se você gerar um cliente para um app FastAPI usando tags, normalmente também separará o código do cliente com base nas tags.
+Se você gerar um cliente para uma aplicação FastAPI usando tags, normalmente também separará o código do cliente com base nas tags.
Dessa forma, você poderá ter as coisas ordenadas e agrupadas corretamente para o código do cliente:
@@ -152,33 +113,33 @@ Nesse caso você tem:
* `ItemsService`
* `UsersService`
-### Nomes dos Métodos do Cliente
+### Nomes dos métodos do cliente { #client-method-names }
-Agora os nomes dos métodos gerados como `createItemItemsPost` não parecem muito "limpos":
+Agora os nomes dos métodos gerados como `createItemItemsPost` não parecem muito “limpos”:
```TypeScript
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
```
-...isto ocorre porque o gerador de clientes usa o **operation ID** interno do OpenAPI para cada *operação de rota*.
+...isso ocorre porque o gerador de clientes usa o **ID de operação** interno do OpenAPI para cada *operação de rota*.
-O OpenAPI exige que cada operation ID seja único em todas as *operações de rota*, então o FastAPI usa o **nome da função**, o **caminho** e o **método/operacao HTTP** para gerar esse operation ID, porque dessa forma ele pode garantir que os operation IDs sejam únicos.
+O OpenAPI exige que cada ID de operação seja único em todas as *operações de rota*, então o FastAPI usa o **nome da função**, o **path** e o **método/operação HTTP** para gerar esse ID de operação, porque dessa forma ele pode garantir que os IDs de operação sejam únicos.
Mas eu vou te mostrar como melhorar isso a seguir. 🤓
-### IDs de Operação Personalizados e Melhores Nomes de Método
+## IDs de operação personalizados e nomes de métodos melhores { #custom-operation-ids-and-better-method-names }
Você pode **modificar** a maneira como esses IDs de operação são **gerados** para torná-los mais simples e ter **nomes de método mais simples** nos clientes.
Neste caso, você terá que garantir que cada ID de operação seja **único** de alguma outra maneira.
-Por exemplo, você poderia garantir que cada *operação de rota* tenha uma tag, e então gerar o ID da operação com base na **tag** e no **nome** da *operação de rota* (o nome da função).
+Por exemplo, você poderia garantir que cada *operação de rota* tenha uma tag, e então gerar o ID de operação com base na **tag** e no **nome** da *operação de rota* (o nome da função).
-### Função Personalizada para Gerar IDs de Operação Únicos
+### Função personalizada para gerar IDs exclusivos { #custom-generate-unique-id-function }
-O FastAPI usa um **ID único** para cada *operação de rota*, ele é usado para o **ID da operação** e também para os nomes de quaisquer modelos personalizados necessários, para requisições ou respostas.
+O FastAPI usa um **ID exclusivo** para cada *operação de rota*, ele é usado para o **ID de operação** e também para os nomes de quaisquer modelos personalizados necessários, para requisições ou respostas.
-Você pode personalizar essa função. Ela recebe uma `APIRoute` e gera uma string.
+Você pode personalizar essa função. Ela recebe uma `APIRoute` e retorna uma string.
Por exemplo, aqui está usando a primeira tag (você provavelmente terá apenas uma tag) e o nome da *operação de rota* (o nome da função).
@@ -186,15 +147,15 @@ Você pode então passar essa função personalizada para o **FastAPI** como o p
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *}
-### Gerar um Cliente TypeScript com IDs de Operação Personalizados
+### Gere um cliente TypeScript com IDs de operação personalizados { #generate-a-typescript-client-with-custom-operation-ids }
Agora, se você gerar o cliente novamente, verá que ele tem os nomes dos métodos melhorados:
-Como você pode ver, os nomes dos métodos agora têm a tag e, em seguida, o nome da função. Agora eles não incluem informações do caminho da URL e da operação HTTP.
+Como você pode ver, os nomes dos métodos agora têm a tag e, em seguida, o nome da função. Agora eles não incluem informações do path da URL e da operação HTTP.
-### Pré-processar a Especificação OpenAPI para o Gerador de Clientes
+### Pré-processar a especificação OpenAPI para o gerador de clientes { #preprocess-the-openapi-specification-for-the-client-generator }
O código gerado ainda tem algumas **informações duplicadas**.
@@ -202,11 +163,11 @@ Nós já sabemos que esse método está relacionado aos **items** porque essa pa
Provavelmente ainda queremos mantê-lo para o OpenAPI em geral, pois isso garantirá que os IDs de operação sejam **únicos**.
-Mas para o cliente gerado, poderíamos **modificar** os IDs de operação do OpenAPI logo antes de gerar os clientes, apenas para tornar esses nomes de método mais **simples**.
+Mas para o cliente gerado, poderíamos **modificar** os IDs de operação do OpenAPI logo antes de gerar os clientes, apenas para tornar esses nomes de método mais agradáveis e **limpos**.
Poderíamos baixar o JSON do OpenAPI para um arquivo `openapi.json` e então poderíamos **remover essa tag prefixada** com um script como este:
-{* ../../docs_src/generate_clients/tutorial004.py *}
+{* ../../docs_src/generate_clients/tutorial004_py39.py *}
//// tab | Node.js
@@ -218,44 +179,30 @@ Poderíamos baixar o JSON do OpenAPI para um arquivo `openapi.json` e então pod
Com isso, os IDs de operação seriam renomeados de coisas como `items-get_items` para apenas `get_items`, dessa forma o gerador de clientes pode gerar nomes de métodos mais simples.
-### Gerar um Cliente TypeScript com o OpenAPI Pré-processado
+### Gere um cliente TypeScript com o OpenAPI pré-processado { #generate-a-typescript-client-with-the-preprocessed-openapi }
-Agora, como o resultado final está em um arquivo `openapi.json`, você modificaria o `package.json` para usar esse arquivo local, por exemplo:
+Como o resultado final está agora em um arquivo `openapi.json`, você precisa atualizar o local de entrada:
-```JSON hl_lines="7"
-{
- "name": "frontend-app",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios"
- },
- "author": "",
- "license": "",
- "devDependencies": {
- "@hey-api/openapi-ts": "^0.27.38",
- "typescript": "^4.6.2"
- }
-}
+```sh
+npx @hey-api/openapi-ts -i ./openapi.json -o src/client
```
-Depois de gerar o novo cliente, você teria agora **nomes de métodos "limpos"**, com todo o **preenchimento automático**, **erros em linha**, etc:
+Depois de gerar o novo cliente, você terá agora **nomes de métodos “limpos”**, com todo o **preenchimento automático**, **erros em linha**, etc:
-## Benefícios
+## Benefícios { #benefits }
-Ao usar os clientes gerados automaticamente, você teria **preenchimento automático** para:
+Ao usar os clientes gerados automaticamente, você terá **preenchimento automático** para:
* Métodos.
-* Corpo de requisições, parâmetros da query, etc.
-* Corpo de respostas.
+* Corpos de requisições, parâmetros de query, etc.
+* Corpos de respostas.
-Você também teria **erros em linha** para tudo.
+Você também terá **erros em linha** para tudo.
-E sempre que você atualizar o código do backend, e **regenerar** o frontend, ele teria quaisquer novas *operações de rota* disponíveis como métodos, as antigas removidas, e qualquer outra alteração seria refletida no código gerado. 🤓
+E sempre que você atualizar o código do backend e **regenerar** o frontend, ele terá quaisquer novas *operações de rota* disponíveis como métodos, as antigas removidas, e qualquer outra alteração será refletida no código gerado. 🤓
-Isso também significa que se algo mudar, será **refletido** no código do cliente automaticamente. E se você **construir** o cliente, ele dará erro se houver alguma **incompatibilidade** nos dados usados.
+Isso também significa que, se algo mudou, será **refletido** no código do cliente automaticamente. E se você **construir** o cliente, ele falhará caso haja qualquer **incompatibilidade** nos dados usados.
-Então, você **detectaria vários erros** muito cedo no ciclo de desenvolvimento, em vez de ter que esperar que os erros apareçam para seus usuários finais em produção e então tentar depurar onde está o problema. ✨
+Assim, você **detectará muitos erros** muito cedo no ciclo de desenvolvimento, em vez de ter que esperar que os erros apareçam para seus usuários finais em produção e então tentar depurar onde está o problema. ✨
diff --git a/docs/pt/docs/advanced/index.md b/docs/pt/docs/advanced/index.md
index 22ba2bf4a4..23e5510743 100644
--- a/docs/pt/docs/advanced/index.md
+++ b/docs/pt/docs/advanced/index.md
@@ -1,10 +1,10 @@
-# Guia de Usuário Avançado
+# Guia de Usuário Avançado { #advanced-user-guide }
-## Recursos Adicionais
+## Recursos Adicionais { #additional-features }
O [Tutorial - Guia de Usuário](../tutorial/index.md){.internal-link target=_blank} deve ser o suficiente para dar a você um tour por todos os principais recursos do **FastAPI**.
-Na próxima seção você verá outras opções, configurações, e recursos adicionais.
+Nas próximas seções você verá outras opções, configurações, e recursos adicionais.
/// tip | Dica
@@ -14,14 +14,8 @@ E é possível que para seu caso de uso, a solução esteja em uma delas.
///
-## Leia o Tutorial primeiro
+## Leia o Tutorial primeiro { #read-the-tutorial-first }
Você ainda pode usar a maior parte dos recursos no **FastAPI** com o conhecimento do [Tutorial - Guia de Usuário](../tutorial/index.md){.internal-link target=_blank}.
E as próximas seções assumem que você já leu ele, e que você conhece suas ideias principais.
-
-## Curso TestDriven.io
-
-Se você gostaria de fazer um curso avançado-iniciante para complementar essa seção da documentação, você pode querer conferir: Test-Driven Development com FastAPI e Docker por **TestDriven.io**.
-
-Eles estão atualmente doando 10% de todos os lucros para o desenvolvimento do **FastAPI**. 🎉 😄
diff --git a/docs/pt/docs/advanced/middleware.md b/docs/pt/docs/advanced/middleware.md
index 7700939f06..30c1834790 100644
--- a/docs/pt/docs/advanced/middleware.md
+++ b/docs/pt/docs/advanced/middleware.md
@@ -1,4 +1,4 @@
-# Middleware Avançado
+# Middleware Avançado { #advanced-middleware }
No tutorial principal você leu como adicionar [Middleware Personalizado](../tutorial/middleware.md){.internal-link target=_blank} à sua aplicação.
@@ -6,9 +6,9 @@ E então você também leu como lidar com [CORS com o `CORSMiddleware`](../tutor
Nesta seção, veremos como usar outros middlewares.
-## Adicionando middlewares ASGI
+## Adicionando middlewares ASGI { #adding-asgi-middlewares }
-Como o **FastAPI** é baseado no Starlette e implementa a especificação ASGI, você pode usar qualquer middleware ASGI.
+Como o **FastAPI** é baseado no Starlette e implementa a especificação ASGI, você pode usar qualquer middleware ASGI.
O middleware não precisa ser feito para o FastAPI ou Starlette para funcionar, desde que siga a especificação ASGI.
@@ -39,52 +39,53 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow")
`app.add_middleware()` recebe uma classe de middleware como o primeiro argumento e quaisquer argumentos adicionais a serem passados para o middleware.
-## Middlewares Integrados
+## Middlewares Integrados { #integrated-middlewares }
**FastAPI** inclui vários middlewares para casos de uso comuns, veremos a seguir como usá-los.
/// note | Detalhes Técnicos
-Para o próximo exemplo, você também poderia usar `from starlette.middleware.something import SomethingMiddleware`.
+Para os próximos exemplos, você também poderia usar `from starlette.middleware.something import SomethingMiddleware`.
**FastAPI** fornece vários middlewares em `fastapi.middleware` apenas como uma conveniência para você, o desenvolvedor. Mas a maioria dos middlewares disponíveis vem diretamente do Starlette.
///
-## `HTTPSRedirectMiddleware`
+## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware }
Garante que todas as requisições devem ser `https` ou `wss`.
Qualquer requisição para `http` ou `ws` será redirecionada para o esquema seguro.
-{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *}
+{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *}
-## `TrustedHostMiddleware`
+## `TrustedHostMiddleware` { #trustedhostmiddleware }
Garante que todas as requisições recebidas tenham um cabeçalho `Host` corretamente configurado, a fim de proteger contra ataques de cabeçalho de host HTTP.
-{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *}
+{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *}
Os seguintes argumentos são suportados:
* `allowed_hosts` - Uma lista de nomes de domínio que são permitidos como nomes de host. Domínios com coringa, como `*.example.com`, são suportados para corresponder a subdomínios. Para permitir qualquer nome de host, use `allowed_hosts=["*"]` ou omita o middleware.
+* `www_redirect` - Se definido como True, as requisições para versões sem www dos hosts permitidos serão redirecionadas para suas versões com www. O padrão é `True`.
Se uma requisição recebida não for validada corretamente, uma resposta `400` será enviada.
-## `GZipMiddleware`
+## `GZipMiddleware` { #gzipmiddleware }
Gerencia respostas GZip para qualquer requisição que inclua `"gzip"` no cabeçalho `Accept-Encoding`.
O middleware lidará com respostas padrão e de streaming.
-{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *}
+{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *}
Os seguintes argumentos são suportados:
* `minimum_size` - Não comprima respostas menores que este tamanho mínimo em bytes. O padrão é `500`.
* `compresslevel` - Usado durante a compressão GZip. É um inteiro variando de 1 a 9. O padrão é `9`. Um valor menor resulta em uma compressão mais rápida, mas em arquivos maiores, enquanto um valor maior resulta em uma compressão mais lenta, mas em arquivos menores.
-## Outros middlewares
+## Outros middlewares { #other-middlewares }
Há muitos outros middlewares ASGI.
diff --git a/docs/pt/docs/advanced/openapi-callbacks.md b/docs/pt/docs/advanced/openapi-callbacks.md
index b0659d3d6a..57c8c5e81d 100644
--- a/docs/pt/docs/advanced/openapi-callbacks.md
+++ b/docs/pt/docs/advanced/openapi-callbacks.md
@@ -1,29 +1,29 @@
-# Callbacks na OpenAPI
+# Callbacks na OpenAPI { #openapi-callbacks }
-Você poderia criar uma API com uma *operação de rota* que poderia acionar uma solicitação a uma *API externa* criada por outra pessoa (provavelmente o mesmo desenvolvedor que estaria *usando* sua API).
+Você poderia criar uma API com uma *operação de rota* que poderia acionar um request a uma *API externa* criada por outra pessoa (provavelmente o mesmo desenvolvedor que estaria *usando* sua API).
-O processo que acontece quando seu aplicativo de API chama a *API externa* é chamado de "callback". Porque o software que o desenvolvedor externo escreveu envia uma solicitação para sua API e então sua API *chama de volta*, enviando uma solicitação para uma *API externa* (que provavelmente foi criada pelo mesmo desenvolvedor).
+O processo que acontece quando sua aplicação de API chama a *API externa* é chamado de "callback". Porque o software que o desenvolvedor externo escreveu envia um request para sua API e então sua API *chama de volta*, enviando um request para uma *API externa* (que provavelmente foi criada pelo mesmo desenvolvedor).
Nesse caso, você poderia querer documentar como essa API externa *deveria* ser. Que *operação de rota* ela deveria ter, que corpo ela deveria esperar, que resposta ela deveria retornar, etc.
-## Um aplicativo com callbacks
+## Um aplicativo com callbacks { #an-app-with-callbacks }
Vamos ver tudo isso com um exemplo.
-Imagine que você tem um aplicativo que permite criar faturas.
+Imagine que você desenvolve um aplicativo que permite criar faturas.
Essas faturas terão um `id`, `title` (opcional), `customer` e `total`.
-O usuário da sua API (um desenvolvedor externo) criará uma fatura em sua API com uma solicitação POST.
+O usuário da sua API (um desenvolvedor externo) criará uma fatura na sua API com um request POST.
Então sua API irá (vamos imaginar):
-* Enviar uma solicitação de pagamento para o desenvolvedor externo.
+* Enviar a fatura para algum cliente do desenvolvedor externo.
* Coletar o dinheiro.
* Enviar a notificação de volta para o usuário da API (o desenvolvedor externo).
-* Isso será feito enviando uma solicitação POST (de *sua API*) para alguma *API externa* fornecida por esse desenvolvedor externo (este é o "callback").
+ * Isso será feito enviando um request POST (de *sua API*) para alguma *API externa* fornecida por esse desenvolvedor externo (este é o "callback").
-## O aplicativo **FastAPI** normal
+## O aplicativo **FastAPI** normal { #the-normal-fastapi-app }
Vamos primeiro ver como o aplicativo da API normal se pareceria antes de adicionar o callback.
@@ -31,7 +31,7 @@ Ele terá uma *operação de rota* que receberá um corpo `Invoice`, e um parâm
Essa parte é bastante normal, a maior parte do código provavelmente já é familiar para você:
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *}
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
/// tip | Dica
@@ -39,11 +39,11 @@ O parâmetro de consulta `callback_url` usa um tipo Pydantic HTTPX ou Requisições.
+Ao implementar o callback por conta própria, você pode usar algo como HTTPX ou Requests.
///
-## Escrevendo o código de documentação do callback
+## Escreva o código de documentação do callback { #write-the-callback-documentation-code }
Esse código não será executado em seu aplicativo, nós só precisamos dele para *documentar* como essa *API externa* deveria ser.
@@ -80,37 +80,37 @@ Então vamos usar esse mesmo conhecimento para documentar como a *API externa* d
/// tip | Dica
-Quando escrever o código para documentar um callback, pode ser útil imaginar que você é aquele *desenvolvedor externo*. E que você está atualmente implementando a *API externa*, não *sua API*.
+Ao escrever o código para documentar um callback, pode ser útil imaginar que você é aquele *desenvolvedor externo*. E que você está atualmente implementando a *API externa*, não *sua API*.
-Adotar temporariamente esse ponto de vista (do *desenvolvedor externo*) pode ajudar a sentir que é mais óbvio onde colocar os parâmetros, o modelo Pydantic para o corpo, para a resposta, etc. para essa *API externa*.
+Adotar temporariamente esse ponto de vista (do *desenvolvedor externo*) pode ajudar a perceber mais facilmente onde colocar os parâmetros, o modelo Pydantic para o corpo, para a resposta, etc. para essa *API externa*.
///
-### Criar um `APIRouter` para o callback
+### Crie um `APIRouter` de callback { #create-a-callback-apirouter }
-Primeiramente crie um novo `APIRouter` que conterá um ou mais callbacks.
+Primeiro crie um novo `APIRouter` que conterá um ou mais callbacks.
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *}
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
-### Crie a *operação de rota* do callback
+### Crie a *operação de rota* do callback { #create-the-callback-path-operation }
Para criar a *operação de rota* do callback, use o mesmo `APIRouter` que você criou acima.
-Ele deve parecer exatamente como uma *operação de rota* normal do FastAPI:
+Ela deve parecer exatamente como uma *operação de rota* normal do FastAPI:
-* Ele provavelmente deveria ter uma declaração do corpo que deveria receber, por exemplo. `body: InvoiceEvent`.
-* E também deveria ter uma declaração de um código de status de resposta, por exemplo. `response_model=InvoiceEventReceived`.
+* Ela provavelmente deveria ter uma declaração do corpo que deveria receber, por exemplo, `body: InvoiceEvent`.
+* E também poderia ter uma declaração da resposta que deveria retornar, por exemplo, `response_model=InvoiceEventReceived`.
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *}
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *}
Há 2 diferenças principais de uma *operação de rota* normal:
* Ela não necessita ter nenhum código real, porque seu aplicativo nunca chamará esse código. Ele é usado apenas para documentar a *API externa*. Então, a função poderia ter apenas `pass`.
-* A *rota* pode conter uma expressão OpenAPI 3 (veja mais abaixo) onde pode usar variáveis com parâmetros e partes da solicitação original enviada para *sua API*.
+* O *path* pode conter uma expressão OpenAPI 3 (veja mais abaixo) em que pode usar variáveis com parâmetros e partes da solicitação original enviada para *sua API*.
-### A expressão do caminho do callback
+### A expressão do path do callback { #the-callback-path-expression }
-A *rota* do callback pode ter uma expressão OpenAPI 3 que pode conter partes da solicitação original enviada para *sua API*.
+O *path* do callback pode ter uma expressão OpenAPI 3 que pode conter partes da solicitação original enviada para *sua API*.
Nesse caso, é a `str`:
@@ -118,7 +118,7 @@ Nesse caso, é a `str`:
"{$callback_url}/invoices/{$request.body.id}"
```
-Então, se o usuário da sua API (o desenvolvedor externo) enviar uma solicitação para *sua API* para:
+Então, se o usuário da sua API (o desenvolvedor externo) enviar um request para *sua API* para:
```
https://yourapi.com/invoices/?callback_url=https://www.external.org/events
@@ -134,7 +134,7 @@ com um corpo JSON de:
}
```
-então *sua API* processará a fatura e, em algum momento posterior, enviará uma solicitação de callback para o `callback_url` (a *API externa*):
+então *sua API* processará a fatura e, em algum momento posterior, enviará um request de callback para o `callback_url` (a *API externa*):
```
https://www.external.org/events/invoices/2expen51ve
@@ -163,13 +163,13 @@ Perceba como a URL de callback usada contém a URL recebida como um parâmetro d
///
-### Adicionar o roteador de callback
+### Adicione o roteador de callback { #add-the-callback-router }
-Nesse ponto você tem a(s) *operação de rota de callback* necessária(s) (a(s) que o *desenvolvedor externo* deveria implementar na *API externa*) no roteador de callback que você criou acima.
+Nesse ponto você tem a(s) *operação(ões) de rota de callback* necessária(s) (a(s) que o *desenvolvedor externo* deveria implementar na *API externa*) no roteador de callback que você criou acima.
-Agora use o parâmetro `callbacks` no decorador da *operação de rota de sua API* para passar o atributo `.routes` (que é na verdade apenas uma `list` de rotas/*operações de rota*) do roteador de callback que você criou acima:
+Agora use o parâmetro `callbacks` no decorador da *operação de rota da sua API* para passar o atributo `.routes` (que é na verdade apenas uma `list` de rotas/*operações de path*) do roteador de callback:
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *}
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
/// tip | Dica
@@ -177,7 +177,7 @@ Perceba que você não está passando o roteador em si (`invoices_callback_route
///
-### Verifique a documentação
+### Verifique a documentação { #check-the-docs }
Agora você pode iniciar seu aplicativo e ir para http://127.0.0.1:8000/docs.
diff --git a/docs/pt/docs/advanced/openapi-webhooks.md b/docs/pt/docs/advanced/openapi-webhooks.md
index f35922234f..011898e8cd 100644
--- a/docs/pt/docs/advanced/openapi-webhooks.md
+++ b/docs/pt/docs/advanced/openapi-webhooks.md
@@ -1,4 +1,4 @@
-# Webhooks OpenAPI
+# Webhooks OpenAPI { #openapi-webhooks }
Existem situações onde você deseja informar os **usuários** da sua API que a sua aplicação pode chamar a aplicação *deles* (enviando uma requisição) com alguns dados, normalmente para **notificar** algum tipo de **evento**.
@@ -6,7 +6,7 @@ Isso significa que no lugar do processo normal de seus usuários enviarem requis
Isso normalmente é chamado de **webhook**.
-## Etapas dos Webhooks
+## Etapas dos webhooks { #webhooks-steps }
Normalmente, o processo é que **você define** em seu código qual é a mensagem que você irá mandar, o **corpo da sua requisição**.
@@ -16,7 +16,7 @@ E os **seus usuários** definem de alguma forma (em algum painel por exemplo) a
Toda a **lógica** sobre como cadastrar as URLs para os webhooks e o código para enviar de fato as requisições cabe a você definir. Você escreve da maneira que você desejar no **seu próprio código**.
-## Documentando webhooks com o FastAPI e OpenAPI
+## Documentando webhooks com o FastAPI e OpenAPI { #documenting-webhooks-with-fastapi-and-openapi }
Com o **FastAPI**, utilizando o OpenAPI, você pode definir os nomes destes webhooks, os tipos das operações HTTP que a sua aplicação pode enviar (e.g. `POST`, `PUT`, etc.) e os **corpos** da requisição que a sua aplicação enviaria.
@@ -28,11 +28,11 @@ Webhooks estão disponíveis a partir do OpenAPI 3.1.0, e possui suporte do Fast
///
-## Uma aplicação com webhooks
+## Uma aplicação com webhooks { #an-app-with-webhooks }
Quando você cria uma aplicação com o **FastAPI**, existe um atributo chamado `webhooks`, que você utilizar para defini-los da mesma maneira que você definiria as suas **operações de rotas**, utilizando por exemplo `@app.webhooks.post()`.
-{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *}
+{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *}
Os webhooks que você define aparecerão no esquema do **OpenAPI** e na **página de documentação** gerada automaticamente.
@@ -42,11 +42,11 @@ O objeto `app.webhooks` é na verdade apenas um `APIRouter`, o mesmo tipo que vo
///
-Note que utilizando webhooks você não está de fato declarando uma **rota** (como `/items/`), o texto que informa é apenas um **identificador** do webhook (o nome do evento), por exemplo em `@app.webhooks.post("new-subscription")`, o nome do webhook é `new-subscription`.
+Note que utilizando webhooks você não está de fato declarando um *path* (como `/items/`), o texto que informa é apenas um **identificador** do webhook (o nome do evento), por exemplo em `@app.webhooks.post("new-subscription")`, o nome do webhook é `new-subscription`.
-Isto porque espera-se que os **seus usuários** definam o verdadeiro **caminho da URL** onde eles desejam receber a requisição do webhook de algum outra maneira. (e.g. um painel).
+Isto porque espera-se que os **seus usuários** definam o verdadeiro **URL path** onde eles desejam receber a requisição do webhook de algum outra maneira. (e.g. um painel).
-### Confira a documentação
+### Confira a documentação { #check-the-docs }
Agora você pode iniciar a sua aplicação e ir até http://127.0.0.1:8000/docs.
diff --git a/docs/pt/docs/advanced/path-operation-advanced-configuration.md b/docs/pt/docs/advanced/path-operation-advanced-configuration.md
index 411d0f9a7a..e1c3e5ab89 100644
--- a/docs/pt/docs/advanced/path-operation-advanced-configuration.md
+++ b/docs/pt/docs/advanced/path-operation-advanced-configuration.md
@@ -1,8 +1,8 @@
-# Configuração Avançada da Operação de Rota
+# Configuração Avançada da Operação de Rota { #path-operation-advanced-configuration }
-## operationId do OpenAPI
+## operationId do OpenAPI { #openapi-operationid }
-/// warning | Aviso
+/// warning | Atenção
Se você não é um "especialista" no OpenAPI, você provavelmente não precisa disso.
@@ -12,15 +12,15 @@ Você pode definir o `operationId` do OpenAPI que será utilizado na sua *opera
Você precisa ter certeza que ele é único para cada operação.
-{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
-### Utilizando o nome da *função de operação de rota* como o operationId
+### Utilizando o nome da *função de operação de rota* como o operationId { #using-the-path-operation-function-name-as-the-operationid }
-Se você quiser utilizar o nome das funções da sua API como `operationId`s, você pode iterar sobre todos esses nomes e sobrescrever o `operationId` em cada *operação de rota* utilizando o `APIRoute.name` dela.
+Se você quiser utilizar o nome das funções da sua API como `operationId`s, você pode iterar sobre todos esses nomes e sobrescrever o `operation_id` em cada *operação de rota* utilizando o `APIRoute.name` dela.
Você deve fazer isso depois de adicionar todas as suas *operações de rota*.
-{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12:21,24] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *}
/// tip | Dica
@@ -28,7 +28,7 @@ Se você chamar `app.openapi()` manualmente, os `operationId`s devem ser atualiz
///
-/// warning | Aviso
+/// warning | Atenção
Se você fizer isso, você tem que ter certeza de que cada uma das suas *funções de operação de rota* tem um nome único.
@@ -36,13 +36,13 @@ Mesmo que elas estejam em módulos (arquivos Python) diferentes.
///
-## Excluir do OpenAPI
+## Excluir do OpenAPI { #exclude-from-openapi }
Para excluir uma *operação de rota* do esquema OpenAPI gerado (e por consequência, dos sistemas de documentação automáticos), utilize o parâmetro `include_in_schema` e defina ele como `False`:
-{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *}
-## Descrição avançada a partir de docstring
+## Descrição avançada a partir de docstring { #advanced-description-from-docstring }
Você pode limitar as linhas utilizadas a partir de uma docstring de uma *função de operação de rota* para o OpenAPI.
@@ -50,9 +50,9 @@ Adicionar um `\f` (um caractere de escape para alimentação de formulário) faz
Ele não será mostrado na documentação, mas outras ferramentas (como o Sphinx) serão capazes de utilizar o resto do texto.
-{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
-## Respostas Adicionais
+## Respostas Adicionais { #additional-responses }
Você provavelmente já viu como declarar o `response_model` e `status_code` para uma *operação de rota*.
@@ -62,11 +62,11 @@ Você também pode declarar respostas adicionais, com seus modelos, códigos de
Existe um capítulo inteiro da nossa documentação sobre isso, você pode ler em [Retornos Adicionais no OpenAPI](additional-responses.md){.internal-link target=_blank}.
-## Extras do OpenAPI
+## Extras do OpenAPI { #openapi-extra }
Quando você declara uma *operação de rota* na sua aplicação, o **FastAPI** irá gerar os metadados relevantes da *operação de rota* automaticamente para serem incluídos no esquema do OpenAPI.
-/// note | Nota
+/// note | Detalhes Técnicos
Na especificação do OpenAPI, isso é chamado de um Objeto de Operação.
@@ -88,11 +88,11 @@ Caso você só precise declarar respostas adicionais, uma forma conveniente de f
Você pode estender o esquema do OpenAPI para uma *operação de rota* utilizando o parâmetro `openapi_extra`.
-### Extensões do OpenAPI
+### Extensões do OpenAPI { #openapi-extensions }
Esse parâmetro `openapi_extra` pode ser útil, por exemplo, para declarar [Extensões do OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
-{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *}
Se você abrir os documentos criados automaticamente para a API, sua extensão aparecerá no final da *operação de rota* específica.
@@ -129,7 +129,7 @@ E se você olhar o esquema OpenAPI resultante (na rota `/openapi.json` da sua AP
}
```
-### Esquema de *operação de rota* do OpenAPI personalizado
+### Esquema de *operação de rota* do OpenAPI personalizado { #custom-openapi-path-operation-schema }
O dicionário em `openapi_extra` vai ter todos os seus níveis mesclados dentro do esquema OpenAPI gerado automaticamente para a *operação de rota*.
@@ -139,39 +139,39 @@ Por exemplo, você poderia optar por ler e validar a requisição com seu própr
Você pode fazer isso com `openapi_extra`:
-{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36,39:40] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *}
Nesse exemplo, nós não declaramos nenhum modelo do Pydantic. Na verdade, o corpo da requisição não está nem mesmo analisado como JSON, ele é lido diretamente como `bytes` e a função `magic_data_reader()` seria a responsável por analisar ele de alguma forma.
De toda forma, nós podemos declarar o esquema esperado para o corpo da requisição.
-### Tipo de conteúdo do OpenAPI personalizado
+### Tipo de conteúdo do OpenAPI personalizado { #custom-openapi-content-type }
-Utilizando esse mesmo truque, você pode utilizar um modelo Pydantic para definir o esquema JSON que é então incluído na seção do esquema personalizado do OpenAPI na *operação de rota*.
+Utilizando esse mesmo truque, você pode utilizar um modelo Pydantic para definir o JSON Schema que é então incluído na seção do esquema personalizado do OpenAPI na *operação de rota*.
E você pode fazer isso até mesmo quando os dados da requisição não seguem o formato JSON.
-Por exemplo, nesta aplicação nós não usamos a funcionalidade integrada ao FastAPI de extrair o esquema JSON dos modelos Pydantic nem a validação automática do JSON. Na verdade, estamos declarando o tipo do conteúdo da requisição como YAML, em vez de JSON:
+Por exemplo, nesta aplicação nós não usamos a funcionalidade integrada ao FastAPI de extrair o JSON Schema dos modelos Pydantic nem a validação automática do JSON. Na verdade, estamos declarando o tipo do conteúdo da requisição como YAML, em vez de JSON:
//// tab | Pydantic v2
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22,24] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
////
//// tab | Pydantic v1
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22,24] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *}
////
/// info | Informação
-Na versão 1 do Pydantic, o método para obter o esquema JSON de um modelo é `Item.schema()`, na versão 2 do Pydantic, o método é `Item.model_json_schema()`
+Na versão 1 do Pydantic, o método para obter o JSON Schema de um modelo é `Item.schema()`, na versão 2 do Pydantic, o método é `Item.model_json_schema()`.
///
-Entretanto, mesmo que não utilizemos a funcionalidade integrada por padrão, ainda estamos usando um modelo Pydantic para gerar um esquema JSON manualmente para os dados que queremos receber no formato YAML.
+Entretanto, mesmo que não utilizemos a funcionalidade integrada por padrão, ainda estamos usando um modelo Pydantic para gerar um JSON Schema manualmente para os dados que queremos receber no formato YAML.
Então utilizamos a requisição diretamente, e extraímos o corpo como `bytes`. Isso significa que o FastAPI não vai sequer tentar analisar o corpo da requisição como JSON.
@@ -179,13 +179,13 @@ E então no nosso código, nós analisamos o conteúdo YAML diretamente, e estam
//// tab | Pydantic v2
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
////
//// tab | Pydantic v1
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *}
////
@@ -195,7 +195,7 @@ Na versão 1 do Pydantic, o método para analisar e validar um objeto era `Item.
///
-///tip | Dica
+/// tip | Dica
Aqui reutilizamos o mesmo modelo do Pydantic.
diff --git a/docs/pt/docs/advanced/response-change-status-code.md b/docs/pt/docs/advanced/response-change-status-code.md
index 358c12d549..ee81f0bfc8 100644
--- a/docs/pt/docs/advanced/response-change-status-code.md
+++ b/docs/pt/docs/advanced/response-change-status-code.md
@@ -1,10 +1,10 @@
-# Retorno - Altere o Código de Status
+# Retorno - Altere o Código de Status { #response-change-status-code }
Você provavelmente leu anteriormente que você pode definir um [Código de Status do Retorno](../tutorial/response-status-code.md){.internal-link target=_blank} padrão.
Porém em alguns casos você precisa retornar um código de status diferente do padrão.
-## Caso de uso
+## Caso de uso { #use-case }
Por exemplo, imagine que você deseja retornar um código de status HTTP de "OK" `200` por padrão.
@@ -14,13 +14,13 @@ Mas você ainda quer ser capaz de filtrar e converter o dado que você retornar
Para estes casos, você pode utilizar um parâmetro `Response`.
-## Use um parâmetro `Response`
+## Use um parâmetro `Response` { #use-a-response-parameter }
Você pode declarar um parâmetro do tipo `Response` em sua *função de operação de rota* (assim como você pode fazer para cookies e headers).
E então você pode definir o `status_code` neste objeto de retorno temporal.
-{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *}
+{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *}
E então você pode retornar qualquer objeto que você precise, como você faria normalmente (um `dict`, um modelo de banco de dados, etc.).
diff --git a/docs/pt/docs/advanced/response-cookies.md b/docs/pt/docs/advanced/response-cookies.md
index f005f0b9b9..67820b433b 100644
--- a/docs/pt/docs/advanced/response-cookies.md
+++ b/docs/pt/docs/advanced/response-cookies.md
@@ -1,12 +1,12 @@
-# Cookies de Resposta
+# Cookies de Resposta { #response-cookies }
-## Usando um parâmetro `Response`
+## Use um parâmetro `Response` { #use-a-response-parameter }
Você pode declarar um parâmetro do tipo `Response` na sua *função de operação de rota*.
E então você pode definir cookies nesse objeto de resposta *temporário*.
-{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *}
+{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *}
Em seguida, você pode retornar qualquer objeto que precise, como normalmente faria (um `dict`, um modelo de banco de dados, etc).
@@ -16,15 +16,15 @@ E se você declarou um `response_model`, ele ainda será usado para filtrar e co
Você também pode declarar o parâmetro `Response` em dependências e definir cookies (e cabeçalhos) nelas.
-## Retornando uma `Response` diretamente
+## Retorne uma `Response` diretamente { #return-a-response-directly }
Você também pode criar cookies ao retornar uma `Response` diretamente no seu código.
-Para fazer isso, você pode criar uma resposta como descrito em [Retornando uma Resposta Diretamente](response-directly.md){.internal-link target=_blank}.
+Para fazer isso, você pode criar uma resposta como descrito em [Retorne uma Resposta Diretamente](response-directly.md){.internal-link target=_blank}.
Então, defina os cookies nela e a retorne:
-{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
+{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *}
/// tip | Dica
@@ -36,7 +36,7 @@ E também que você não esteja enviando nenhum dado que deveria ter sido filtra
///
-### Mais informações
+### Mais informações { #more-info }
/// note | Detalhes Técnicos
diff --git a/docs/pt/docs/advanced/response-directly.md b/docs/pt/docs/advanced/response-directly.md
index ea717be008..bbbef2f913 100644
--- a/docs/pt/docs/advanced/response-directly.md
+++ b/docs/pt/docs/advanced/response-directly.md
@@ -1,4 +1,4 @@
-# Retornando uma Resposta Diretamente
+# Retornando uma Resposta Diretamente { #return-a-response-directly }
Quando você cria uma *operação de rota* no **FastAPI** você pode retornar qualquer dado nela: um dicionário (`dict`), uma lista (`list`), um modelo do Pydantic ou do seu banco de dados, etc.
@@ -10,7 +10,7 @@ Mas você pode retornar a `JSONResponse` diretamente nas suas *operações de ro
Pode ser útil para retornar cabeçalhos e cookies personalizados, por exemplo.
-## Retornando uma `Response`
+## Retornando uma `Response` { #return-a-response }
Na verdade, você pode retornar qualquer `Response` ou subclasse dela.
@@ -26,7 +26,7 @@ Ele não vai fazer conversões de dados com modelos do Pydantic, não irá conve
Isso te dá bastante flexibilidade. Você pode retornar qualquer tipo de dado, sobrescrever qualquer declaração e validação nos dados, etc.
-## Utilizando o `jsonable_encoder` em uma `Response`
+## Utilizando o `jsonable_encoder` em uma `Response` { #using-the-jsonable-encoder-in-a-response }
Como o **FastAPI** não realiza nenhuma mudança na `Response` que você retorna, você precisa garantir que o conteúdo dela está pronto para uso.
@@ -34,7 +34,7 @@ Por exemplo, você não pode colocar um modelo do Pydantic em uma `JSONResponse`
Para esses casos, você pode usar o `jsonable_encoder` para converter seus dados antes de repassá-los para a resposta:
-{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *}
+{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *}
/// note | Detalhes Técnicos
@@ -44,23 +44,22 @@ Você também pode utilizar `from starlette.responses import JSONResponse`.
///
-## Retornando uma `Response`
+## Retornando uma `Response` personalizada { #returning-a-custom-response }
O exemplo acima mostra todas as partes que você precisa, mas ainda não é muito útil, já que você poderia ter retornado o `item` diretamente, e o **FastAPI** colocaria em uma `JSONResponse` para você, convertendo em um `dict`, etc. Tudo isso por padrão.
Agora, vamos ver como você pode usar isso para retornar uma resposta personalizada.
-Vamos dizer quer retornar uma resposta XML.
+Vamos dizer que você quer retornar uma resposta XML.
Você pode colocar o seu conteúdo XML em uma string, colocar em uma `Response`, e retorná-lo:
-{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
+{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
-## Notas
+## Notas { #notes }
Quando você retorna uma `Response` diretamente os dados não são validados, convertidos (serializados) ou documentados automaticamente.
-Mas você ainda pode documentar como descrito em [Retornos Adicionais no OpenAPI
-](additional-responses.md){.internal-link target=_blank}.
+Mas você ainda pode documentar como descrito em [Retornos Adicionais no OpenAPI](additional-responses.md){.internal-link target=_blank}.
Você pode ver nas próximas seções como usar/declarar essas `Responses` customizadas enquanto mantém a conversão e documentação automática dos dados.
diff --git a/docs/pt/docs/advanced/response-headers.md b/docs/pt/docs/advanced/response-headers.md
index a1fc84cc0e..14c3fb1869 100644
--- a/docs/pt/docs/advanced/response-headers.md
+++ b/docs/pt/docs/advanced/response-headers.md
@@ -1,12 +1,12 @@
-# Cabeçalhos de resposta
+# Cabeçalhos de resposta { #response-headers }
-## Usando um parâmetro `Response`
+## Use um parâmetro `Response` { #use-a-response-parameter }
Você pode declarar um parâmetro do tipo `Response` na sua *função de operação de rota* (assim como você pode fazer para cookies).
Então você pode definir os cabeçalhos nesse objeto de resposta *temporário*.
-{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *}
+{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *}
Em seguida você pode retornar qualquer objeto que precisar, da maneira que faria normalmente (um `dict`, um modelo de banco de dados, etc.).
@@ -16,15 +16,15 @@ Se você declarou um `response_model`, ele ainda será utilizado para filtrar e
Você também pode declarar o parâmetro `Response` em dependências e definir cabeçalhos (e cookies) nelas.
-## Retornar uma `Response` diretamente
+## Retorne uma `Response` diretamente { #return-a-response-directly }
Você também pode adicionar cabeçalhos quando retornar uma `Response` diretamente.
Crie uma resposta conforme descrito em [Retornar uma resposta diretamente](response-directly.md){.internal-link target=_blank} e passe os cabeçalhos como um parâmetro adicional:
-{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *}
+{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *}
-/// note | Detalhes técnicos
+/// note | Detalhes Técnicos
Você também pode usar `from starlette.responses import Response` ou `from starlette.responses import JSONResponse`.
@@ -34,8 +34,8 @@ E como a `Response` pode ser usada frequentemente para definir cabeçalhos e coo
///
-## Cabeçalhos personalizados
+## Cabeçalhos personalizados { #custom-headers }
-Tenha em mente que cabeçalhos personalizados proprietários podem ser adicionados usando o prefixo 'X-'.
+Tenha em mente que cabeçalhos personalizados proprietários podem ser adicionados usando o prefixo `X-`.
Porém, se voce tiver cabeçalhos personalizados que deseja que um cliente no navegador possa ver, você precisa adicioná-los às suas configurações de CORS (saiba mais em [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), usando o parâmetro `expose_headers` descrito na documentação de CORS do Starlette.
diff --git a/docs/pt/docs/advanced/security/http-basic-auth.md b/docs/pt/docs/advanced/security/http-basic-auth.md
index 3315139273..bd572217b2 100644
--- a/docs/pt/docs/advanced/security/http-basic-auth.md
+++ b/docs/pt/docs/advanced/security/http-basic-auth.md
@@ -1,10 +1,10 @@
-# HTTP Basic Auth
+# HTTP Basic Auth { #http-basic-auth }
Para os casos mais simples, você pode utilizar o HTTP Basic Auth.
No HTTP Basic Auth, a aplicação espera um cabeçalho que contém um usuário e uma senha.
-Caso ela não receba, ela retorna um erro HTTP 401 "Unauthorized" (*Não Autorizado*).
+Caso ela não receba, ela retorna um erro HTTP 401 "Unauthorized".
E retorna um cabeçalho `WWW-Authenticate` com o valor `Basic`, e um parâmetro opcional `realm`.
@@ -12,7 +12,7 @@ Isso sinaliza ao navegador para mostrar o prompt integrado para um usuário e se
Então, quando você digitar o usuário e senha, o navegador os envia automaticamente no cabeçalho.
-## HTTP Basic Auth Simples
+## HTTP Basic Auth Simples { #simple-http-basic-auth }
* Importe `HTTPBasic` e `HTTPBasicCredentials`.
* Crie um "esquema `security`" utilizando `HTTPBasic`.
@@ -22,11 +22,11 @@ Então, quando você digitar o usuário e senha, o navegador os envia automatica
{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *}
-Quando você tentar abrir a URL pela primeira vez (ou clicar no botão "Executar" nos documentos) o navegador vai pedir pelo seu usuário e senha:
+Quando você tentar abrir a URL pela primeira vez (ou clicar no botão "Executar" na documentação) o navegador vai pedir pelo seu usuário e senha:
-## Verifique o usuário
+## Verifique o usuário { #check-the-username }
Aqui está um exemplo mais completo.
@@ -52,7 +52,7 @@ if not (credentials.username == "stanleyjobson") or not (credentials.password ==
Porém, ao utilizar o `secrets.compare_digest()`, isso estará seguro contra um tipo de ataque chamado "timing attacks" (ataques de temporização).
-### Ataques de Temporização
+### Ataques de Temporização { #timing-attacks }
Mas o que é um "timing attack" (ataque de temporização)?
@@ -80,19 +80,19 @@ if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
O Python terá que comparar todo o `stanleyjobso` tanto em `stanleyjobsox` como em `stanleyjobson` antes de perceber que as strings não são a mesma. Então isso levará alguns microssegundos a mais para retornar "Usuário ou senha incorretos".
-#### O tempo para responder ajuda os invasores
+#### O tempo para responder ajuda os invasores { #the-time-to-answer-helps-the-attackers }
Neste ponto, ao perceber que o servidor demorou alguns microssegundos a mais para enviar o retorno "Usuário ou senha incorretos", os invasores irão saber que eles acertaram _alguma coisa_, algumas das letras iniciais estavam certas.
E eles podem tentar de novo sabendo que provavelmente é algo mais parecido com `stanleyjobsox` do que com `johndoe`.
-#### Um ataque "profissional"
+#### Um ataque "profissional" { #a-professional-attack }
Claro, os invasores não tentariam tudo isso de forma manual, eles escreveriam um programa para fazer isso, possivelmente com milhares ou milhões de testes por segundo. E obteriam apenas uma letra a mais por vez.
Mas fazendo isso, em alguns minutos ou horas os invasores teriam adivinhado o usuário e senha corretos, com a "ajuda" da nossa aplicação, apenas usando o tempo levado para responder.
-#### Corrija com o `secrets.compare_digest()`
+#### Corrija com o `secrets.compare_digest()` { #fix-it-with-secrets-compare-digest }
Mas em nosso código já estamos utilizando o `secrets.compare_digest()`.
@@ -100,8 +100,7 @@ Resumindo, levará o mesmo tempo para comparar `stanleyjobsox` com `stanleyjobso
Deste modo, ao utilizar `secrets.compare_digest()` no código de sua aplicação, ela estará a salvo contra toda essa gama de ataques de segurança.
-
-### Retorne o erro
+### Retorne o erro { #return-the-error }
Após detectar que as credenciais estão incorretas, retorne um `HTTPException` com o status 401 (o mesmo retornado quando nenhuma credencial foi informada) e adicione o cabeçalho `WWW-Authenticate` para fazer com que o navegador mostre o prompt de login novamente:
diff --git a/docs/pt/docs/advanced/security/index.md b/docs/pt/docs/advanced/security/index.md
index 6c7becb67a..70fb999d0e 100644
--- a/docs/pt/docs/advanced/security/index.md
+++ b/docs/pt/docs/advanced/security/index.md
@@ -1,6 +1,6 @@
-# Segurança Avançada
+# Segurança Avançada { #advanced-security }
-## Funcionalidades Adicionais
+## Funcionalidades Adicionais { #additional-features }
Existem algumas funcionalidades adicionais para lidar com segurança além das cobertas em [Tutorial - Guia de Usuário: Segurança](../../tutorial/security/index.md){.internal-link target=_blank}.
@@ -12,7 +12,7 @@ E é possível que para o seu caso de uso, a solução está em uma delas.
///
-## Leia o Tutorial primeiro
+## Leia o Tutorial primeiro { #read-the-tutorial-first }
As próximas seções pressupõem que você já leu o principal [Tutorial - Guia de Usuário: Segurança](../../tutorial/security/index.md){.internal-link target=_blank}.
diff --git a/docs/pt/docs/advanced/security/oauth2-scopes.md b/docs/pt/docs/advanced/security/oauth2-scopes.md
index 07b3459452..591ac9b4aa 100644
--- a/docs/pt/docs/advanced/security/oauth2-scopes.md
+++ b/docs/pt/docs/advanced/security/oauth2-scopes.md
@@ -1,4 +1,4 @@
-# Escopos OAuth2
+# Escopos OAuth2 { #oauth2-scopes }
Você pode utilizar escopos do OAuth2 diretamente com o **FastAPI**, eles são integrados para funcionar perfeitamente.
@@ -10,7 +10,7 @@ Toda vez que você "se autentica com" Facebook, Google, GitHub, Microsoft, X (Tw
Nesta seção, você verá como gerenciar a autenticação e autorização com os mesmos escopos do OAuth2 em sua aplicação **FastAPI**.
-/// warning | Aviso
+/// warning | Atenção
Isso é uma seção mais ou menos avançada. Se você está apenas começando, você pode pular.
@@ -18,7 +18,7 @@ Você não necessariamente precisa de escopos do OAuth2, e você pode lidar com
Mas o OAuth2 com escopos pode ser integrado de maneira fácil em sua API (com OpenAPI) e a sua documentação de API.
-No entando, você ainda aplica estes escopos, ou qualquer outro requisito de segurança/autorização, conforme necessário, em seu código.
+No entanto, você ainda aplica estes escopos, ou qualquer outro requisito de segurança/autorização, conforme necessário, em seu código.
Em muitos casos, OAuth2 com escopos pode ser um exagero.
@@ -26,7 +26,7 @@ Mas se você sabe que precisa, ou está curioso, continue lendo.
///
-## Escopos OAuth2 e OpenAPI
+## Escopos OAuth2 e OpenAPI { #oauth2-scopes-and-openapi }
A especificação OAuth2 define "escopos" como uma lista de strings separadas por espaços.
@@ -58,15 +58,15 @@ Para o OAuth2, eles são apenas strings.
///
-## Visão global
+## Visão global { #global-view }
Primeiro, vamos olhar rapidamente as partes que mudam dos exemplos do **Tutorial - Guia de Usuário** para [OAuth2 com Senha (e hash), Bearer com tokens JWT](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Agora utilizando escopos OAuth2:
-{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:125,129:135,140,156] *}
+{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *}
Agora vamos revisar essas mudanças passo a passo.
-## Esquema de segurança OAuth2
+## Esquema de segurança OAuth2 { #oauth2-security-scheme }
A primeira mudança é que agora nós estamos declarando o esquema de segurança OAuth2 com dois escopos disponíveis, `me` e `items`.
@@ -82,9 +82,9 @@ Este é o mesmo mecanismo utilizado quando você adiciona permissões enquanto s
-## Token JWT com escopos
+## Token JWT com escopos { #jwt-token-with-scopes }
-Agora, modifique o *caminho de rota* para retornar os escopos solicitados.
+Agora, modifique a *operação de rota* do token para retornar os escopos solicitados.
Nós ainda estamos utilizando o mesmo `OAuth2PasswordRequestForm`. Ele inclui a propriedade `scopes` com uma `list` de `str`, com cada escopo que ele recebeu na requisição.
@@ -98,15 +98,15 @@ Porém em sua aplicação, por segurança, você deve garantir que você apenas
///
-{* ../../docs_src/security/tutorial005_an_py310.py hl[156] *}
+{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}
-## Declare escopos em *operações de rota* e dependências
+## Declare escopos em *operações de rota* e dependências { #declare-scopes-in-path-operations-and-dependencies }
Agora nós declaramos que a *operação de rota* para `/users/me/items/` exige o escopo `items`.
Para isso, nós importamos e utilizamos `Security` de `fastapi`.
-Você pode utilizar `Security` para declarar dependências (assim como `Depends`), porém o `Security` também recebe o parâmetros `scopes` com uma lista de escopos (strings).
+Você pode utilizar `Security` para declarar dependências (assim como `Depends`), porém o `Security` também recebe o parâmetro `scopes` com uma lista de escopos (strings).
Neste caso, nós passamos a função `get_current_active_user` como dependência para `Security` (da mesma forma que nós faríamos com `Depends`).
@@ -124,9 +124,9 @@ Nós estamos fazendo isso aqui para demonstrar como o **FastAPI** lida com escop
///
-{* ../../docs_src/security/tutorial005_an_py310.py hl[5,140,171] *}
+{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}
-/// info | Informações Técnicas
+/// info | Detalhes Técnicos
`Security` é na verdade uma subclasse de `Depends`, e ele possui apenas um parâmetro extra que veremos depois.
@@ -136,7 +136,7 @@ Mas quando você importa `Query`, `Path`, `Depends`, `Security` entre outros de
///
-## Utilize `SecurityScopes`
+## Utilize `SecurityScopes` { #use-securityscopes }
Agora atualize a dependência `get_current_user`.
@@ -152,7 +152,7 @@ A classe `SecurityScopes` é semelhante à classe `Request` (`Request` foi utili
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}
-## Utilize os `scopes`
+## Utilize os `scopes` { #use-the-scopes }
O parâmetro `security_scopes` será do tipo `SecurityScopes`.
@@ -166,7 +166,7 @@ Nesta exceção, nós incluímos os escopos necessários (se houver algum) como
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}
-## Verifique o `username` e o formato dos dados
+## Verifique o `username` e o formato dos dados { #verify-the-username-and-data-shape }
Nós verificamos que nós obtemos um `username`, e extraímos os escopos.
@@ -180,17 +180,17 @@ No lugar de, por exemplo, um `dict`, ou alguma outra coisa, que poderia quebrar
Nós também verificamos que nós temos um usuário com o "*username*", e caso contrário, nós levantamos a mesma exceção que criamos anteriormente.
-{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:128] *}
+{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}
-## Verifique os `scopes`
+## Verifique os `scopes` { #verify-the-scopes }
Nós verificamos agora que todos os escopos necessários, por essa dependência e todos os dependentes (incluindo as *operações de rota*) estão incluídas nos escopos fornecidos pelo token recebido, caso contrário, levantamos uma `HTTPException`.
Para isso, nós utilizamos `security_scopes.scopes`, que contém uma `list` com todos esses escopos como uma `str`.
-{* ../../docs_src/security/tutorial005_an_py310.py hl[129:135] *}
+{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}
-## Árvore de dependência e escopos
+## Árvore de dependência e escopos { #dependency-tree-and-scopes }
Vamos rever novamente essa árvore de dependência e os escopos.
@@ -223,7 +223,7 @@ Tudo depende dos `scopes` declarados em cada *operação de rota* e cada depend
///
-## Mais detalhes sobre `SecurityScopes`
+## Mais detalhes sobre `SecurityScopes` { #more-details-about-securityscopes }
Você pode utilizar `SecurityScopes` em qualquer lugar, e em diversos lugares. Ele não precisa estar na dependência "raiz".
@@ -233,9 +233,9 @@ Porque o `SecurityScopes` terá todos os escopos declarados por dependentes, voc
Todos eles serão validados independentemente para cada *operação de rota*.
-## Verifique
+## Verifique { #check-it }
-Se você abrir os documentos da API, você pode antenticar e especificar quais escopos você quer autorizar.
+Se você abrir os documentos da API, você pode autenticar e especificar quais escopos você quer autorizar.
@@ -245,9 +245,9 @@ E se você selecionar o escopo `me`, mas não o escopo `items`, você poderá ac
Isso é o que aconteceria se uma aplicação terceira que tentou acessar uma dessas *operações de rota* com um token fornecido por um usuário, dependendo de quantas permissões o usuário forneceu para a aplicação.
-## Sobre integrações de terceiros
+## Sobre integrações de terceiros { #about-third-party-integrations }
-Neste exemplos nós estamos utilizando o fluxo de senha do OAuth2.
+Neste exemplo nós estamos utilizando o fluxo de senha do OAuth2.
Isso é apropriado quando nós estamos autenticando em nossa própria aplicação, provavelmente com o nosso próprio "*frontend*".
@@ -269,6 +269,6 @@ Mas no final, eles estão implementando o mesmo padrão OAuth2.
O **FastAPI** inclui utilitários para todos esses fluxos de autenticação OAuth2 em `fastapi.security.oauth2`.
-## `Security` em docoradores de `dependências`
+## `Security` em decoradores de `dependencies` { #security-in-decorator-dependencies }
-Da mesma forma que você pode definir uma `list` de `Depends` no parâmetro de `dependencias` do decorador (como explicado em [Dependências em decoradores de operações de rota](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), você também pode utilizar `Security` com escopos lá.
+Da mesma forma que você pode definir uma `list` de `Depends` no parâmetro `dependencies` do decorador (como explicado em [Dependências em decoradores de operações de rota](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), você também pode utilizar `Security` com escopos lá.
diff --git a/docs/pt/docs/advanced/settings.md b/docs/pt/docs/advanced/settings.md
index cdc6400adf..6f5b7feae7 100644
--- a/docs/pt/docs/advanced/settings.md
+++ b/docs/pt/docs/advanced/settings.md
@@ -1,148 +1,30 @@
-# Configurações e Variáveis de Ambiente
+# Configurações e Variáveis de Ambiente { #settings-and-environment-variables }
-Em muitos casos a sua aplicação pode precisar de configurações externas, como chaves secretas, credenciais de banco de dados, credenciais para serviços de email, etc.
+Em muitos casos, sua aplicação pode precisar de configurações externas, por exemplo chaves secretas, credenciais de banco de dados, credenciais para serviços de e-mail, etc.
-A maioria dessas configurações é variável (podem mudar), como URLs de bancos de dados. E muitas delas podem conter dados sensíveis, como tokens secretos.
+A maioria dessas configurações é variável (pode mudar), como URLs de banco de dados. E muitas podem ser sensíveis, como segredos.
-Por isso é comum prover essas configurações como variáveis de ambiente que são utilizidas pela aplicação.
-
-## Variáveis de Ambiente
+Por esse motivo, é comum fornecê-las em variáveis de ambiente lidas pela aplicação.
/// tip | Dica
-Se você já sabe o que são variáveis de ambiente e como utilizá-las, sinta-se livre para avançar para o próximo tópico.
+Para entender variáveis de ambiente, você pode ler [Variáveis de Ambiente](../environment-variables.md){.internal-link target=_blank}.
///
-Uma variável de ambiente (abreviada em inglês para "env var") é uma variável definida fora do código Python, no sistema operacional, e pode ser lida pelo seu código Python (ou por outros programas).
+## Tipagem e validação { #types-and-validation }
-Você pode criar e utilizar variáveis de ambiente no terminal, sem precisar utilizar Python:
+Essas variáveis de ambiente só conseguem lidar com strings de texto, pois são externas ao Python e precisam ser compatíveis com outros programas e com o resto do sistema (e até com diferentes sistemas operacionais, como Linux, Windows, macOS).
-//// tab | Linux, macOS, Windows Bash
+Isso significa que qualquer valor lido em Python a partir de uma variável de ambiente será uma `str`, e qualquer conversão para um tipo diferente ou validação precisa ser feita em código.
-
Se você tentar interagir com qualquer uma das duas interfaces de usuário, elas funcionarão corretamente, porque o navegador será capaz de se comunicar com cada aplicação ou sub-aplicação específica.
-### Detalhes Técnicos: `root_path`
+### Detalhes Técnicos: `root_path` { #technical-details-root-path }
-Quando você monta uma sub-aplicação como descrito acima, o FastAPI se encarrega de comunicar o caminho de montagem para a sub-aplicação usando um mecanismo da especificação ASGI chamado `root_path`.
+Quando você monta uma sub-aplicação como descrito acima, o FastAPI se encarrega de comunicar o path de montagem para a sub-aplicação usando um mecanismo da especificação ASGI chamado `root_path`.
-Dessa forma, a sub-aplicação saberá usar esse prefixo de caminho para a interface de documentação.
+Dessa forma, a sub-aplicação saberá usar esse prefixo de path para a interface de documentação.
E a sub-aplicação também poderia ter suas próprias sub-aplicações montadas e tudo funcionaria corretamente, porque o FastAPI lida com todos esses `root_path`s automaticamente.
diff --git a/docs/pt/docs/advanced/templates.md b/docs/pt/docs/advanced/templates.md
index 65ff89faee..eb64e72bb8 100644
--- a/docs/pt/docs/advanced/templates.md
+++ b/docs/pt/docs/advanced/templates.md
@@ -1,4 +1,4 @@
-# Templates
+# Templates { #templates }
Você pode usar qualquer template engine com o **FastAPI**.
@@ -6,28 +6,30 @@ Uma escolha comum é o Jinja2, o mesmo usado pelo Flask e outras ferramentas.
Existem utilitários para configurá-lo facilmente que você pode usar diretamente em sua aplicação **FastAPI** (fornecidos pelo Starlette).
-## Instalação de dependências
+## Instalar dependências { #install-dependencies }
-Para instalar o `jinja2`, siga o código abaixo:
+Certifique-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e instalar `jinja2`:
-## Lidando com desconexões e múltiplos clientes
+## Lidando com desconexões e múltiplos clientes { #handling-disconnections-and-multiple-clients }
Quando uma conexão WebSocket é fechada, o `await websocket.receive_text()` levantará uma exceção `WebSocketDisconnect`, que você pode então capturar e lidar como neste exemplo.
-{*../../docs_src/websockets/tutorial003_py39.py hl[79:81]*}
+{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *}
Para testar:
-* Abrar o aplicativo com várias abas do navegador.
+* Abra o aplicativo com várias abas do navegador.
* Escreva mensagens a partir delas.
* Então feche uma das abas.
@@ -172,13 +172,13 @@ Client #1596980209979 left the chat
O app acima é um exemplo mínimo e simples para demonstrar como lidar e transmitir mensagens para várias conexões WebSocket.
-Mas tenha em mente que, como tudo é manipulado na memória, em uma única lista, ele só funcionará enquanto o processo estiver em execução e só funcionará com um único processo.
+Mas tenha em mente que, como tudo é manipulado na memória, em uma única list, ele só funcionará enquanto o processo estiver em execução e só funcionará com um único processo.
Se você precisa de algo fácil de integrar com o FastAPI, mas que seja mais robusto, suportado por Redis, PostgreSQL ou outros, verifique o encode/broadcaster.
///
-## Mais informações
+## Mais informações { #more-info }
Para aprender mais sobre as opções, verifique a documentação do Starlette para:
diff --git a/docs/pt/docs/advanced/wsgi.md b/docs/pt/docs/advanced/wsgi.md
index a36261e5ef..c3c21d430a 100644
--- a/docs/pt/docs/advanced/wsgi.md
+++ b/docs/pt/docs/advanced/wsgi.md
@@ -1,22 +1,22 @@
-# Adicionando WSGI - Flask, Django, entre outros
+# Adicionando WSGI - Flask, Django, entre outros { #including-wsgi-flask-django-others }
-Como você viu em [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank} e [Behind a Proxy](behind-a-proxy.md){.internal-link target=_blank}, você pode **"montar"** aplicações WSGI.
+Como você viu em [Subaplicações - Montagens](sub-applications.md){.internal-link target=_blank} e [Atrás de um Proxy](behind-a-proxy.md){.internal-link target=_blank}, você pode montar aplicações WSGI.
Para isso, você pode utilizar o `WSGIMiddleware` para encapsular a sua aplicação WSGI, como por exemplo Flask, Django, etc.
-## Usando o `WSGIMiddleware`
+## Usando `WSGIMiddleware` { #using-wsgimiddleware }
Você precisa importar o `WSGIMiddleware`.
-Em seguinda, encapsular a aplicação WSGI (e.g. Flask) com o middleware.
+Em seguida, encapsule a aplicação WSGI (e.g. Flask) com o middleware.
-E então **"montar"** em um caminho de rota.
+E então monte isso sob um path.
-{* ../../docs_src/wsgi/tutorial001.py hl[2:3,23] *}
+{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *}
-## Conferindo
+## Confira { #check-it }
-Agora todas as requisições sob o caminho `/v1/` serão manipuladas pela aplicação utilizando Flask.
+Agora, todas as requisições sob o path `/v1/` serão manipuladas pela aplicação Flask.
E o resto será manipulado pelo **FastAPI**.
diff --git a/docs/pt/docs/alternatives.md b/docs/pt/docs/alternatives.md
index 66cf3fe127..fd992ec951 100644
--- a/docs/pt/docs/alternatives.md
+++ b/docs/pt/docs/alternatives.md
@@ -1,34 +1,34 @@
-# Alternativas, Inspiração e Comparações
+# Alternativas, Inspiração e Comparações { #alternatives-inspiration-and-comparisons }
-O que inspirou o **FastAPI**, como ele se compara às alternativas e o que FastAPI aprendeu delas.
+O que inspirou o **FastAPI**, como ele se compara às alternativas e o que ele aprendeu com elas.
-## Introdução
+## Introdução { #intro }
-**FastAPI** não poderia existir se não fosse pelos trabalhos anteriores de outras pessoas.
+**FastAPI** não existiria se não fosse pelo trabalho anterior de outras pessoas.
-Houveram tantas ferramentas criadas que ajudaram a inspirar sua criação.
+Houve muitas ferramentas criadas antes que ajudaram a inspirar sua criação.
-Tenho evitado criar um novo framework por anos. Primeiramente tentei resolver todos os recursos cobertos pelo **FastAPI** utilizando muitos frameworks diferentes, plug-ins e ferramentas.
+Tenho evitado criar um novo framework por vários anos. Primeiro tentei resolver todas as funcionalidades cobertas pelo **FastAPI** utilizando muitos frameworks, plug-ins e ferramentas diferentes.
-Mas em algum ponto, não houve outra opção senão criar algo que fornecesse todos esses recursos, pegando as melhores idéias de ferramentas anteriores, e combinando eles da melhor forma possível, utilizando recursos da linguagem que não estavam disponíveis antes (_Type Hints_ no Python 3.6+).
+Mas em algum momento, não havia outra opção senão criar algo que fornecesse todos esses recursos, pegando as melhores ideias de ferramentas anteriores e combinando-as da melhor maneira possível, usando funcionalidades da linguagem que nem sequer estavam disponíveis antes (anotações de tipo no Python 3.6+).
-## Ferramentas anteriores
+## Ferramentas anteriores { #previous-tools }
-### Django
+### Django { #django }
-É o framework mais popular e largamente confiável. É utilizado para construir sistemas como o _Instagram_.
+É o framework Python mais popular e amplamente confiável. É utilizado para construir sistemas como o Instagram.
-É bem acoplado com banco de dados relacional (como MySQL ou PostgreSQL), então, tendo um banco de dados NoSQL (como Couchbase, MongoDB, Cassandra etc) como a principal ferramenta de armazenamento não é muito fácil.
+É relativamente bem acoplado com bancos de dados relacionais (como MySQL ou PostgreSQL), então, ter um banco de dados NoSQL (como Couchbase, MongoDB, Cassandra, etc.) como mecanismo principal de armazenamento não é muito fácil.
-Foi criado para gerar HTML no _backend_, não para criar APIs utilizando um _frontend_ moderno (como React, Vue.js e Angular) ou por outros sistemas (como dispositivos IoT) comunicando com ele.
+Foi criado para gerar o HTML no backend, não para criar APIs usadas por um frontend moderno (como React, Vue.js e Angular) ou por outros sistemas (como dispositivos IoT) comunicando com ele.
-### Django REST Framework
+### Django REST Framework { #django-rest-framework }
-Django REST framework foi criado para ser uma caixa de ferramentas flexível para construção de APIs web utilizando Django por baixo, para melhorar suas capacidades de API.
+Django REST framework foi criado para ser uma caixa de ferramentas flexível para construção de APIs Web utilizando Django por baixo, para melhorar suas capacidades de API.
-Ele é utilizado por muitas companhias incluindo Mozilla, Red Hat e Eventbrite.
+Ele é utilizado por muitas empresas incluindo Mozilla, Red Hat e Eventbrite.
-Ele foi um dos primeiros exemplos de **documentação automática de API**, e essa foi especificamente uma das primeiras idéias que inspirou "a busca por" **FastAPI**.
+Foi um dos primeiros exemplos de **documentação automática de API**, e essa foi especificamente uma das primeiras ideias que inspirou "a busca por" **FastAPI**.
/// note | Nota
@@ -38,57 +38,57 @@ Django REST Framework foi criado por Tom Christie. O mesmo criador de Starlette
/// check | **FastAPI** inspirado para
-Ter uma documentação automática da API em interface web.
+Ter uma interface web de documentação automática da API.
///
-### Flask
+### Flask { #flask }
-Flask é um "microframework", não inclui integração com banco de dados nem muitas das coisas que vêm por padrão no Django.
+Flask é um "microframework", não inclui integrações com banco de dados nem muitas das coisas que vêm por padrão no Django.
-Sua simplicidade e flexibilidade permitem fazer coisas como utilizar bancos de dados NoSQL como principal sistema de armazenamento de dados.
+Essa simplicidade e flexibilidade permitem fazer coisas como utilizar bancos de dados NoSQL como o principal sistema de armazenamento de dados.
-Por ser tão simples, é relativamente intuitivo de aprender, embora a documentação esteja de forma mais técnica em alguns pontos.
+Por ser muito simples, é relativamente intuitivo de aprender, embora a documentação se torne um pouco técnica em alguns pontos.
-Ele é comumente utilizado por outras aplicações que não necessariamente precisam de banco de dados, gerenciamento de usuários, ou algum dos muitos recursos que já vem instalados no Django. Embora muitos desses recursos possam ser adicionados com plug-ins.
+Ele também é comumente utilizado por outras aplicações que não necessariamente precisam de banco de dados, gerenciamento de usuários, ou qualquer uma das muitas funcionalidades que já vêm prontas no Django. Embora muitas dessas funcionalidades possam ser adicionadas com plug-ins.
-Esse desacoplamento de partes, e sendo um "microframework" que pode ser extendido para cobrir exatamente o que é necessário era um recurso chave que eu queria manter.
+Esse desacoplamento de partes, e ser um "microframework" que pode ser estendido para cobrir exatamente o que é necessário era uma funcionalidade chave que eu queria manter.
-Dada a simplicidade do Flask, parecia uma ótima opção para construção de APIs. A próxima coisa a procurar era um "Django REST Framework" para Flask.
+Dada a simplicidade do Flask, ele parecia uma boa opção para construção de APIs. A próxima coisa a encontrar era um "Django REST Framework" para Flask.
/// check | **FastAPI** inspirado para
-Ser um microframework. Fazer ele fácil para misturar e combinar com ferramentas e partes necessárias.
+Ser um microframework. Tornar fácil misturar e combinar as ferramentas e partes necessárias.
-Ser simples e com sistema de roteamento fácil de usar.
+Ter um sistema de roteamento simples e fácil de usar.
///
-### Requests
+### Requests { #requests }
-**FastAPI** não é uma alternativa para **Requests**. O escopo deles é muito diferente.
+**FastAPI** na verdade não é uma alternativa ao **Requests**. O escopo deles é muito diferente.
-Na verdade é comum utilizar Requests *dentro* de uma aplicação FastAPI.
+Na verdade, é comum utilizar Requests dentro de uma aplicação FastAPI.
-Ainda assim, FastAPI pegou alguma inspiração do Requests.
+Ainda assim, o FastAPI tirou bastante inspiração do Requests.
-**Requests** é uma biblioteca para interagir com APIs (como um cliente), enquanto **FastAPI** é uma biblioteca para *construir* APIs (como um servidor).
+**Requests** é uma biblioteca para interagir com APIs (como um cliente), enquanto **FastAPI** é uma biblioteca para construir APIs (como um servidor).
-Eles estão, mais ou menos, em pontas opostas, um complementando o outro.
+Eles estão, mais ou menos, em pontas opostas, complementando-se.
-Requests tem um projeto muito simples e intuitivo, fácil de usar, com padrões sensíveis. Mas ao mesmo tempo, é muito poderoso e customizável.
+Requests tem um design muito simples e intuitivo, é muito fácil de usar, com padrões sensatos. Mas ao mesmo tempo, é muito poderoso e personalizável.
É por isso que, como dito no site oficial:
> Requests é um dos pacotes Python mais baixados de todos os tempos
-O jeito de usar é muito simples. Por exemplo, para fazer uma requisição `GET`, você deveria escrever:
+O jeito de usar é muito simples. Por exemplo, para fazer uma requisição `GET`, você escreveria:
```Python
response = requests.get("http://example.com/some/url")
```
-A contra-parte da aplicação FastAPI, *rota de operação*, poderia parecer como:
+A contra-parte na aplicação FastAPI, a operação de rota, poderia ficar assim:
```Python hl_lines="1"
@app.get("/some/url")
@@ -102,50 +102,50 @@ Veja as similaridades em `requests.get(...)` e `@app.get(...)`.
* Ter uma API simples e intuitiva.
* Utilizar nomes de métodos HTTP (operações) diretamente, de um jeito direto e intuitivo.
-* Ter padrões sensíveis, mas customizações poderosas.
+* Ter padrões sensatos, mas customizações poderosas.
///
-### Swagger / OpenAPI
+### Swagger / OpenAPI { #swagger-openapi }
-O principal recurso que eu queria do Django REST Framework era a documentação automática da API.
+A principal funcionalidade que eu queria do Django REST Framework era a documentação automática da API.
-Então eu descobri que existia um padrão para documentar APIs, utilizando JSON (ou YAML, uma extensão do JSON) chamado Swagger.
+Então descobri que existia um padrão para documentar APIs, utilizando JSON (ou YAML, uma extensão do JSON) chamado Swagger.
-E tinha uma interface web para APIs Swagger já criada. Então, sendo capaz de gerar documentação Swagger para uma API poderia permitir utilizar essa interface web automaticamente.
+E havia uma interface web para APIs Swagger já criada. Então, ser capaz de gerar documentação Swagger para uma API permitiria usar essa interface web automaticamente.
-Em algum ponto, Swagger foi dado para a Fundação Linux, e foi renomeado OpenAPI.
+Em algum ponto, Swagger foi doado para a Fundação Linux, para ser renomeado OpenAPI.
-Isso acontece porquê quando alguém fala sobre a versão 2.0 é comum dizer "Swagger", e para a versão 3+, "OpenAPI".
+É por isso que ao falar sobre a versão 2.0 é comum dizer "Swagger", e para a versão 3+ "OpenAPI".
/// check | **FastAPI** inspirado para
-Adotar e usar um padrão aberto para especificações API, ao invés de algum esquema customizado.
+Adotar e usar um padrão aberto para especificações de API, em vez de um schema personalizado.
-E integrar ferramentas de interface para usuários baseado nos padrões:
+E integrar ferramentas de interface para usuários baseadas nos padrões:
* Swagger UI
* ReDoc
-Esses dois foram escolhidos por serem bem populares e estáveis, mas fazendo uma pesquisa rápida, você pode encontrar dúzias de interfaces alternativas adicionais para OpenAPI (assim você poderá utilizar com **FastAPI**).
+Essas duas foram escolhidas por serem bem populares e estáveis, mas fazendo uma pesquisa rápida, você pode encontrar dúzias de interfaces alternativas adicionais para OpenAPI (que você pode utilizar com **FastAPI**).
///
-### Flask REST frameworks
+### Flask REST frameworks { #flask-rest-frameworks }
-Existem vários Flask REST frameworks, mas depois de investir tempo e trabalho investigando eles, eu descobri que muitos estão descontinuados ou abandonados, com alguns tendo questões que fizeram eles inadequados.
+Existem vários Flask REST frameworks, mas depois de investir tempo e trabalho investigando-os, descobri que muitos estão descontinuados ou abandonados, com diversas questões em aberto que os tornaram inadequados.
-### Marshmallow
+### Marshmallow { #marshmallow }
-Um dos principais recursos necessários em sistemas API é "serialização" de dados, que é pegar dados do código (Python) e converter eles em alguma coisa que possa ser enviado através da rede. Por exemplo, converter um objeto contendo dados de um banco de dados em um objeto JSON. Converter objetos `datetime` em strings etc.
+Uma das principais funcionalidades necessárias em sistemas de API é a "serialização" de dados, que é pegar dados do código (Python) e convertê-los em algo que possa ser enviado pela rede. Por exemplo, converter um objeto contendo dados de um banco de dados em um objeto JSON. Converter objetos `datetime` em strings, etc.
-Outro grande recurso necessário nas APIs é validação de dados, certificando que os dados são válidos, dados certos parâmetros. Por exemplo, algum campo é `int`, e não alguma string aleatória. Isso é especialmente útil para dados que estão chegando.
+Outra grande funcionalidade necessária pelas APIs é a validação de dados, garantindo que os dados são válidos, dados certos parâmetros. Por exemplo, que algum campo seja `int`, e não alguma string aleatória. Isso é especialmente útil para dados de entrada.
Sem um sistema de validação de dados, você teria que realizar todas as verificações manualmente, no código.
-Esses recursos são o que Marshmallow foi construído para fornecer. Ele é uma ótima biblioteca, e eu já utilizei muito antes.
+Essas funcionalidades são o que o Marshmallow foi construído para fornecer. É uma ótima biblioteca, e eu a utilizei bastante antes.
-Mas ele foi criado antes da existência do _type hints_ do Python. Então, para definir todo o _schema_ você precisa utilizar específicas ferramentas e classes fornecidas pelo Marshmallow.
+Mas ele foi criado antes de existirem as anotações de tipo do Python. Então, para definir cada schema você precisa utilizar utilitários e classes específicos fornecidos pelo Marshmallow.
/// check | **FastAPI** inspirado para
@@ -153,17 +153,17 @@ Usar código para definir "schemas" que forneçam, automaticamente, tipos de dad
///
-### Webargs
+### Webargs { #webargs }
-Outro grande recurso necessário pelas APIs é a análise de dados vindos de requisições.
+Outra grande funcionalidade requerida pelas APIs é o parsing de dados vindos de requisições de entrada.
-Webargs é uma ferramente feita para fornecer o que está no topo de vários frameworks, inclusive Flask.
+Webargs é uma ferramenta feita para fornecer isso no topo de vários frameworks, inclusive Flask.
-Ele utiliza Marshmallow por baixo para validação de dados. E ele foi criado pelos mesmos desenvolvedores.
+Ele utiliza Marshmallow por baixo para a validação de dados. E foi criado pelos mesmos desenvolvedores.
-Ele é uma grande ferramenta e eu também a utilizei muito, antes de ter o **FastAPI**.
+É uma grande ferramenta e eu também a utilizei bastante, antes de ter o **FastAPI**.
-/// info
+/// info | Informação
Webargs foi criado pelos mesmos desenvolvedores do Marshmallow.
@@ -171,29 +171,29 @@ Webargs foi criado pelos mesmos desenvolvedores do Marshmallow.
/// check | **FastAPI** inspirado para
-Ter validação automática de dados vindos de requisições.
+Ter validação automática dos dados de requisições de entrada.
///
-### APISpec
+### APISpec { #apispec }
-Marshmallow e Webargs fornecem validação, análise e serialização como plug-ins.
+Marshmallow e Webargs fornecem validação, parsing e serialização como plug-ins.
-Mas a documentação ainda está faltando. Então APISpec foi criado.
+Mas a documentação ainda estava faltando. Então APISpec foi criado.
-APISpec tem plug-ins para muitos frameworks (e tem um plug-in para Starlette também).
+É um plug-in para muitos frameworks (e há um plug-in para Starlette também).
-O jeito como ele funciona é que você escreve a definição do _schema_ usando formato YAML dentro da _docstring_ de cada função controlando uma rota.
+O jeito como ele funciona é que você escreve a definição do schema usando formato YAML dentro da docstring de cada função que lida com uma rota.
-E ele gera _schemas_ OpenAPI.
+E ele gera schemas OpenAPI.
-É assim como funciona no Flask, Starlette, Responder etc.
+É assim como funciona no Flask, Starlette, Responder, etc.
-Mas então, nós temos novamente o problema de ter uma micro-sintaxe, dentro de uma string Python (um grande YAML).
+Mas então, temos novamente o problema de ter uma micro-sintaxe, dentro de uma string Python (um grande YAML).
-O editor não poderá ajudar muito com isso. E se nós modificarmos os parâmetros dos _schemas_ do Marshmallow e esquecer de modificar também aquela _docstring_ YAML, o _schema_ gerado pode ficar obsoleto.
+O editor não pode ajudar muito com isso. E se modificarmos parâmetros ou schemas do Marshmallow e esquecermos de também modificar aquela docstring em YAML, o schema gerado ficaria obsoleto.
-/// info
+/// info | Informação
APISpec foi criado pelos mesmos desenvolvedores do Marshmallow.
@@ -201,31 +201,31 @@ APISpec foi criado pelos mesmos desenvolvedores do Marshmallow.
/// check | **FastAPI** inspirado para
-Dar suporte a padrões abertos para APIs, OpenAPI.
+Dar suporte ao padrão aberto para APIs, OpenAPI.
///
-### Flask-apispec
+### Flask-apispec { #flask-apispec }
-É um plug-in Flask, que amarra junto Webargs, Marshmallow e APISpec.
+É um plug-in Flask, que amarra juntos Webargs, Marshmallow e APISpec.
-Ele utiliza a informação do Webargs e Marshmallow para gerar automaticamente _schemas_ OpenAPI, usando APISpec.
+Ele utiliza a informação do Webargs e Marshmallow para gerar automaticamente schemas OpenAPI, usando APISpec.
-É uma grande ferramenta, mas muito subestimada. Ela deveria ser um pouco mais popular do que muitos outros plug-ins Flask. É de ser esperado que sua documentação seja bem concisa e abstrata.
+É uma grande ferramenta, muito subestimada. Deveria ser bem mais popular do que muitos plug-ins Flask por aí. Pode ser devido à sua documentação ser concisa e abstrata demais.
-Isso resolveu o problema de ter que escrever YAML (outra sintaxe) dentro das _docstrings_ Python.
+Isso resolveu ter que escrever YAML (outra sintaxe) dentro das docstrings do Python.
-Essa combinação de Flask, Flask-apispec com Marshmallow e Webargs foi meu _backend stack_ favorito até construir o **FastAPI**.
+Essa combinação de Flask, Flask-apispec com Marshmallow e Webargs foi a minha stack de backend favorita até construir o **FastAPI**.
-Usando essa combinação levou a criação de vários geradores Flask _full-stack_. Há muitas _stacks_ que eu (e vários times externos) estou utilizando até agora:
+Usá-la levou à criação de vários geradores Flask full-stack. Estas são as principais stacks que eu (e várias equipes externas) tenho utilizado até agora:
* https://github.com/tiangolo/full-stack
* https://github.com/tiangolo/full-stack-flask-couchbase
* https://github.com/tiangolo/full-stack-flask-couchdb
-E esses mesmos geradores _full-stack_ foram a base dos [Geradores de Projetos **FastAPI**](project-generation.md){.internal-link target=_blank}.
+E esses mesmos geradores full-stack foram a base dos [Geradores de Projetos **FastAPI**](project-generation.md){.internal-link target=_blank}.
-/// info
+/// info | Informação
Flask-apispec foi criado pelos mesmos desenvolvedores do Marshmallow.
@@ -233,151 +233,149 @@ Flask-apispec foi criado pelos mesmos desenvolvedores do Marshmallow.
/// check | **FastAPI** inspirado para
-Gerar _schema_ OpenAPI automaticamente, a partir do mesmo código que define serialização e validação.
+Gerar o schema OpenAPI automaticamente, a partir do mesmo código que define serialização e validação.
///
-### NestJS (and Angular)
+### NestJS (e Angular) { #nestjs-and-angular }
-NestJS, que não é nem Python, é um framework NodeJS JavaScript (TypeScript) inspirado pelo Angular.
+Isso nem é Python, NestJS é um framework NodeJS em JavaScript (TypeScript) inspirado pelo Angular.
-Ele alcança de uma forma similar ao que pode ser feito com o Flask-apispec.
+Ele alcança algo um tanto similar ao que pode ser feito com Flask-apispec.
-Ele tem um sistema de injeção de dependência integrado, inspirado pelo Angular 2. É necessário fazer o pré-registro dos "injetáveis" (como todos os sistemas de injeção de dependência que conheço), então, adicionando verbosidade e repetição de código.
+Ele tem um sistema de injeção de dependência integrado, inspirado pelo Angular 2. É necessário fazer o pré-registro dos "injetáveis" (como todos os sistemas de injeção de dependência que conheço), então, adiciona verbosidade e repetição de código.
-Como os parâmetros são descritos com tipos TypeScript (similar aos _type hints_ do Python), o suporte ao editor é muito bom.
+Como os parâmetros são descritos com tipos do TypeScript (similares às anotações de tipo do Python), o suporte do editor é muito bom.
-Mas como os dados TypeScript não são preservados após a compilação para o JavaScript, ele não pode depender dos tipos para definir a validação, serialização e documentação ao mesmo tempo. Devido a isso e a algumas decisões de projeto, para pegar a validação, serialização e geração automática do _schema_, é necessário adicionar decoradores em muitos lugares. Então, ele se torna muito verboso.
+Mas como os dados do TypeScript não são preservados após a compilação para JavaScript, ele não pode depender dos tipos para definir validação, serialização e documentação ao mesmo tempo. Devido a isso e a algumas decisões de projeto, para obter validação, serialização e geração automática de schema, é necessário adicionar decorators em muitos lugares. Então, ele se torna bastante verboso.
-Ele também não controla modelos aninhados muito bem. Então, se o corpo JSON na requisição for um objeto JSON que contém campos internos que contém objetos JSON aninhados, ele não consegue ser validado e documentado apropriadamente.
+Ele não consegue lidar muito bem com modelos aninhados. Então, se o corpo JSON na requisição for um objeto JSON que contém campos internos que por sua vez são objetos JSON aninhados, ele não consegue ser documentado e validado apropriadamente.
/// check | **FastAPI** inspirado para
-Usar tipos Python para ter um ótimo suporte do editor.
+Usar tipos do Python para ter um ótimo suporte do editor.
-Ter um sistema de injeção de dependência poderoso. Achar um jeito de minimizar repetição de código.
+Ter um sistema de injeção de dependência poderoso. Encontrar um jeito de minimizar repetição de código.
///
-### Sanic
+### Sanic { #sanic }
-Ele foi um dos primeiros frameworks Python extremamente rápido baseado em `asyncio`. Ele foi feito para ser muito similar ao Flask.
+Ele foi um dos primeiros frameworks Python extremamente rápidos baseados em `asyncio`. Ele foi feito para ser muito similar ao Flask.
-/// note | Detalhes técnicos
+/// note | Detalhes Técnicos
-Ele utiliza `uvloop` ao invés do '_loop_' `asyncio` padrão do Python. É isso que deixa ele tão rápido.
+Ele utilizava `uvloop` em vez do loop `asyncio` padrão do Python. É isso que o deixava tão rápido.
-Ele claramente inspirou Uvicorn e Starlette, que são atualmente mais rápidos que o Sanic em testes de performance abertos.
+Ele claramente inspirou Uvicorn e Starlette, que atualmente são mais rápidos que o Sanic em benchmarks abertos.
///
/// check | **FastAPI** inspirado para
-Achar um jeito de ter uma performance insana.
+Encontrar um jeito de ter uma performance insana.
-É por isso que o **FastAPI** é baseado em Starlette, para que ele seja o framework mais rápido disponível (performance testada por terceiros).
+É por isso que o **FastAPI** é baseado em Starlette, pois ela é o framework mais rápido disponível (testado por benchmarks de terceiros).
///
-### Falcon
+### Falcon { #falcon }
-Falcon é outro framework Python de alta performance, e é projetado para ser minimalista, e funciona como fundação de outros frameworks como Hug.
+Falcon é outro framework Python de alta performance, projetado para ser minimalista, e servir como base para outros frameworks como Hug.
-Ele usa o padrão anterior para frameworks web Python (WSGI) que é síncrono, então ele não pode controlar _WebSockets_ e outros casos de uso. No entanto, ele também tem uma boa performance.
+Ele é projetado para ter funções que recebem dois parâmetros, uma "request" e uma "response". Então você "lê" partes da requisição, e "escreve" partes para a resposta. Por causa desse design, não é possível declarar parâmetros de requisição e corpos com as anotações de tipo padrão do Python como parâmetros de função.
-Ele é projetado para ter funções que recebem dois parâmetros, uma "requisição" e uma "resposta". Então você "lê" as partes da requisição, e "escreve" partes para a resposta. Devido ao seu design, não é possível declarar parâmetros de requisição e corpos com _type hints_ Python padrão como parâmetros de funções.
-
-Então, validação de dados, serialização e documentação tem que ser feitos no código, não automaticamente. Ou eles terão que ser implementados como um framework acima do Falcon, como o Hug. Essa mesma distinção acontece em outros frameworks que são inspirados pelo design do Falcon, tendo um objeto de requisição e um objeto de resposta como parâmetros.
+Então, validação de dados, serialização e documentação têm que ser feitos no código, não automaticamente. Ou eles têm que ser implementados como um framework acima do Falcon, como o Hug. Essa mesma distinção acontece em outros frameworks inspirados pelo design do Falcon, de ter um objeto de request e um objeto de response como parâmetros.
/// check | **FastAPI** inspirado para
-Achar jeitos de conseguir melhor performance.
+Encontrar maneiras de obter uma ótima performance.
-Juntamente com Hug (como Hug é baseado no Falcon) inspirou **FastAPI** para declarar um parâmetro de `resposta` nas funções.
+Juntamente com Hug (como Hug é baseado no Falcon) inspirou **FastAPI** a declarar um parâmetro de `response` nas funções.
Embora no FastAPI seja opcional, é utilizado principalmente para configurar cabeçalhos, cookies e códigos de status alternativos.
///
-### Molten
+### Molten { #molten }
-Eu descobri Molten nos primeiros estágios da construção do **FastAPI**. E ele tem umas idéias bem similares:
+Eu descobri Molten nos primeiros estágios da construção do **FastAPI**. E ele tem ideias bastante similares:
-* Baseado em _type hints_ Python.
-* Validação e documentação desses tipos.
-* Sistema de injeção de dependência.
+* Baseado nas anotações de tipo do Python.
+* Validação e documentação a partir desses tipos.
+* Sistema de Injeção de Dependência.
-Ele não utiliza validação de dados, seriallização e documentação de bibliotecas de terceiros como o Pydantic, ele tem seu prórpio. Então, essas definições de tipo de dados não podem ser reutilizados tão facilmente.
+Ele não utiliza uma biblioteca de terceiros para validação de dados, serialização e documentação como o Pydantic, ele tem a sua própria. Então, essas definições de tipos de dados não seriam reutilizáveis tão facilmente.
-Ele exige um pouco mais de verbosidade nas configurações. E como é baseado no WSGI (ao invés de ASGI), ele não é projetado para ter a vantagem da alta performance fornecida por ferramentas como Uvicorn, Starlette e Sanic.
+Ele exige configurações um pouco mais verbosas. E como é baseado em WSGI (em vez de ASGI), ele não é projetado para tirar vantagem da alta performance fornecida por ferramentas como Uvicorn, Starlette e Sanic.
-O sistema de injeção de dependência exige pré-registro das dependências e as dependências são resolvidas baseadas nos tipos declarados. Então, não é possível declarar mais do que um "componente" que fornece um certo tipo.
+O sistema de injeção de dependência exige pré-registro das dependências e elas são resolvidas com base nos tipos declarados. Então, não é possível declarar mais de um "componente" que forneça um certo tipo.
-Rotas são declaradas em um único lugar, usando funções declaradas em outros lugares (ao invés de usar decoradores que possam ser colocados diretamente acima da função que controla o _endpoint_). Isso é mais perto de como o Django faz isso do que como Flask (e Starlette) faz. Ele separa no código coisas que são relativamente amarradas.
+As rotas são declaradas em um único lugar, usando funções declaradas em outros lugares (em vez de usar decorators que possam ser colocados diretamente acima da função que lida com o endpoint). Isso é mais próximo de como o Django faz do que de como o Flask (e o Starlette) fazem. Separa no código coisas que são relativamente bem acopladas.
/// check | **FastAPI** inspirado para
-Definir validações extras para tipos de dados usando valores "padrão" de atributos dos modelos. Isso melhora o suporte do editor, e não estava disponível no Pydantic antes.
+Definir validações extras para tipos de dados usando o valor "padrão" de atributos dos modelos. Isso melhora o suporte do editor, e não estava disponível no Pydantic antes.
Isso na verdade inspirou a atualização de partes do Pydantic, para dar suporte ao mesmo estilo de declaração da validação (toda essa funcionalidade já está disponível no Pydantic).
///
-### Hug
+### Hug { #hug }
-Hug foi um dos primeiros frameworks a implementar a declaração de tipos de parâmetros usando Python _type hints_. Isso foi uma ótima idéia que inspirou outras ferramentas a fazer o mesmo.
+Hug foi um dos primeiros frameworks a implementar a declaração de tipos de parâmetros de API usando anotações de tipo do Python. Isso foi uma ótima ideia que inspirou outras ferramentas a fazer o mesmo.
-Ele usou tipos customizados em suas declarações ao invés dos tipos padrão Python, mas mesmo assim foi um grande passo.
+Ele usou tipos personalizados em suas declarações em vez dos tipos padrão do Python, mas mesmo assim foi um grande passo adiante.
-Ele também foi um dos primeiros frameworks a gerar um _schema_ customizado declarando a API inteira em JSON.
+Ele também foi um dos primeiros frameworks a gerar um schema personalizado declarando a API inteira em JSON.
-Ele não era baseado em um padrão como OpenAPI e JSON Schema. Então não poderia ter interação direta com outras ferramentas, como Swagger UI. Mas novamente, era uma idéia muito inovadora.
+Ele não era baseado em um padrão como OpenAPI e JSON Schema. Então não seria simples integrá-lo com outras ferramentas, como Swagger UI. Mas novamente, era uma ideia muito inovadora.
-Hug tinha um incomum, interessante recurso: usando o mesmo framework, é possível criar tanto APIs como CLIs.
+Ele tem um recurso interessante e incomum: usando o mesmo framework, é possível criar APIs e também CLIs.
-Como é baseado nos padrões anteriores de frameworks web síncronos (WSGI), ele não pode controlar _Websockets_ e outras coisas, embora ele ainda tenha uma alta performance também.
+Como é baseado no padrão anterior para frameworks web Python síncronos (WSGI), ele não consegue lidar com Websockets e outras coisas, embora ainda tenha alta performance também.
-/// info
+/// info | Informação
-Hug foi criado por Timothy Crosley, o mesmo criador do `isort`, uma grande ferramenta para ordenação automática de _imports_ em arquivos Python.
+Hug foi criado por Timothy Crosley, o mesmo criador do `isort`, uma ótima ferramenta para ordenar automaticamente imports em arquivos Python.
///
-/// check | Idéias inspiradas para o **FastAPI**
+/// check | Ideias que inspiraram o **FastAPI**
-Hug inspirou partes do APIStar, e foi uma das ferramentas que eu achei mais promissora, ao lado do APIStar.
+Hug inspirou partes do APIStar, e foi uma das ferramentas que achei mais promissoras, ao lado do APIStar.
-Hug ajudou a inspirar o **FastAPI** a usar _type hints_ do Python para declarar parâmetros, e para gerar um _schema_ definindo a API automaticamente.
+Hug ajudou a inspirar o **FastAPI** a usar anotações de tipo do Python para declarar parâmetros e para gerar um schema definindo a API automaticamente.
-Hug inspirou **FastAPI** a declarar um parâmetro de `resposta` em funções para definir cabeçalhos e cookies.
+Hug inspirou **FastAPI** a declarar um parâmetro de `response` em funções para definir cabeçalhos e cookies.
///
-### APIStar (<= 0.5)
+### APIStar (<= 0.5) { #apistar-0-5 }
-Antes de decidir construir **FastAPI** eu encontrei o servidor **APIStar**. Tinha quase tudo que eu estava procurando e tinha um grande projeto.
+Pouco antes de decidir construir o **FastAPI** eu encontrei o servidor **APIStar**. Ele tinha quase tudo o que eu estava procurando e tinha um ótimo design.
-Ele foi uma das primeiras implementações de um framework usando Python _type hints_ para declarar parâmetros e requisições que eu nunca vi (antes no NestJS e Molten). Eu encontrei ele mais ou menos na mesma época que o Hug. Mas o APIStar utilizava o padrão OpenAPI.
+Foi uma das primeiras implementações de um framework usando anotações de tipo do Python para declarar parâmetros e requisições que eu já vi (antes do NestJS e Molten). Eu o encontrei mais ou menos na mesma época que o Hug. Mas o APIStar utilizava o padrão OpenAPI.
-Ele tinha validação de dados automática, serialização de dados e geração de _schema_ OpenAPI baseado nos mesmos _type hints_ em vários locais.
+Ele tinha validação de dados automática, serialização de dados e geração de schema OpenAPI baseadas nas mesmas anotações de tipo em vários locais.
-Definições de _schema_ de corpo não utilizavam os mesmos Python _type hints_ como Pydantic, ele era um pouco mais similar ao Marshmallow, então, o suporte ao editor não seria tão bom, ainda assim, APIStar era a melhor opção disponível.
+As definições de schema de corpo não utilizavam as mesmas anotações de tipo do Python como o Pydantic, eram um pouco mais similares ao Marshmallow, então o suporte do editor não seria tão bom, ainda assim, APIStar era a melhor opção disponível.
-Ele obteve as melhores performances em testes na época (somente batido por Starlette).
+Ele obteve os melhores benchmarks de performance na época (somente ultrapassado por Starlette).
A princípio, ele não tinha uma interface web com documentação automática da API, mas eu sabia que poderia adicionar o Swagger UI a ele.
-Ele tinha um sistema de injeção de dependência. Ele exigia pré-registro dos componentes, como outras ferramentas já discutidas acima. Mas ainda era um grande recurso.
+Ele tinha um sistema de injeção de dependência. Exigia pré-registro dos componentes, como outras ferramentas já discutidas acima. Mas ainda assim era um grande recurso.
-Eu nunca fui capaz de usar ele num projeto inteiro, por não ter integração de segurança, então, eu não pude substituir todos os recursos que eu tinha com os geradores _full-stack_ baseados no Flask-apispec. Eu tive em minha gaveta de projetos a idéia de criar um _pull request_ adicionando essa funcionalidade.
+Eu nunca fui capaz de usá-lo em um projeto completo, pois ele não tinha integração de segurança, então, eu não pude substituir todos os recursos que eu tinha com os geradores full-stack baseados no Flask-apispec. Eu tinha no meu backlog de projetos criar um pull request adicionando essa funcionalidade.
Mas então, o foco do projeto mudou.
-Ele não era mais um framework web API, como o criador precisava focar no Starlette.
+Ele não era mais um framework web de API, pois o criador precisava focar no Starlette.
Agora APIStar é um conjunto de ferramentas para validar especificações OpenAPI, não um framework web.
-/// info
+/// info | Informação
APIStar foi criado por Tom Christie. O mesmo cara que criou:
@@ -391,98 +389,97 @@ APIStar foi criado por Tom Christie. O mesmo cara que criou:
Existir.
-A idéia de declarar múltiplas coisas (validação de dados, serialização e documentação) com os mesmos tipos Python, que ao mesmo tempo fornecesse grande suporte ao editor, era algo que eu considerava uma brilhante idéia.
+A ideia de declarar múltiplas coisas (validação de dados, serialização e documentação) com os mesmos tipos do Python, que ao mesmo tempo fornecessem grande suporte ao editor, era algo que eu considerava uma ideia brilhante.
-E após procurar por um logo tempo por um framework similar e testar muitas alternativas diferentes, APIStar foi a melhor opção disponível.
+E após procurar por muito tempo por um framework similar e testar muitas alternativas diferentes, APIStar foi a melhor opção disponível.
-Então APIStar parou de existir como um servidor e Starlette foi criado, e foi uma nova melhor fundação para tal sistema. Essa foi a inspiração final para construir **FastAPI**.
+Então APIStar deixou de existir como servidor e o Starlette foi criado, sendo uma nova e melhor fundação para tal sistema. Essa foi a inspiração final para construir o **FastAPI**.
-Eu considero **FastAPI** um "sucessor espiritual" para o APIStar, evoluindo e melhorando os recursos, sistema de tipagem e outras partes, baseado na aprendizagem de todas essas ferramentas acima.
+Eu considero o **FastAPI** um "sucessor espiritual" do APIStar, enquanto aprimora e amplia as funcionalidades, o sistema de tipagem e outras partes, baseado nos aprendizados de todas essas ferramentas anteriores.
///
-## Usados por **FastAPI**
+## Usados por **FastAPI** { #used-by-fastapi }
-### Pydantic
+### Pydantic { #pydantic }
-Pydantic é uma biblioteca para definir validação de dados, serialização e documentação (usando JSON Schema) baseado nos Python _type hints_.
+Pydantic é uma biblioteca para definir validação de dados, serialização e documentação (usando JSON Schema) com base nas anotações de tipo do Python.
-Isso faz dele extremamente intuitivo.
+Isso o torna extremamente intuitivo.
-Ele é comparável ao Marshmallow. Embora ele seja mais rápido que Marshmallow em testes de performance. E ele é baseado nos mesmos Python _type hints_, o suporte ao editor é ótimo.
+Ele é comparável ao Marshmallow. Embora seja mais rápido que o Marshmallow em benchmarks. E como é baseado nas mesmas anotações de tipo do Python, o suporte do editor é ótimo.
/// check | **FastAPI** usa isso para
-Controlar toda a validação de dados, serialização de dados e modelo de documentação automática (baseado no JSON Schema).
+Controlar toda a validação de dados, serialização de dados e documentação automática de modelos (baseada no JSON Schema).
-**FastAPI** então pega dados do JSON Schema e coloca eles no OpenAPI, à parte de todas as outras coisas que ele faz.
+**FastAPI** então pega esses dados do JSON Schema e os coloca no OpenAPI, além de todas as outras coisas que faz.
///
-### Starlette
+### Starlette { #starlette }
-Starlette é um framework/caixa de ferramentas ASGI peso leve, o que é ideal para construir serviços assíncronos de alta performance.
+Starlette é um framework/caixa de ferramentas ASGI leve, o que é ideal para construir serviços asyncio de alta performance.
-Ele é muito simples e intuitivo. É projetado para ser extensível facilmente, e ter componentes modulares.
+Ele é muito simples e intuitivo. É projetado para ser facilmente extensível, e ter componentes modulares.
Ele tem:
* Performance seriamente impressionante.
* Suporte a WebSocket.
-* Suporte a GraphQL.
-* Tarefas de processamento interno por trás dos panos.
+* Tarefas em segundo plano dentro do processo.
* Eventos de inicialização e encerramento.
* Cliente de testes construído com HTTPX.
-* Respostas CORS, GZip, Arquivos Estáticos, Streaming.
+* CORS, GZip, Arquivos Estáticos, respostas Streaming.
* Suporte para Sessão e Cookie.
* 100% coberto por testes.
* Código base 100% anotado com tipagem.
-* Dependências complexas Zero.
+* Poucas dependências obrigatórias.
-Starlette é atualmente o mais rápido framework Python testado. Somente ultrapassado pelo Uvicorn, que não é um framework, mas um servidor.
+Starlette é atualmente o framework Python mais rápido testado. Somente ultrapassado pelo Uvicorn, que não é um framework, mas um servidor.
Starlette fornece toda a funcionalidade básica de um microframework web.
-Mas ele não fornece validação de dados automática, serialização e documentação.
+Mas ele não fornece validação de dados automática, serialização ou documentação.
-Essa é uma das principais coisas que **FastAPI** adiciona no topo, tudo baseado em Python _type hints_ (usando Pydantic). Isso, mais o sistema de injeção de dependência, utilidades de segurança, geração de _schema_ OpenAPI, etc.
+Essa é uma das principais coisas que o **FastAPI** adiciona por cima, tudo baseado nas anotações de tipo do Python (usando Pydantic). Isso, mais o sistema de injeção de dependência, utilidades de segurança, geração de schema OpenAPI, etc.
/// note | Detalhes Técnicos
-ASGI é um novo "padrão" sendo desenvolvido pelos membros do time central do Django. Ele ainda não está como "Padrão Python" (PEP), embora eles estejam em processo de fazer isso.
+ASGI é um novo "padrão" sendo desenvolvido por membros do time central do Django. Ele ainda não é um "padrão Python" (uma PEP), embora eles estejam no processo de fazer isso.
-No entanto, ele já está sendo utilizado como "padrão" por diversas ferramentas. Isso melhora enormemente a interoperabilidade, como você poderia trocar Uvicorn por qualquer outro servidor ASGI (como Daphne ou Hypercorn), ou você poderia adicionar ferramentas compatíveis com ASGI, como `python-socketio`.
+No entanto, ele já está sendo utilizado como "padrão" por diversas ferramentas. Isso melhora enormemente a interoperabilidade, pois você poderia trocar Uvicorn por qualquer outro servidor ASGI (como Daphne ou Hypercorn), ou você poderia adicionar ferramentas compatíveis com ASGI, como `python-socketio`.
///
/// check | **FastAPI** usa isso para
-Controlar todas as partes web centrais. Adiciona recursos no topo.
+Controlar todas as partes web centrais. Adiciona funcionalidades por cima.
-A classe `FastAPI` em si herda `Starlette`.
+A classe `FastAPI` em si herda diretamente da classe `Starlette`.
-Então, qualquer coisa que você faz com Starlette, você pode fazer diretamente com **FastAPI**, pois ele é basicamente um Starlette com esteróides.
+Então, qualquer coisa que você pode fazer com Starlette, você pode fazer diretamente com o **FastAPI**, pois ele é basicamente um Starlette com esteróides.
///
-### Uvicorn
+### Uvicorn { #uvicorn }
-Uvicorn é um servidor ASGI peso leve, construído com uvloop e httptools.
+Uvicorn é um servidor ASGI extremamente rápido, construído com uvloop e httptools.
-Ele não é um framework web, mas sim um servidor. Por exemplo, ele não fornece ferramentas para roteamento por rotas. Isso é algo que um framework como Starlette (ou **FastAPI**) poderia fornecer por cima.
+Ele não é um framework web, mas sim um servidor. Por exemplo, ele não fornece ferramentas para roteamento por paths. Isso é algo que um framework como Starlette (ou **FastAPI**) forneceria por cima.
Ele é o servidor recomendado para Starlette e **FastAPI**.
-/// check | **FastAPI** recomenda isso para
+/// check | **FastAPI** o recomenda como
O principal servidor web para rodar aplicações **FastAPI**.
-Você pode combinar ele com o Gunicorn, para ter um servidor multi-processos assíncrono.
+Você também pode usar a opção de linha de comando `--workers` para ter um servidor assíncrono multi-processos.
Verifique mais detalhes na seção [Deployment](deployment/index.md){.internal-link target=_blank}.
///
-## Performance e velocidade
+## Benchmarks e velocidade { #benchmarks-and-speed }
Para entender, comparar e ver a diferença entre Uvicorn, Starlette e FastAPI, verifique a seção sobre [Benchmarks](benchmarks.md){.internal-link target=_blank}.
diff --git a/docs/pt/docs/async.md b/docs/pt/docs/async.md
index c70924ea52..f01ff23159 100644
--- a/docs/pt/docs/async.md
+++ b/docs/pt/docs/async.md
@@ -1,10 +1,10 @@
-# Concorrência e async / await
+# Concorrência e async / await { #concurrency-and-async-await }
Detalhes sobre a sintaxe `async def` para *funções de operação de rota* e alguns conceitos de código assíncrono, concorrência e paralelismo.
-## Com pressa?
+## Com pressa? { #in-a-hurry }
-TL;DR:
+TL;DR:
Se você estiver utilizando bibliotecas de terceiros que dizem para você chamar as funções com `await`, como:
@@ -12,7 +12,7 @@ Se você estiver utilizando bibliotecas de terceiros que dizem para você chamar
results = await some_library()
```
-Então, declare sua *função de operação de rota* com `async def` como:
+Então, declare suas *funções de operação de rota* com `async def` como:
```Python hl_lines="2"
@app.get('/')
@@ -21,7 +21,7 @@ async def read_results():
return results
```
-/// note
+/// note | Nota
Você só pode usar `await` dentro de funções criadas com `async def`.
@@ -29,7 +29,7 @@ Você só pode usar `await` dentro de funções criadas com `async def`.
---
-Se você está usando biblioteca de terceiros que se comunica com alguma coisa (um banco de dados, uma API, sistema de arquivos etc) e não tem suporte para utilizar `await` (esse é atualmente o caso para a maioria das bibliotecas de banco de dados), então declare suas *funções de operação de rota* normalmente, com apenas `def`, como:
+Se você está usando uma biblioteca de terceiros que se comunica com alguma coisa (um banco de dados, uma API, o sistema de arquivos etc.) e não tem suporte para utilizar `await` (esse é atualmente o caso para a maioria das bibliotecas de banco de dados), então declare suas *funções de operação de rota* normalmente, com apenas `def`, como:
```Python hl_lines="2"
@app.get('/')
@@ -40,7 +40,7 @@ def results():
---
-Se sua aplicação (de alguma forma) não tem que se comunicar com nada mais e esperar que o respondam, use `async def`.
+Se sua aplicação (de alguma forma) não tem que se comunicar com nada mais e esperar que o respondam, use `async def`, mesmo que você não precise usar `await` dentro dela.
---
@@ -54,17 +54,17 @@ De qualquer forma, em ambos os casos acima, FastAPI irá trabalhar assincronamen
Mas, seguindo os passos acima, ele será capaz de fazer algumas otimizações de performance.
-## Detalhes Técnicos
+## Detalhes Técnicos { #technical-details }
-Versões modernas de Python tem suporte para **"código assíncrono"** usando algo chamado **"corrotinas"**, com sintaxe **`async` e `await`**.
+Versões modernas de Python têm suporte para **"código assíncrono"** usando algo chamado **"corrotinas"**, com sintaxe **`async` e `await`**.
-Vamos ver aquela frase por partes na seção abaixo:
+Vamos ver aquela frase por partes nas seções abaixo:
* **Código assíncrono**
* **`async` e `await`**
* **Corrotinas**
-## Código assíncrono
+## Código assíncrono { #asynchronous-code }
Código assíncrono apenas significa que a linguagem 💬 tem um jeito de dizer para o computador / programa 🤖 que em certo ponto do código, ele 🤖 terá que esperar *algo* finalizar em outro lugar. Vamos dizer que esse *algo* seja chamado "arquivo lento" 📝.
@@ -74,10 +74,10 @@ Então o computador / programa 🤖 irá voltar sempre que tiver uma chance, sej
Depois, ele 🤖 pega a primeira tarefa para finalizar (vamos dizer, nosso "arquivo lento" 📝) e continua o que tem que fazer com ela.
-Esse "esperar por algo" normalmente se refere a operações I/O que são relativamente "lentas" (comparadas à velocidade do processador e da memória RAM), como esperar por:
+Esse "esperar por algo" normalmente se refere a operações I/O que são relativamente "lentas" (comparadas à velocidade do processador e da memória RAM), como esperar por:
* dados do cliente para serem enviados através da rede
-* dados enviados pelo seu programa serem recebidos pelo clente através da rede
+* dados enviados pelo seu programa serem recebidos pelo cliente através da rede
* conteúdo de um arquivo no disco ser lido pelo sistema e entregue ao seu programa
* conteúdo que seu programa deu ao sistema para ser escrito no disco
* uma operação em uma API remota
@@ -85,7 +85,7 @@ Esse "esperar por algo" normalmente se refere a operações I/O, essas operações são chamadas operações "limitadas por I/O".
+Quanto o tempo de execução é consumido majoritariamente pela espera de operações I/O, essas operações são chamadas operações "limitadas por I/O".
Isso é chamado de "assíncrono" porque o computador / programa não tem que ser "sincronizado" com a tarefa lenta, esperando pelo momento exato em que a tarefa finaliza, enquanto não faz nada, para ser capaz de pegar o resultado da tarefa e dar continuidade ao trabalho.
@@ -93,9 +93,9 @@ Ao invés disso, sendo um sistema "assíncrono", uma vez finalizada, a tarefa po
Para "síncrono" (contrário de "assíncrono") também é utilizado o termo "sequencial", porquê o computador / programa segue todos os passos, em sequência, antes de trocar para uma tarefa diferente, mesmo se alguns passos envolvam esperar.
-### Concorrência e hambúrgueres
+### Concorrência e hambúrgueres { #concurrency-and-burgers }
-Essa idéia de código **assíncrono** descrita acima é às vezes chamado de **"concorrência"**. Isso é diferente de **"paralelismo"**.
+Essa ideia de código **assíncrono** descrita acima é às vezes chamada de **"concorrência"**. Isso é diferente de **"paralelismo"**.
**Concorrência** e **paralelismo** ambos são relacionados a "diferentes coisas acontecendo mais ou menos ao mesmo tempo".
@@ -103,31 +103,51 @@ Mas os detalhes entre *concorrência* e *paralelismo* são bem diferentes.
Para ver essa diferença, imagine a seguinte história sobre hambúrgueres:
-### Hambúrgueres concorrentes
+### Hambúrgueres concorrentes { #concurrent-burgers }
Você vai com seu _crush_ na lanchonete, e fica na fila enquanto o caixa pega os pedidos das pessoas na sua frente. 😍
+
+
Então chega a sua vez, você pede dois saborosos hambúrgueres para você e seu _crush_. 🍔🍔
-O caixa diz alguma coisa para o cozinheiro na cozinha para que eles saivam que têm que preparar seus hambúrgueres (mesmo que ele esteja atualmente preparando os lanches dos outros clientes).
+
+
+O caixa diz alguma coisa para o cozinheiro na cozinha para que eles saibam que têm que preparar seus hambúrgueres (mesmo que ele esteja atualmente preparando os lanches dos outros clientes).
+
+
Você paga. 💸
O caixa te entrega seu número de chamada.
+
+
Enquanto você espera, você vai com seu _crush_ e pega uma mesa, senta e conversa com seu _crush_ por um bom tempo (já que seus hambúrgueres são muito saborosos, e leva um tempo para serem preparados).
Já que você está sentado na mesa com seu _crush_, esperando os hambúrgueres, você pode passar esse tempo admirando o quão lindo, maravilhoso e esperto é seu _crush_ ✨😍✨.
+
+
Enquanto espera e conversa com seu _crush_, de tempos em tempos, você verifica o número da chamada exibido no balcão para ver se já é sua vez.
Então em algum momento, é finalmente sua vez. Você vai ao balcão, pega seus hambúrgueres e volta para a mesa.
+
+
Você e seu _crush_ comem os hambúrgueres e aproveitam o tempo. ✨
+
+
+/// info | Informação
+
+Belas ilustrações de Ketrina Thompson. 🎨
+
+///
+
---
-Imagine que você seja o computador / programa nessa história.
+Imagine que você seja o computador / programa 🤖 nessa história.
Enquanto você está na fila, você está somente ocioso 😴, esperando por sua vez, sem fazer nada muito "produtivo". Mas a fila é rápida porque o caixa só está pegando os pedidos (não os preparando), então está tudo bem.
@@ -139,13 +159,13 @@ Contudo, à medida que você se afasta do balcão e senta na mesa, com um númer
Então o caixa 💁 diz que "seus hambúrgueres estão prontos" colocando seu número no balcão, mas você não corre que nem um maluco imediatamente quando o número exibido é o seu. Você sabe que ninguém irá roubar seus hambúrgueres porque você tem o seu número da chamada, e os outros têm os deles.
-Então você espera seu _crush_ terminar a história que estava contando (terminar o trabalho atual ⏯ / tarefa sendo processada 🤓), sorri gentilmente e diz que você está indo buscar os hambúrgueres.
+Então você espera seu _crush_ terminar a história que estava contando (terminar o trabalho atual ⏯ / tarefa sendo processada 🤓), sorri gentilmente e diz que você está indo buscar os hambúrgueres ⏸.
-Então você vai ao balcão 🔀, para a tarefa inicial que agora está finalizada⏯, pega os hambúrgueres, agradece, e leva-os para a mesa. Isso finaliza esse passo / tarefa da interação com o balcão ⏹. Isso, por sua vez, cria uma nova tarefa, a de "comer hambúrgueres" 🔀 ⏯, mas a tarefa anterior de "pegar os hambúrgueres" já está finalizada ⏹.
+Então você vai ao balcão 🔀, para a tarefa inicial que agora está finalizada ⏯, pega os hambúrgueres, agradece, e leva-os para a mesa. Isso finaliza esse passo / tarefa da interação com o balcão ⏹. Isso, por sua vez, cria uma nova tarefa, a de "comer hambúrgueres" 🔀 ⏯, mas a tarefa anterior de "pegar os hambúrgueres" já está finalizada ⏹.
-### Hambúrgueres paralelos
+### Hambúrgueres paralelos { #parallel-burgers }
-Agora vamos imaginar que esses não são "Hambúrgueres Concorrentes", e sim "Hambúrgueres Paralelos"
+Agora vamos imaginar que esses não são "Hambúrgueres Concorrentes", e sim "Hambúrgueres Paralelos".
Você vai com seu _crush_ na lanchonete paralela.
@@ -153,29 +173,47 @@ Você fica na fila enquanto vários (vamos dizer 8) caixas que também são cozi
Todo mundo na sua frente está esperando seus hambúrgueres ficarem prontos antes de deixar o caixa porque cada um dos 8 caixas vai e prepara o hambúrguer logo após receber o pedido, antes de pegar o próximo pedido.
+
+
Então é finalmente sua vez, você pede 2 hambúrgueres muito saborosos para você e seu _crush_.
Você paga 💸.
+
+
O caixa vai para a cozinha.
Você espera, na frente do balcão 🕙, para que ninguém pegue seus hambúrgueres antes de você, já que não tem números de chamadas.
+
+
Como você e seu _crush_ estão ocupados não permitindo que ninguém passe na frente e pegue seus hambúrgueres assim que estiverem prontos, você não pode dar atenção ao seu _crush_. 😞
-Isso é trabalho "síncrono", você está "sincronizado" com o caixa / cozinheiro👨🍳. Você tem que esperar 🕙 e estar lá no exato momento que o caixa / cozinheiro 👨🍳 terminar os hambúrgueres e os der a você, ou então, outro alguém pode pegá-los.
+Isso é trabalho "síncrono", você está "sincronizado" com o caixa / cozinheiro 👨🍳. Você tem que esperar 🕙 e estar lá no exato momento que o caixa / cozinheiro 👨🍳 terminar os hambúrgueres e os der a você, ou então, outro alguém pode pegá-los.
+
+
Então seu caixa / cozinheiro 👨🍳 finalmente volta com seus hambúrgueres, depois de um longo tempo esperando 🕙 por eles em frente ao balcão.
+
+
Você pega seus hambúrgueres e vai para a mesa com seu _crush_.
Vocês comem os hambúrgueres, e o trabalho está terminado. ⏹
+
+
Não houve muita conversa ou flerte já que a maior parte do tempo foi gasto esperando 🕙 na frente do balcão. 😞
+/// info | Informação
+
+Belas ilustrações de Ketrina Thompson. 🎨
+
+///
+
---
-Nesse cenário dos hambúrgueres paralelos, você é um computador / programa com dois processadores (você e seu _crush_), ambos esperando 🕙 e dedicando sua atenção ⏯ "esperando no balcão" 🕙 por um bom tempo.
+Nesse cenário dos hambúrgueres paralelos, você é um computador / programa 🤖 com dois processadores (você e seu _crush_), ambos esperando 🕙 e dedicando sua atenção ⏯ "esperando no balcão" 🕙 por um bom tempo.
A lanchonete paralela tem 8 processadores (caixas / cozinheiros), enquanto a lanchonete dos hambúrgueres concorrentes tinha apenas 2 (um caixa e um cozinheiro).
@@ -183,7 +221,7 @@ Ainda assim, a experiência final não foi a melhor. 😞
---
-Essa seria o equivalente paralelo à histório dos hambúrgueres. 🍔
+Essa seria o equivalente paralelo à história dos hambúrgueres. 🍔
Para um exemplo "mais real", imagine um banco.
@@ -195,7 +233,7 @@ E você tinha que esperar 🕙 na fila por um longo tempo ou poderia perder a ve
Você provavelmente não gostaria de levar seu _crush_ 😍 com você para um rolezinho no banco 🏦.
-### Conclusão dos hambúrgueres
+### Conclusão dos hambúrgueres { #burger-conclusion }
Nesse cenário dos "hambúrgueres com seu _crush_", como tem muita espera, faz mais sentido ter um sistema concorrente ⏸🔀⏯.
@@ -215,7 +253,7 @@ E esse é o mesmo nível de performance que você tem com o **FastAPI**.
E como você pode ter paralelismo e assincronicidade ao mesmo tempo, você tem uma maior performance do que a maioria dos frameworks NodeJS testados e lado a lado com Go, que é uma linguagem compilada, mais próxima ao C (tudo graças ao Starlette).
-### Concorrência é melhor que paralelismo?
+### Concorrência é melhor que paralelismo? { #is-concurrency-better-than-parallelism }
Não! Essa não é a moral da história.
@@ -239,7 +277,7 @@ Mas nesse caso, se você trouxesse os 8 ex-caixas / cozinheiros / agora-faxineir
Nesse cenário, cada um dos faxineiros (incluindo você) poderia ser um processador, fazendo a sua parte do trabalho.
-E a maior parte do tempo de execução é tomada por trabalho real (ao invés de ficar esperando), e o trabalho em um computador é feito pela CPU. Eles chamam esses problemas de "limitados por CPU".
+E a maior parte do tempo de execução é tomada por trabalho real (ao invés de ficar esperando), e o trabalho em um computador é feito pela CPU. Eles chamam esses problemas de "limitados por CPU".
---
@@ -249,22 +287,20 @@ Por exemplo:
* **Processamento de áudio** ou **imagem**
* **Visão Computacional**: uma imagem é composta por milhões de pixels, cada pixel tem 3 valores / cores, processar isso normalmente exige alguma computação em todos esses pixels ao mesmo tempo
+* **Aprendizado de Máquina**: Normalmente exige muita multiplicação de matrizes e vetores. Pense numa grande planilha com números e em multiplicar todos eles juntos e ao mesmo tempo.
+* **Deep Learning**: Esse é um subcampo do Aprendizado de Máquina, então, o mesmo se aplica. A diferença é que não há apenas uma grande planilha com números para multiplicar, mas um grande conjunto delas, e em muitos casos, você utiliza um processador especial para construir e/ou usar esses modelos.
-* **Machine Learning**: Normalmente exige muita multiplicação de matrizes e vetores. Pense numa grande planilha com números e em multiplicar todos eles juntos e ao mesmo tempo.
-
-* **Deep Learning**: Esse é um subcampo do Machine Learning, então, o mesmo se aplica. A diferença é que não há apenas uma grande planilha com números para multiplicar, mas um grande conjunto delas, e em muitos casos, você utiliza um processador especial para construir e/ou usar esses modelos.
-
-### Concorrência + Paralelismo: Web + Machine learning
+### Concorrência + Paralelismo: Web + Aprendizado de Máquina { #concurrency-parallelism-web-machine-learning }
Com **FastAPI** você pode levar a vantagem da concorrência que é muito comum para desenvolvimento web (o mesmo atrativo de NodeJS).
-Mas você também pode explorar os benefícios do paralelismo e multiprocessamento (tendo múltiplos processadores rodando em paralelo) para trabalhos **limitados por CPU** como aqueles em sistemas de Machine Learning.
+Mas você também pode explorar os benefícios do paralelismo e multiprocessamento (tendo múltiplos processadores rodando em paralelo) para trabalhos **limitados por CPU** como aqueles em sistemas de Aprendizado de Máquina.
-Isso, somado ao simples fato que Python é a principal linguagem para **Data Science**, Machine Learning e especialmente Deep Learning, faz do FastAPI uma ótima escolha para APIs web e aplicações com Data Science / Machine Learning (entre muitas outras).
+Isso, somado ao simples fato que Python é a principal linguagem para **Data Science**, Aprendizado de Máquina e especialmente Deep Learning, faz do FastAPI uma ótima escolha para APIs web e aplicações com Data Science / Aprendizado de Máquina (entre muitas outras).
-Para ver como alcançar esse paralelismo em produção veja a seção sobre [Deployment](deployment/index.md){.internal-link target=_blank}.
+Para ver como alcançar esse paralelismo em produção veja a seção sobre [Implantação](deployment/index.md){.internal-link target=_blank}.
-## `async` e `await`
+## `async` e `await` { #async-and-await }
Versões modernas do Python têm um modo muito intuitivo para definir código assíncrono. Isso faz parecer do mesmo jeito do código normal "sequencial" e fazer a "espera" para você nos momentos certos.
@@ -274,7 +310,7 @@ Quando tem uma operação que exigirá espera antes de dar os resultados e tem s
burgers = await get_burgers(2)
```
-A chave aqui é o `await`. Ele diz ao Python que ele tem que esperar por `get_burgers(2)` finalizar suas coisas 🕙 antes de armazenar os resultados em `burgers`. Com isso, o Python saberá que ele pode ir e fazer outras coisas 🔀 ⏯ nesse meio tempo (como receber outra requisição).
+A chave aqui é o `await`. Ele diz ao Python que ele tem que esperar ⏸ por `get_burgers(2)` finalizar suas coisas 🕙 antes de armazenar os resultados em `burgers`. Com isso, o Python saberá que ele pode ir e fazer outras coisas 🔀 ⏯ nesse meio tempo (como receber outra requisição).
Para o `await` funcionar, tem que estar dentro de uma função que suporte essa assincronicidade. Para fazer isso, apenas declare a função com `async def`:
@@ -306,18 +342,18 @@ burgers = get_burgers(2)
Então, se você está usando uma biblioteca que diz que você pode chamá-la com `await`, você precisa criar as *funções de operação de rota* com `async def`, como em:
-```Python hl_lines="2 3"
+```Python hl_lines="2-3"
@app.get('/burgers')
async def read_burgers():
burgers = await get_burgers(2)
return burgers
```
-### Mais detalhes técnicos
+### Mais detalhes técnicos { #more-technical-details }
Você deve ter observado que `await` pode ser usado somente dentro de funções definidas com `async def`.
-Mas ao mesmo tempo, funções definidas com `async def` têm que ser "aguardadas". Então, funções com `async def` pdem ser chamadas somente dentro de funções definidas com `async def` também.
+Mas ao mesmo tempo, funções definidas com `async def` têm que ser "aguardadas". Então, funções com `async def` podem ser chamadas somente dentro de funções definidas com `async def` também.
Então, sobre o ovo e a galinha, como você chama a primeira função async?
@@ -325,7 +361,7 @@ Se você estivar trabalhando com **FastAPI** não terá que se preocupar com iss
Mas se você quiser usar `async` / `await` sem FastAPI, você também pode fazê-lo.
-### Escreva seu próprio código assíncrono
+### Escreva seu próprio código assíncrono { #write-your-own-async-code }
Starlette (e **FastAPI**) são baseados no AnyIO, o que o torna compatível com ambos o asyncio da biblioteca padrão do Python, e o Trio.
@@ -333,10 +369,9 @@ Em particular, você pode usar diretamente o AnyIO por ser altamente compatível e ganhar seus benefícios (e.g. *concorrência estruturada*).
-Eu criei outra biblioteca em cima do AnyIO, como uma fina camada acima, para melhorar um pouco as anotações de tipo e obter melhor **autocompletar**, **erros de linha**, etc. Ela também possui uma introdução amigável e um tutorial para ajudar você a **entender** e escrever **seu próprio código async**: Asyncer. Seria particularmente útil se você precisar **combinar código async com código regular** (bloqueador/síncrono).
+Eu criei outra biblioteca em cima do AnyIO, como uma fina camada acima, para melhorar um pouco as anotações de tipo e obter melhor **preenchimento automático**, **erros inline**, etc. Ela também possui uma introdução amigável e um tutorial para ajudar você a **entender** e escrever **seu próprio código async**: Asyncer. Seria particularmente útil se você precisar **combinar código async com código regular** (bloqueador/síncrono).
-
-### Outras formas de código assíncrono
+### Outras formas de código assíncrono { #other-forms-of-asynchronous-code }
Esse estilo de usar `async` e `await` é relativamente novo na linguagem.
@@ -346,17 +381,17 @@ Essa mesma sintaxe (ou quase a mesma) foi também incluída recentemente em vers
Mas antes disso, controlar código assíncrono era bem mais complexo e difícil.
-Nas versões anteriores do Python, você poderia utilizar threads ou Gevent. Mas o código é bem mais complexo de entender, debugar, e pensar sobre.
+Nas versões anteriores do Python, você poderia utilizar threads ou Gevent. Mas o código é bem mais complexo de entender, debugar, e pensar sobre.
Nas versões anteriores do NodeJS / Navegador JavaScript, você utilizaria "callbacks". O que leva ao "inferno do callback".
-## Corrotinas
+## Corrotinas { #coroutines }
**Corrotina** é apenas um jeito bonitinho para a coisa que é retornada de uma função `async def`. O Python sabe que é algo como uma função, que pode começar e que vai terminar em algum ponto, mas que pode ser pausada ⏸ internamente também, sempre que tiver um `await` dentro dela.
Mas toda essa funcionalidade de código assíncrono com `async` e `await` é muitas vezes resumida como usando "corrotinas". É comparável ao principal recurso chave do Go, a "Gorrotina".
-## Conclusão
+## Conclusão { #conclusion }
Vamos ver a mesma frase de cima:
@@ -366,9 +401,9 @@ Isso pode fazer mais sentido agora. ✨
Tudo isso é o que empodera o FastAPI (através do Starlette) e que o faz ter uma performance tão impressionante.
-## Detalhes muito técnicos
+## Detalhes muito técnicos { #very-technical-details }
-/// warning
+/// warning | Atenção
Você pode provavelmente pular isso.
@@ -378,23 +413,23 @@ Se você tem certo conhecimento técnico (corrotinas, threads, blocking etc) e e
///
-### Funções de operação de rota
+### Funções de operação de rota { #path-operation-functions }
Quando você declara uma *função de operação de rota* com `def` normal ao invés de `async def`, ela é rodada em uma threadpool externa que é então aguardada, ao invés de ser chamada diretamente (já que ela bloquearia o servidor).
-Se você está chegando de outro framework assíncrono que não funciona como descrito acima e você está acostumado a definir *funções de operação de rota* triviais somente de computação com simples `def` para ter um mínimo ganho de performance (cerca de 100 nanosegundos), por favor observe que no **FastAPI** o efeito pode ser bem o oposto. Nesses casos, é melhor usar `async def` a menos que suas *funções de operação de rota* utilizem código que performe bloqueamento IO.
+Se você está chegando de outro framework assíncrono que não funciona como descrito acima e você está acostumado a definir *funções de operação de rota* triviais somente de computação com simples `def` para ter um mínimo ganho de performance (cerca de 100 nanosegundos), por favor observe que no **FastAPI** o efeito pode ser bem o oposto. Nesses casos, é melhor usar `async def` a menos que suas *funções de operação de rota* utilizem código que performe bloqueamento I/O.
Ainda, em ambas as situações, as chances são que o **FastAPI** [ainda será mais rápido](index.md#performance){.internal-link target=_blank} do que (ou ao menos comparável a) seu framework anterior.
-### Dependências
+### Dependências { #dependencies }
O mesmo se aplica para as [dependências](tutorial/dependencies/index.md){.internal-link target=_blank}. Se uma dependência tem as funções com padrão `def` ao invés de `async def`, ela é rodada no threadpool externo.
-### Sub-dependências
+### Sub-dependências { #sub-dependencies }
Você pode ter múltiplas dependências e [sub-dependências](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} requisitando uma à outra (como parâmetros de definições de funções), algumas delas podem ser criadas com `async def` e algumas com `def` normal. Isso ainda funcionaria, e aquelas criadas com `def` normal seriam chamadas em uma thread externa (do threadpool) ao invés de serem "aguardadas".
-### Outras funções de utilidade
+### Outras funções de utilidade { #other-utility-functions }
Qualquer outra função de utilidade que você chame diretamente pode ser criada com `def` normal ou `async def` e o FastAPI não irá afetar o modo como você a chama.
diff --git a/docs/pt/docs/benchmarks.md b/docs/pt/docs/benchmarks.md
index 07461ce463..c0b0c4c466 100644
--- a/docs/pt/docs/benchmarks.md
+++ b/docs/pt/docs/benchmarks.md
@@ -1,10 +1,10 @@
-# Comparações
+# Benchmarks { #benchmarks }
-As comparações independentes da TechEmpower mostram as aplicações **FastAPI** rodando com Uvicorn como um dos _frameworks_ Python mais rápidos disponíveis, somente atrás dos próprios Starlette e Uvicorn (utilizados internamente pelo FastAPI). (*)
+Benchmarks independentes da TechEmpower mostram as aplicações **FastAPI** rodando com Uvicorn como um dos frameworks Python mais rápidos disponíveis, somente atrás dos próprios Starlette e Uvicorn (utilizados internamente pelo FastAPI).
Mas quando se checa _benchmarks_ e comparações você deveria ter o seguinte em mente.
-## Comparações e velocidade
+## Benchmarks e velocidade { #benchmarks-and-speed }
Ao verificar os _benchmarks_, é comum observar algumas ferramentas de diferentes tipos comparadas como equivalentes.
diff --git a/docs/pt/docs/deployment/cloud.md b/docs/pt/docs/deployment/cloud.md
index fc490db4dc..419fd76265 100644
--- a/docs/pt/docs/deployment/cloud.md
+++ b/docs/pt/docs/deployment/cloud.md
@@ -1,13 +1,24 @@
-# Implantar FastAPI em provedores de nuvem
+# Implantar FastAPI em provedores de nuvem { #deploy-fastapi-on-cloud-providers }
Você pode usar praticamente **qualquer provedor de nuvem** para implantar seu aplicativo FastAPI.
-Na maioria dos casos, os principais provedores de nuvem têm guias para implantar o FastAPI com eles.
+Na maioria dos casos, os principais provedores de nuvem têm tutoriais para implantar o FastAPI com eles.
-## Provedores de Nuvem - Patrocinadores
+## FastAPI Cloud { #fastapi-cloud }
-Alguns provedores de nuvem ✨ [**patrocinam o FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, o que garante o **desenvolvimento** contínuo e saudável do FastAPI e seu **ecossistema**.
+**FastAPI Cloud** é desenvolvido pelo mesmo autor e equipe por trás do **FastAPI**.
-E isso mostra seu verdadeiro comprometimento com o FastAPI e sua **comunidade** (você), pois eles não querem apenas fornecer a você um **bom serviço**, mas também querem ter certeza de que você tenha uma **estrutura boa e saudável**, o FastAPI. 🙇
+Ele simplifica o processo de **criar**, **implantar** e **acessar** uma API com o mínimo de esforço.
-Talvez você queira experimentar os serviços deles e seguir os guias.
+Traz a mesma **experiência do desenvolvedor** de criar aplicações com FastAPI para **implantá-las** na nuvem. 🎉
+
+FastAPI Cloud é o patrocinador principal e provedor de financiamento dos projetos de código aberto *FastAPI and friends*. ✨
+
+## Provedores de Nuvem - Patrocinadores { #cloud-providers-sponsors }
+
+Alguns outros provedores de nuvem ✨ [**patrocinam o FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ também. 🙇
+
+Você também pode considerá-los para seguir seus tutoriais e experimentar seus serviços:
+
+* Render
+* Railway
diff --git a/docs/pt/docs/deployment/concepts.md b/docs/pt/docs/deployment/concepts.md
index 014ca3797a..6af4b177a3 100644
--- a/docs/pt/docs/deployment/concepts.md
+++ b/docs/pt/docs/deployment/concepts.md
@@ -1,4 +1,4 @@
-# Conceitos de Implantações
+# Conceitos de Implantações { #deployments-concepts }
Ao implantar um aplicativo **FastAPI**, ou na verdade, qualquer tipo de API da web, há vários conceitos com os quais você provavelmente se importa e, usando-os, você pode encontrar a maneira **mais apropriada** de **implantar seu aplicativo**.
@@ -23,7 +23,7 @@ Nos próximos capítulos, darei a você mais **receitas concretas** para implant
Mas por enquanto, vamos verificar essas importantes **ideias conceituais**. Esses conceitos também se aplicam a qualquer outro tipo de API da web. 💡
-## Segurança - HTTPS
+## Segurança - HTTPS { #security-https }
No [capítulo anterior sobre HTTPS](https.md){.internal-link target=_blank} aprendemos como o HTTPS fornece criptografia para sua API.
@@ -31,7 +31,7 @@ Também vimos que o HTTPS normalmente é fornecido por um componente **externo**
E tem que haver algo responsável por **renovar os certificados HTTPS**, pode ser o mesmo componente ou pode ser algo diferente.
-### Ferramentas de exemplo para HTTPS
+### Ferramentas de exemplo para HTTPS { #example-tools-for-https }
Algumas das ferramentas que você pode usar como um proxy de terminação TLS são:
@@ -55,11 +55,11 @@ Mostrarei alguns exemplos concretos nos próximos capítulos.
Os próximos conceitos a serem considerados são todos sobre o programa que executa sua API real (por exemplo, Uvicorn).
-## Programa e Processo
+## Programa e Processo { #program-and-process }
Falaremos muito sobre o "**processo**" em execução, então é útil ter clareza sobre o que ele significa e qual é a diferença com a palavra "**programa**".
-### O que é um Programa
+### O que é um Programa { #what-is-a-program }
A palavra **programa** é comumente usada para descrever muitas coisas:
@@ -67,7 +67,7 @@ A palavra **programa** é comumente usada para descrever muitas coisas:
* O **arquivo** que pode ser **executado** pelo sistema operacional, por exemplo: `python`, `python.exe` ou `uvicorn`.
* Um programa específico enquanto está **em execução** no sistema operacional, usando a CPU e armazenando coisas na memória. Isso também é chamado de **processo**.
-### O que é um Processo
+### O que é um Processo { #what-is-a-process }
A palavra **processo** normalmente é usada de forma mais específica, referindo-se apenas ao que está sendo executado no sistema operacional (como no último ponto acima):
@@ -88,11 +88,11 @@ E, por exemplo, você provavelmente verá que há vários processos executando o
Agora que sabemos a diferença entre os termos **processo** e **programa**, vamos continuar falando sobre implantações.
-## Executando na inicialização
+## Executando na inicialização { #running-on-startup }
Na maioria dos casos, quando você cria uma API web, você quer que ela esteja **sempre em execução**, ininterrupta, para que seus clientes possam sempre acessá-la. Isso é claro, a menos que você tenha um motivo específico para querer que ela seja executada somente em certas situações, mas na maioria das vezes você quer que ela esteja constantemente em execução e **disponível**.
-### Em um servidor remoto
+### Em um servidor remoto { #in-a-remote-server }
Ao configurar um servidor remoto (um servidor em nuvem, uma máquina virtual, etc.), a coisa mais simples que você pode fazer é usar `fastapi run` (que usa Uvicorn) ou algo semelhante, manualmente, da mesma forma que você faz ao desenvolver localmente.
@@ -102,15 +102,15 @@ Mas se sua conexão com o servidor for perdida, o **processo em execução** pro
E se o servidor for reiniciado (por exemplo, após atualizações ou migrações do provedor de nuvem), você provavelmente **não notará**. E por causa disso, você nem saberá que precisa reiniciar o processo manualmente. Então, sua API simplesmente permanecerá inativa. 😱
-### Executar automaticamente na inicialização
+### Executar automaticamente na inicialização { #run-automatically-on-startup }
Em geral, você provavelmente desejará que o programa do servidor (por exemplo, Uvicorn) seja iniciado automaticamente na inicialização do servidor e, sem precisar de nenhuma **intervenção humana**, tenha um processo sempre em execução com sua API (por exemplo, Uvicorn executando seu aplicativo FastAPI).
-### Programa separado
+### Programa separado { #separate-program }
Para conseguir isso, você normalmente terá um **programa separado** que garantiria que seu aplicativo fosse executado na inicialização. E em muitos casos, ele também garantiria que outros componentes ou aplicativos também fossem executados, por exemplo, um banco de dados.
-### Ferramentas de exemplo para executar na inicialização
+### Ferramentas de exemplo para executar na inicialização { #example-tools-to-run-at-startup }
Alguns exemplos de ferramentas que podem fazer esse trabalho são:
@@ -125,29 +125,29 @@ Alguns exemplos de ferramentas que podem fazer esse trabalho são:
Darei exemplos mais concretos nos próximos capítulos.
-## Reinicializações
+## Reinicializações { #restarts }
Semelhante a garantir que seu aplicativo seja executado na inicialização, você provavelmente também deseja garantir que ele seja **reiniciado** após falhas.
-### Nós cometemos erros
+### Nós cometemos erros { #we-make-mistakes }
Nós, como humanos, cometemos **erros** o tempo todo. O software quase *sempre* tem **bugs** escondidos em lugares diferentes. 🐛
E nós, como desenvolvedores, continuamos aprimorando o código à medida que encontramos esses bugs e implementamos novos recursos (possivelmente adicionando novos bugs também 😅).
-### Pequenos erros são tratados automaticamente
+### Pequenos erros são tratados automaticamente { #small-errors-automatically-handled }
Ao criar APIs da web com FastAPI, se houver um erro em nosso código, o FastAPI normalmente o conterá na única solicitação que acionou o erro. 🛡
O cliente receberá um **Erro Interno do Servidor 500** para essa solicitação, mas o aplicativo continuará funcionando para as próximas solicitações em vez de travar completamente.
-### Erros maiores - Travamentos
+### Erros maiores - Travamentos { #bigger-errors-crashes }
No entanto, pode haver casos em que escrevemos algum código que **trava todo o aplicativo**, fazendo com que o Uvicorn e o Python travem. 💥
E ainda assim, você provavelmente não gostaria que o aplicativo permanecesse inativo porque houve um erro em um lugar, você provavelmente quer que ele **continue em execução** pelo menos para as *operações de caminho* que não estão quebradas.
-### Reiniciar após falha
+### Reiniciar após falha { #restart-after-crash }
Mas nos casos com erros realmente graves que travam o **processo** em execução, você vai querer um componente externo que seja responsável por **reiniciar** o processo, pelo menos algumas vezes...
@@ -161,7 +161,7 @@ Então, vamos nos concentrar nos casos principais, onde ele pode travar completa
Você provavelmente gostaria de ter a coisa responsável por reiniciar seu aplicativo como um **componente externo**, porque a essa altura, o mesmo aplicativo com Uvicorn e Python já havia travado, então não há nada no mesmo código do mesmo aplicativo que possa fazer algo a respeito.
-### Ferramentas de exemplo para reiniciar automaticamente
+### Ferramentas de exemplo para reiniciar automaticamente { #example-tools-to-restart-automatically }
Na maioria dos casos, a mesma ferramenta usada para **executar o programa na inicialização** também é usada para lidar com **reinicializações** automáticas.
@@ -176,19 +176,19 @@ Por exemplo, isso poderia ser resolvido por:
* Gerenciado internamente por um provedor de nuvem como parte de seus serviços
* Outros...
-## Replicação - Processos e Memória
+## Replicação - Processos e Memória { #replication-processes-and-memory }
Com um aplicativo FastAPI, usando um programa de servidor como o comando `fastapi` que executa o Uvicorn, executá-lo uma vez em **um processo** pode atender a vários clientes simultaneamente.
Mas em muitos casos, você desejará executar vários processos de trabalho ao mesmo tempo.
-### Processos Múltiplos - Trabalhadores
+### Processos Múltiplos - Trabalhadores { #multiple-processes-workers }
Se você tiver mais clientes do que um único processo pode manipular (por exemplo, se a máquina virtual não for muito grande) e tiver **vários núcleos** na CPU do servidor, você poderá ter **vários processos** em execução com o mesmo aplicativo ao mesmo tempo e distribuir todas as solicitações entre eles.
Quando você executa **vários processos** do mesmo programa de API, eles são comumente chamados de **trabalhadores**.
-### Processos do Trabalhador e Portas
+### Processos do Trabalhador e Portas { #worker-processes-and-ports }
Lembra da documentação [Sobre HTTPS](https.md){.internal-link target=_blank} que diz que apenas um processo pode escutar em uma combinação de porta e endereço IP em um servidor?
@@ -196,19 +196,19 @@ Isso ainda é verdade.
Então, para poder ter **vários processos** ao mesmo tempo, tem que haver um **único processo escutando em uma porta** que então transmite a comunicação para cada processo de trabalho de alguma forma.
-### Memória por Processo
+### Memória por Processo { #memory-per-process }
Agora, quando o programa carrega coisas na memória, por exemplo, um modelo de aprendizado de máquina em uma variável, ou o conteúdo de um arquivo grande em uma variável, tudo isso **consome um pouco da memória (RAM)** do servidor.
E vários processos normalmente **não compartilham nenhuma memória**. Isso significa que cada processo em execução tem suas próprias coisas, variáveis e memória. E se você estiver consumindo uma grande quantidade de memória em seu código, **cada processo** consumirá uma quantidade equivalente de memória.
-### Memória do servidor
+### Memória do servidor { #server-memory }
Por exemplo, se seu código carrega um modelo de Machine Learning com **1 GB de tamanho**, quando você executa um processo com sua API, ele consumirá pelo menos 1 GB de RAM. E se você iniciar **4 processos** (4 trabalhadores), cada um consumirá 1 GB de RAM. Então, no total, sua API consumirá **4 GB de RAM**.
E se o seu servidor remoto ou máquina virtual tiver apenas 3 GB de RAM, tentar carregar mais de 4 GB de RAM causará problemas. 🚨
-### Processos Múltiplos - Um Exemplo
+### Processos Múltiplos - Um Exemplo { #multiple-processes-an-example }
Neste exemplo, há um **Processo Gerenciador** que inicia e controla dois **Processos de Trabalhadores**.
@@ -224,7 +224,7 @@ Um detalhe interessante é que a porcentagem da **CPU usada** por cada processo
Se você tiver uma API que faz uma quantidade comparável de cálculos todas as vezes e tiver muitos clientes, então a **utilização da CPU** provavelmente *também será estável* (em vez de ficar constantemente subindo e descendo rapidamente).
-### Exemplos de ferramentas e estratégias de replicação
+### Exemplos de ferramentas e estratégias de replicação { #examples-of-replication-tools-and-strategies }
Pode haver várias abordagens para conseguir isso, e falarei mais sobre estratégias específicas nos próximos capítulos, por exemplo, ao falar sobre Docker e contêineres.
@@ -247,7 +247,7 @@ Falarei mais sobre imagens de contêiner, Docker, Kubernetes, etc. em um capítu
///
-## Etapas anteriores antes de começar
+## Etapas anteriores antes de começar { #previous-steps-before-starting }
Há muitos casos em que você deseja executar algumas etapas **antes de iniciar** sua aplicação.
@@ -269,7 +269,7 @@ Nesse caso, você não precisaria se preocupar com nada disso. 🤷
///
-### Exemplos de estratégias de etapas anteriores
+### Exemplos de estratégias de etapas anteriores { #examples-of-previous-steps-strategies }
Isso **dependerá muito** da maneira como você **implanta seu sistema** e provavelmente estará conectado à maneira como você inicia programas, lida com reinicializações, etc.
@@ -285,7 +285,7 @@ Darei exemplos mais concretos de como fazer isso com contêineres em um capítul
///
-## Utilização de recursos
+## Utilização de recursos { #resource-utilization }
Seu(s) servidor(es) é(são) um **recurso** que você pode consumir ou **utilizar**, com seus programas, o tempo de computação nas CPUs e a memória RAM disponível.
@@ -305,7 +305,7 @@ Você poderia colocar um **número arbitrário** para atingir, por exemplo, algo
Você pode usar ferramentas simples como `htop` para ver a CPU e a RAM usadas no seu servidor ou a quantidade usada por cada processo. Ou você pode usar ferramentas de monitoramento mais complexas, que podem ser distribuídas entre servidores, etc.
-## Recapitular
+## Recapitular { #recap }
Você leu aqui alguns dos principais conceitos que provavelmente precisa ter em mente ao decidir como implantar seu aplicativo:
diff --git a/docs/pt/docs/deployment/docker.md b/docs/pt/docs/deployment/docker.md
index cf18bb1532..b26a69b54f 100644
--- a/docs/pt/docs/deployment/docker.md
+++ b/docs/pt/docs/deployment/docker.md
@@ -1,4 +1,4 @@
-# FastAPI em contêineres - Docker
+# FastAPI em contêineres - Docker { #fastapi-in-containers-docker }
Ao fazer o deploy de aplicações FastAPI uma abordagem comum é construir uma **imagem de contêiner Linux**. Isso normalmente é feito usando o **Docker**. Você pode a partir disso fazer o deploy dessa imagem de algumas maneiras.
@@ -6,7 +6,7 @@ Usando contêineres Linux você tem diversas vantagens incluindo **segurança**,
/// tip | Dica
-Está com pressa e já sabe dessas coisas? Pode ir direto para [`Dockerfile` abaixo 👇](#construindo-uma-imagem-docker-para-fastapi).
+Está com pressa e já sabe dessas coisas? Pode ir direto para o [`Dockerfile` abaixo 👇](#build-a-docker-image-for-fastapi).
///
@@ -24,25 +24,25 @@ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
-CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
+CMD ["fastapi", "run", "app/main.py", "--port", "80"]
-# If running behind a proxy like Nginx or Traefik add --proxy-headers
-# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"]
+# Se estiver executando atrás de um proxy como Nginx ou Traefik, adicione --proxy-headers
+# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"]
```
-## O que é um Contêiner
+## O que é um Contêiner { #what-is-a-container }
-Contêineres (especificamente contêineres Linux) são um jeito muito **leve** de empacotar aplicações contendo todas as dependências e arquivos necessários enquanto os mantém isolados de outros contêineres (outras aplicações ou componentes) no mesmo sistema.
+Contêineres (principalmente contêineres Linux) são um jeito muito **leve** de empacotar aplicações contendo todas as dependências e arquivos necessários enquanto os mantém isolados de outros contêineres (outras aplicações ou componentes) no mesmo sistema.
Contêineres Linux rodam usando o mesmo kernel Linux do hospedeiro (máquina, máquina virtual, servidor na nuvem, etc). Isso simplesmente significa que eles são muito leves (comparados com máquinas virtuais emulando um sistema operacional completo).
Dessa forma, contêineres consomem **poucos recursos**, uma quantidade comparável com rodar os processos diretamente (uma máquina virtual consumiria muito mais).
-Contêineres também possuem seus próprios processos (comumente um único processo), sistema de arquivos e rede **isolados** simplificando deploy, segurança, desenvolvimento, etc.
+Contêineres também possuem seus próprios processos em execução (comumente **um único processo**), sistema de arquivos e rede **isolados**, simplificando deploy, segurança, desenvolvimento, etc.
-## O que é uma Imagem de Contêiner
+## O que é uma Imagem de Contêiner { #what-is-a-container-image }
Um **contêiner** roda a partir de uma **imagem de contêiner**.
@@ -56,7 +56,7 @@ Uma imagem de contêiner é comparável ao arquivo de **programa** e seus conte
E o **contêiner** em si (em contraste à **imagem de contêiner**) é a própria instância da imagem rodando, comparável a um **processo**. Na verdade, um contêiner está rodando somente quando há um **processo rodando** (e normalmente é somente um processo). O contêiner finaliza quando não há um processo rodando nele.
-## Imagens de contêiner
+## Imagens de contêiner { #container-images }
Docker tem sido uma das principais ferramentas para criar e gerenciar **imagens de contêiner** e **contêineres**.
@@ -71,15 +71,15 @@ E existe muitas outras imagens para diferentes coisas, como bancos de dados, por
* MongoDB
* Redis, etc.
-Usando imagens de contêiner pré-prontas é muito fácil **combinar** e usar diferentes ferramentas. Por exemplo, para testar um novo banco de dados. Em muitos casos, você pode usar as **imagens oficiais** precisando somente de variáveis de ambiente para configurá-las.
+Usando imagens de contêiner pré-prontas é muito fácil **combinar** e usar diferentes ferramentas. Por exemplo, para testar um novo banco de dados. Em muitos casos, você pode usar as **imagens oficiais**, precisando somente de variáveis de ambiente para configurá-las.
-Dessa forma, em muitos casos você pode aprender sobre contêineres e Docker e re-usar essa experiência com diversos componentes e ferramentas.
+Dessa forma, em muitos casos você pode aprender sobre contêineres e Docker e reusar essa experiência com diversos componentes e ferramentas.
Então, você rodaria **vários contêineres** com coisas diferentes, como um banco de dados, uma aplicação Python, um servidor web com uma aplicação frontend React, e conectá-los juntos via sua rede interna.
Todos os sistemas de gerenciamento de contêineres (como Docker ou Kubernetes) possuem essas funcionalidades de rede integradas a eles.
-## Contêineres e Processos
+## Contêineres e Processos { #containers-and-processes }
Uma **imagem de contêiner** normalmente inclui em seus metadados o programa padrão ou comando que deve ser executado quando o **contêiner** é iniciado e os parâmetros a serem passados para esse programa. Muito similar ao que seria se estivesse na linha de comando.
@@ -91,11 +91,11 @@ Um contêiner normalmente tem um **único processo**, mas também é possível i
Mas não é possível ter um contêiner rodando sem **pelo menos um processo rodando**. Se o processo principal parar, o contêiner também para.
-## Construindo uma Imagem Docker para FastAPI
+## Construir uma Imagem Docker para FastAPI { #build-a-docker-image-for-fastapi }
Okay, vamos construir algo agora! 🚀
-Eu vou mostrar como construir uma **imagem Docker** para FastAPI **do zero**, baseado na **imagem oficial do Python**.
+Eu vou mostrar como construir uma **imagem Docker** para FastAPI **do zero**, baseada na imagem **oficial do Python**.
Isso é o que você quer fazer na **maioria dos casos**, por exemplo:
@@ -103,22 +103,21 @@ Isso é o que você quer fazer na **maioria dos casos**, por exemplo:
* Quando rodando em uma **Raspberry Pi**
* Usando um serviço em nuvem que irá rodar uma imagem de contêiner para você, etc.
-### O Pacote Requirements
+### Requisitos de Pacotes { #package-requirements }
-Você normalmente teria os **requisitos do pacote** para sua aplicação em algum arquivo.
+Você normalmente teria os **requisitos de pacotes** da sua aplicação em algum arquivo.
Isso pode depender principalmente da ferramenta que você usa para **instalar** esses requisitos.
-O caminho mais comum de fazer isso é ter um arquivo `requirements.txt` com os nomes dos pacotes e suas versões, um por linha.
+A forma mais comum de fazer isso é ter um arquivo `requirements.txt` com os nomes dos pacotes e suas versões, um por linha.
-Você, naturalmente, usaria as mesmas ideias que você leu em [Sobre Versões do FastAPI](versions.md){.internal-link target=_blank} para definir os intervalos de versões.
+Você, naturalmente, usaria as mesmas ideias que você leu em [Sobre versões do FastAPI](versions.md){.internal-link target=_blank} para definir os intervalos de versões.
Por exemplo, seu `requirements.txt` poderia parecer com:
```
-fastapi>=0.68.0,<0.69.0
-pydantic>=1.8.0,<2.0.0
-uvicorn>=0.15.0,<0.16.0
+fastapi[standard]>=0.113.0,<0.114.0
+pydantic>=2.7.0,<3.0.0
```
E você normalmente instalaria essas dependências de pacote com `pip`, por exemplo:
@@ -128,27 +127,25 @@ E você normalmente instalaria essas dependências de pacote com `pip`, por exem
```console
$ pip install -r requirements.txt
---> 100%
-Successfully installed fastapi pydantic uvicorn
+Successfully installed fastapi pydantic
```
-## Alterar o tema
+## Alterar o tema { #change-the-theme }
Da mesma forma que você pode definir o tema de destaque de sintaxe com a chave `"syntaxHighlight.theme"` (observe que há um ponto no meio):
-{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *}
+{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *}
Essa configuração alteraria o tema de cores de destaque de sintaxe:
-## Alterar parâmetros de UI padrão do Swagger
+## Alterar parâmetros de UI padrão do Swagger { #change-default-swagger-ui-parameters }
O FastAPI inclui alguns parâmetros de configuração padrão apropriados para a maioria dos casos de uso.
Inclui estas configurações padrão:
-{* ../../fastapi/openapi/docs.py ln[7:23] *}
+{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *}
Você pode substituir qualquer um deles definindo um valor diferente no argumento `swagger_ui_parameters`.
Por exemplo, para desabilitar `deepLinking` você pode passar essas configurações para `swagger_ui_parameters`:
-{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *}
+{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *}
-## Outros parâmetros da UI do Swagger
+## Outros parâmetros da UI do Swagger { #other-swagger-ui-parameters }
Para ver todas as outras configurações possíveis que você pode usar, leia a documentação oficial dos parâmetros da UI do Swagger.
-## Configurações somente JavaScript
+## Configurações somente JavaScript { #javascript-only-settings }
-A interface do usuário do Swagger também permite que outras configurações sejam objetos **somente JavaScript** (por exemplo, funções JavaScript).
+A UI do Swagger também permite que outras configurações sejam objetos **somente JavaScript** (por exemplo, funções JavaScript).
-O FastAPI também inclui estas configurações de `predefinições` somente para JavaScript:
+O FastAPI também inclui estas configurações `presets` somente para JavaScript:
```JavaScript
presets: [
@@ -67,4 +67,4 @@ presets: [
Esses são objetos **JavaScript**, não strings, então você não pode passá-los diretamente do código Python.
-Se você precisar usar configurações somente JavaScript como essas, você pode usar um dos métodos acima. Sobrescreva todas as *operações de rotas* do Swagger UI e escreva manualmente qualquer JavaScript que você precisar.
+Se você precisar usar configurações somente JavaScript como essas, você pode usar um dos métodos acima. Substitua toda a *operação de rota* do Swagger UI e escreva manualmente qualquer JavaScript que você precisar.
diff --git a/docs/pt/docs/how-to/custom-docs-ui-assets.md b/docs/pt/docs/how-to/custom-docs-ui-assets.md
index b7de6c8bdc..adda9eca50 100644
--- a/docs/pt/docs/how-to/custom-docs-ui-assets.md
+++ b/docs/pt/docs/how-to/custom-docs-ui-assets.md
@@ -1,26 +1,26 @@
-# Recursos Estáticos Personalizados para a UI de Documentação (Hospedagem Própria)
+# Recursos Estáticos Personalizados para a UI de Documentação (Hospedagem Própria) { #custom-docs-ui-static-assets-self-hosting }
A documentação da API usa **Swagger UI** e **ReDoc**, e cada um deles precisa de alguns arquivos JavaScript e CSS.
-Por padrão, esses arquivos são fornecidos por um CDN.
+Por padrão, esses arquivos são fornecidos por um CDN.
Mas é possível personalizá-los, você pode definir um CDN específico ou providenciar os arquivos você mesmo.
-## CDN Personalizado para JavaScript e CSS
+## CDN Personalizado para JavaScript e CSS { #custom-cdn-for-javascript-and-css }
-Vamos supor que você deseja usar um CDN diferente, por exemplo, você deseja usar `https://unpkg.com/`.
+Vamos supor que você deseja usar um CDN diferente, por exemplo, você deseja usar `https://unpkg.com/`.
Isso pode ser útil se, por exemplo, você mora em um país que restringe algumas URLs.
-### Desativar a documentação automática
+### Desativar a documentação automática { #disable-the-automatic-docs }
O primeiro passo é desativar a documentação automática, pois por padrão, ela usa o CDN padrão.
-Para desativá-los, defina suas URLs como `None` ao criar seu aplicativo `FastAPI`:
+Para desativá-los, defina suas URLs como `None` ao criar sua aplicação FastAPI:
-{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *}
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *}
-### Incluir a documentação personalizada
+### Incluir a documentação personalizada { #include-the-custom-docs }
Agora você pode criar as *operações de rota* para a documentação personalizada.
@@ -34,7 +34,7 @@ Você pode reutilizar as funções internas do FastAPI para criar as páginas HT
E de forma semelhante para o ReDoc...
-{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *}
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *}
/// tip | Dica
@@ -46,23 +46,23 @@ Swagger UI lidará com isso nos bastidores para você, mas ele precisa desse aux
///
-### Criar uma *operação de rota* para testar
+### Criar uma *operação de rota* para testar { #create-a-path-operation-to-test-it }
Agora, para poder testar se tudo funciona, crie uma *operação de rota*:
-{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *}
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *}
-### Teste
+### Teste { #test-it }
Agora, você deve ser capaz de ir para a documentação em http://127.0.0.1:8000/docs, e recarregar a página, ela carregará esses recursos do novo CDN.
-## Hospedagem Própria de JavaScript e CSS para a documentação
+## Hospedagem Própria de JavaScript e CSS para a documentação { #self-hosting-javascript-and-css-for-docs }
Hospedar o JavaScript e o CSS pode ser útil se, por exemplo, você precisa que seu aplicativo continue funcionando mesmo offline, sem acesso aberto à Internet, ou em uma rede local.
-Aqui você verá como providenciar esses arquivos você mesmo, no mesmo aplicativo FastAPI, e configurar a documentação para usá-los.
+Aqui você verá como providenciar esses arquivos você mesmo, na mesma aplicação FastAPI, e configurar a documentação para usá-los.
-### Estrutura de Arquivos do Projeto
+### Estrutura de Arquivos do Projeto { #project-file-structure }
Vamos supor que a estrutura de arquivos do seu projeto se pareça com isso:
@@ -85,7 +85,7 @@ Sua nova estrutura de arquivos poderia se parecer com isso:
└── static/
```
-### Baixe os arquivos
+### Baixe os arquivos { #download-the-files }
Baixe os arquivos estáticos necessários para a documentação e coloque-os no diretório `static/`.
@@ -96,7 +96,7 @@ Você provavelmente pode clicar com o botão direito em cada link e selecionar u
* `swagger-ui-bundle.js`
* `swagger-ui.css`
-E o **ReDoc** usa os arquivos:
+E o **ReDoc** usa o arquivo:
* `redoc.standalone.js`
@@ -113,14 +113,14 @@ Depois disso, sua estrutura de arquivos deve se parecer com:
└── swagger-ui.css
```
-### Prover os arquivos estáticos
+### Prover os arquivos estáticos { #serve-the-static-files }
* Importe `StaticFiles`.
* "Monte" a instância `StaticFiles()` em um caminho específico.
-{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *}
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *}
-### Teste os arquivos estáticos
+### Teste os arquivos estáticos { #test-the-static-files }
Inicialize seu aplicativo e vá para http://127.0.0.1:8000/static/redoc.standalone.js.
@@ -138,15 +138,15 @@ Isso confirma que você está conseguindo fornecer arquivos estáticos do seu ap
Agora, podemos configurar o aplicativo para usar esses arquivos estáticos para a documentação.
-### Desativar a documentação automática para arquivos estáticos
+### Desativar a documentação automática para arquivos estáticos { #disable-the-automatic-docs-for-static-files }
Da mesma forma que ao usar um CDN personalizado, o primeiro passo é desativar a documentação automática, pois ela usa o CDN padrão.
-Para desativá-los, defina suas URLs como `None` ao criar seu aplicativo `FastAPI`:
+Para desativá-los, defina suas URLs como `None` ao criar sua aplicação FastAPI:
-{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *}
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *}
-### Incluir a documentação personalizada para arquivos estáticos
+### Incluir a documentação personalizada para arquivos estáticos { #include-the-custom-docs-for-static-files }
E da mesma forma que com um CDN personalizado, agora você pode criar as *operações de rota* para a documentação personalizada.
@@ -155,12 +155,12 @@ Novamente, você pode reutilizar as funções internas do FastAPI para criar as
* `openapi_url`: a URL onde a página HTML para a documentação pode obter o esquema OpenAPI para a sua API. Você pode usar aqui o atributo `app.openapi_url`.
* `title`: o título da sua API.
* `oauth2_redirect_url`: Você pode usar `app.swagger_ui_oauth2_redirect_url` aqui para usar o padrão.
-* `swagger_js_url`: a URL onde a página HTML para a sua documentação do Swagger UI pode obter o arquivo **JavaScript**. Este é o URL do CDN personalizado. **Este é o URL que seu aplicativo está fornecendo**.
+* `swagger_js_url`: a URL onde a página HTML para a sua documentação do Swagger UI pode obter o arquivo **JavaScript**. **Este é o URL que seu aplicativo está fornecendo**.
* `swagger_css_url`: a URL onde a página HTML para a sua documentação do Swagger UI pode obter o arquivo **CSS**. **Esse é o que seu aplicativo está fornecendo**.
E de forma semelhante para o ReDoc...
-{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *}
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *}
/// tip | Dica
@@ -172,13 +172,13 @@ Swagger UI lidará com isso nos bastidores para você, mas ele precisa desse aux
///
-### Criar uma *operação de rota* para testar arquivos estáticos
+### Criar uma *operação de rota* para testar arquivos estáticos { #create-a-path-operation-to-test-static-files }
Agora, para poder testar se tudo funciona, crie uma *operação de rota*:
-{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *}
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *}
-### Teste a UI de Arquivos Estáticos
+### Teste a UI de Arquivos Estáticos { #test-static-files-ui }
Agora, você deve ser capaz de desconectar o WiFi, ir para a documentação em http://127.0.0.1:8000/docs, e recarregar a página.
diff --git a/docs/pt/docs/how-to/custom-request-and-route.md b/docs/pt/docs/how-to/custom-request-and-route.md
index 151a0f5d44..b4ea1c282b 100644
--- a/docs/pt/docs/how-to/custom-request-and-route.md
+++ b/docs/pt/docs/how-to/custom-request-and-route.md
@@ -1,12 +1,12 @@
-# Requisições Personalizadas e Classes da APIRoute
+# Request e classe APIRoute personalizadas { #custom-request-and-apiroute-class }
-Em algum casos, você pode querer sobreescrever a lógica usada pelas classes `Request`e `APIRoute`.
+Em alguns casos, você pode querer sobrescrever a lógica usada pelas classes `Request` e `APIRoute`.
-Em particular, isso pode ser uma boa alternativa para uma lógica em um middleware
+Em particular, isso pode ser uma boa alternativa para uma lógica em um middleware.
Por exemplo, se você quiser ler ou manipular o corpo da requisição antes que ele seja processado pela sua aplicação.
-/// danger | Perigo
+/// danger | Cuidado
Isso é um recurso "avançado".
@@ -14,7 +14,7 @@ Se você for um iniciante em **FastAPI** você deve considerar pular essa seçã
///
-## Casos de Uso
+## Casos de Uso { #use-cases }
Alguns casos de uso incluem:
@@ -22,13 +22,13 @@ Alguns casos de uso incluem:
* Descomprimir corpos de requisição comprimidos com gzip.
* Registrar automaticamente todos os corpos de requisição.
-## Manipulando codificações de corpo de requisição personalizadas
+## Manipulando codificações de corpo de requisição personalizadas { #handling-custom-request-body-encodings }
Vamos ver como usar uma subclasse personalizada de `Request` para descomprimir requisições gzip.
E uma subclasse de `APIRoute` para usar essa classe de requisição personalizada.
-### Criar uma classe `GzipRequest` personalizada
+### Criar uma classe `GzipRequest` personalizada { #create-a-custom-gziprequest-class }
/// tip | Dica
@@ -42,9 +42,9 @@ Se não houver `gzip` no cabeçalho, ele não tentará descomprimir o corpo.
Dessa forma, a mesma classe de rota pode lidar com requisições comprimidas ou não comprimidas.
-{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *}
+{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *}
-### Criar uma classe `GzipRoute` personalizada
+### Criar uma classe `GzipRoute` personalizada { #create-a-custom-gziproute-class }
Em seguida, criamos uma subclasse personalizada de `fastapi.routing.APIRoute` que fará uso do `GzipRequest`.
@@ -54,11 +54,11 @@ Esse método retorna uma função. E essa função é o que irá receber uma req
Aqui nós usamos para criar um `GzipRequest` a partir da requisição original.
-{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *}
+{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *}
/// note | Detalhes Técnicos
-Um `Request` também tem um `request.receive`, que é uma função para "receber" o corpo da requisição.
+Um `Request` tem um atributo `request.scope`, que é apenas um `dict` do Python contendo os metadados relacionados à requisição.
Um `Request` também tem um `request.receive`, que é uma função para "receber" o corpo da requisição.
@@ -78,7 +78,7 @@ Depois disso, toda a lógica de processamento é a mesma.
Mas por causa das nossas mudanças em `GzipRequest.body`, o corpo da requisição será automaticamente descomprimido quando for carregado pelo **FastAPI** quando necessário.
-## Acessando o corpo da requisição em um manipulador de exceção
+## Acessando o corpo da requisição em um manipulador de exceção { #accessing-the-request-body-in-an-exception-handler }
/// tip | Dica
@@ -92,18 +92,18 @@ Também podemos usar essa mesma abordagem para acessar o corpo da requisição e
Tudo que precisamos fazer é manipular a requisição dentro de um bloco `try`/`except`:
-{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *}
+{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *}
Se uma exceção ocorrer, a instância `Request` ainda estará em escopo, então podemos ler e fazer uso do corpo da requisição ao lidar com o erro:
-{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *}
+{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *}
-## Classe `APIRoute` personalizada em um router
+## Classe `APIRoute` personalizada em um router { #custom-apiroute-class-in-a-router }
-você também pode definir o parametro `route_class` de uma `APIRouter`;
+Você também pode definir o parâmetro `route_class` de uma `APIRouter`:
-{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *}
+{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *}
Nesse exemplo, as *operações de rota* sob o `router` irão usar a classe `TimedRoute` personalizada, e terão um cabeçalho extra `X-Response-Time` na resposta com o tempo que levou para gerar a resposta:
-{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *}
+{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *}
diff --git a/docs/pt/docs/how-to/extending-openapi.md b/docs/pt/docs/how-to/extending-openapi.md
index b4785edc15..b1b50ce654 100644
--- a/docs/pt/docs/how-to/extending-openapi.md
+++ b/docs/pt/docs/how-to/extending-openapi.md
@@ -1,10 +1,10 @@
-# Extendendo o OpenAPI
+# Extendendo o OpenAPI { #extending-openapi }
Existem alguns casos em que pode ser necessário modificar o esquema OpenAPI gerado.
Nesta seção, você verá como fazer isso.
-## O processo normal
+## O processo normal { #the-normal-process }
O processo normal (padrão) é o seguinte:
@@ -33,31 +33,31 @@ O parâmetro `summary` está disponível no OpenAPI 3.1.0 e superior, suportado
///
-## Sobrescrevendo os padrões
+## Sobrescrevendo os padrões { #overriding-the-defaults }
Com as informações acima, você pode usar a mesma função utilitária para gerar o esquema OpenAPI e sobrescrever cada parte que precisar.
Por exemplo, vamos adicionar Extensão OpenAPI do ReDoc para incluir um logo personalizado.
-### **FastAPI** Normal
+### **FastAPI** Normal { #normal-fastapi }
Primeiro, escreva toda a sua aplicação **FastAPI** normalmente:
-{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *}
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[1,4,7:9] *}
-### Gerar o esquema OpenAPI
+### Gerar o esquema OpenAPI { #generate-the-openapi-schema }
Em seguida, use a mesma função utilitária para gerar o esquema OpenAPI, dentro de uma função `custom_openapi()`:
-{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *}
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[2,15:21] *}
-### Modificar o esquema OpenAPI
+### Modificar o esquema OpenAPI { #modify-the-openapi-schema }
Agora, você pode adicionar a extensão do ReDoc, incluindo um `x-logo` personalizado ao "objeto" `info` no esquema OpenAPI:
-{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *}
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *}
-### Armazenar em cache o esquema OpenAPI
+### Armazenar em cache o esquema OpenAPI { #cache-the-openapi-schema }
Você pode usar a propriedade `.openapi_schema` como um "cache" para armazenar o esquema gerado.
@@ -65,16 +65,16 @@ Dessa forma, sua aplicação não precisará gerar o esquema toda vez que um usu
Ele será gerado apenas uma vez, e o mesmo esquema armazenado em cache será utilizado nas próximas requisições.
-{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *}
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *}
-### Sobrescrever o método
+### Sobrescrever o método { #override-the-method }
Agora, você pode substituir o método `.openapi()` pela sua nova função.
-{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *}
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *}
-### Verificar
+### Verificar { #check-it }
Uma vez que você acessar http://127.0.0.1:8000/redoc, verá que está usando seu logo personalizado (neste exemplo, o logo do **FastAPI**):
-
+
diff --git a/docs/pt/docs/how-to/general.md b/docs/pt/docs/how-to/general.md
index 4f21463b28..7f91468625 100644
--- a/docs/pt/docs/how-to/general.md
+++ b/docs/pt/docs/how-to/general.md
@@ -1,39 +1,38 @@
-# Geral - Como Fazer - Receitas
+# Geral - Como Fazer - Receitas { #general-how-to-recipes }
Aqui estão vários links para outros locais na documentação, para perguntas gerais ou frequentes
-## Filtro de dados- Segurança
+## Filtro de dados- Segurança { #filter-data-security }
-Para assegurar que você não vai retornar mais dados do que deveria, leia a seção [Tutorial - Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank}.
+Para assegurar que você não vai retornar mais dados do que deveria, leia a seção [Tutorial - Modelo de Resposta - Tipo de Retorno](../tutorial/response-model.md){.internal-link target=_blank}.
-## Tags de Documentação - OpenAPI
-Para adicionar tags às suas *rotas* e agrupá-las na UI da documentação, leia a seção [Tutorial - Path Operation Configurations - Tags](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}.
+## Tags de Documentação - OpenAPI { #documentation-tags-openapi }
+Para adicionar tags às suas *operações de rota* e agrupá-las na UI da documentação, leia a seção [Tutorial - Configurações da Operação de Rota - Tags](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}.
-## Resumo e Descrição da documentação - OpenAPI
+## Resumo e Descrição da documentação - OpenAPI { #documentation-summary-and-description-openapi }
-Para adicionar um resumo e uma descrição às suas *rotas* e exibi-los na UI da documentação, leia a seção [Tutorial - Path Operation Configurations - Summary and Description](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}.
+Para adicionar um resumo e uma descrição às suas *operações de rota* e exibi-los na UI da documentação, leia a seção [Tutorial - Configurações da Operação de Rota - Resumo e Descrição](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}.
-## Documentação das Descrições de Resposta - OpenAPI
+## Documentação das Descrições de Resposta - OpenAPI { #documentation-response-description-openapi }
-Para definir a descrição de uma resposta exibida na interface da documentação, leia a seção [Tutorial - Path Operation Configurations - Response description](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}.
+Para definir a descrição de uma resposta exibida na interface da documentação, leia a seção [Tutorial - Configurações da Operação de Rota - Descrição da Resposta](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}.
-## Documentação para Depreciar uma *Operação de Rota* - OpenAPI
+## Documentação para Depreciar uma *Operação de Rota* - OpenAPI { #documentation-deprecate-a-path-operation-openapi }
-Para depreciar uma *operação de rota* e exibi-la na interface da documentação, leia a seção [Tutorial - Path Operation Configurations - Deprecation](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}.
+Para depreciar uma *operação de rota* e exibi-la na interface da documentação, leia a seção [Tutorial - Configurações da Operação de Rota - Depreciação](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}.
-## Converter qualquer dado para JSON
+## Converter qualquer dado para compatível com JSON { #convert-any-data-to-json-compatible }
+Para converter qualquer dado para um formato compatível com JSON, leia a seção [Tutorial - Codificador Compatível com JSON](../tutorial/encoder.md){.internal-link target=_blank}.
-Para converter qualquer dado para um formato compatível com JSON, leia a seção [Tutorial - JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}.
+## OpenAPI Metadata - Docs { #openapi-metadata-docs }
-## OpenAPI Metadata - Docs
+Para adicionar metadados ao seu esquema OpenAPI, incluindo licensa, versão, contato, etc, leia a seção [Tutorial - Metadados e URLs da Documentação](../tutorial/metadata.md){.internal-link target=_blank}.
-Para adicionar metadados ao seu esquema OpenAPI, incluindo licensa, versão, contato, etc, leia a seção [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md){.internal-link target=_blank}.
+## OpenAPI com URL customizada { #openapi-custom-url }
-## OpenAPI com URL customizada
+Para customizar a URL do OpenAPI (ou removê-la), leia a seção [Tutorial - Metadados e URLs da Documentação](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}.
-Para customizar a URL do OpenAPI (ou removê-la), leia a seção [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}.
+## URLs de documentação do OpenAPI { #openapi-docs-urls }
-## URLs de documentação do OpenAPI
-
-Para alterar as URLs usadas para as interfaces de usuário da documentação gerada automaticamente, leia a seção [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}.
+Para alterar as URLs usadas para as interfaces de usuário da documentação gerada automaticamente, leia a seção [Tutorial - Metadados e URLs da Documentação](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}.
diff --git a/docs/pt/docs/how-to/graphql.md b/docs/pt/docs/how-to/graphql.md
index ef0bad7f67..98266cc288 100644
--- a/docs/pt/docs/how-to/graphql.md
+++ b/docs/pt/docs/how-to/graphql.md
@@ -1,4 +1,4 @@
-# GraphQL
+# GraphQL { #graphql }
Como o **FastAPI** é baseado no padrão **ASGI**, é muito fácil integrar qualquer biblioteca **GraphQL** também compatível com ASGI.
@@ -14,7 +14,7 @@ Certifique-se de avaliar se os **benefícios** para o seu caso de uso compensam
///
-## Bibliotecas GraphQL
+## Bibliotecas GraphQL { #graphql-libraries }
Aqui estão algumas das bibliotecas **GraphQL** que têm suporte **ASGI**. Você pode usá-las com **FastAPI**:
@@ -27,21 +27,21 @@ Aqui estão algumas das bibliotecas **GraphQL** que têm suporte **ASGI**. Você
* Graphene
* Com starlette-graphene3
-## GraphQL com Strawberry
+## GraphQL com Strawberry { #graphql-with-strawberry }
-Se você precisar ou quiser trabalhar com **GraphQL**, **Strawberry** é a biblioteca **recomendada** pois tem o design mais próximo ao design do **FastAPI**, ela é toda baseada em **type annotations**.
+Se você precisar ou quiser trabalhar com **GraphQL**, **Strawberry** é a biblioteca **recomendada** pois tem o design mais próximo ao design do **FastAPI**, ela é toda baseada em **anotações de tipo**.
Dependendo do seu caso de uso, você pode preferir usar uma biblioteca diferente, mas se você me perguntasse, eu provavelmente sugeriria que você experimentasse o **Strawberry**.
Aqui está uma pequena prévia de como você poderia integrar Strawberry com FastAPI:
-{* ../../docs_src/graphql/tutorial001.py hl[3,22,25:26] *}
+{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
Você pode aprender mais sobre Strawberry na documentação do Strawberry.
E também na documentação sobre Strawberry com FastAPI.
-## Antigo `GraphQLApp` do Starlette
+## Antigo `GraphQLApp` do Starlette { #older-graphqlapp-from-starlette }
Versões anteriores do Starlette incluiam uma classe `GraphQLApp` para integrar com Graphene.
@@ -49,11 +49,11 @@ Ela foi descontinuada do Starlette, mas se você tem código que a utilizava, vo
/// tip | Dica
-Se você precisa de GraphQL, eu ainda recomendaria que você desse uma olhada no Strawberry, pois ele é baseado em type annotations em vez de classes e tipos personalizados.
+Se você precisa de GraphQL, eu ainda recomendaria que você desse uma olhada no Strawberry, pois ele é baseado em anotações de tipo em vez de classes e tipos personalizados.
///
-## Saiba Mais
+## Saiba Mais { #learn-more }
Você pode aprender mais sobre **GraphQL** na documentação oficial do GraphQL.
diff --git a/docs/pt/docs/how-to/index.md b/docs/pt/docs/how-to/index.md
index 6747b01c7c..a3d16380fe 100644
--- a/docs/pt/docs/how-to/index.md
+++ b/docs/pt/docs/how-to/index.md
@@ -1,12 +1,12 @@
-# Como Fazer - Exemplos Práticos
+# Como Fazer - Receitas { #how-to-recipes }
-Aqui você encontrará diferentes exemplos práticos ou tutoriais de "como fazer" para vários tópicos.
+Aqui você encontrará diferentes exemplos práticos ou tutoriais de "como fazer" para **vários tópicos**.
A maioria dessas ideias será mais ou menos **independente**, e na maioria dos casos você só precisará estudá-las se elas se aplicarem diretamente ao **seu projeto**.
Se algo parecer interessante e útil para o seu projeto, vá em frente e dê uma olhada. Caso contrário, você pode simplesmente ignorá-lo.
-/// tip
+/// tip | Dica
Se você deseja **aprender FastAPI** de forma estruturada (recomendado), leia capítulo por capítulo [Tutorial - Guia de Usuário](../tutorial/index.md){.internal-link target=_blank} em vez disso.
diff --git a/docs/pt/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/pt/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
new file mode 100644
index 0000000000..2a2659a03d
--- /dev/null
+++ b/docs/pt/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
@@ -0,0 +1,133 @@
+# Migrar do Pydantic v1 para o Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 }
+
+Se você tem uma aplicação FastAPI antiga, pode estar usando o Pydantic versão 1.
+
+O FastAPI tem suporte ao Pydantic v1 ou v2 desde a versão 0.100.0.
+
+Se você tiver o Pydantic v2 instalado, ele será utilizado. Se, em vez disso, tiver o Pydantic v1, será ele que será utilizado.
+
+O Pydantic v1 está agora descontinuado e o suporte a ele será removido nas próximas versões do FastAPI, você deveria migrar para o Pydantic v2. Assim, você terá as funcionalidades, melhorias e correções mais recentes.
+
+/// warning | Atenção
+
+Além disso, a equipe do Pydantic interrompeu o suporte ao Pydantic v1 para as versões mais recentes do Python, a partir do **Python 3.14**.
+
+Se quiser usar as funcionalidades mais recentes do Python, você precisará garantir que usa o Pydantic v2.
+
+///
+
+Se você tem uma aplicação FastAPI antiga com Pydantic v1, aqui vou mostrar como migrá-la para o Pydantic v2 e as **novas funcionalidades no FastAPI 0.119.0** para ajudar em uma migração gradual.
+
+## Guia oficial { #official-guide }
+
+O Pydantic tem um Guia de Migração oficial do v1 para o v2.
+
+Ele também inclui o que mudou, como as validações agora são mais corretas e rigorosas, possíveis ressalvas, etc.
+
+Você pode lê-lo para entender melhor o que mudou.
+
+## Testes { #tests }
+
+Garanta que você tenha [testes](../tutorial/testing.md){.internal-link target=_blank} para sua aplicação e que os execute na integração contínua (CI).
+
+Assim, você pode fazer a atualização e garantir que tudo continua funcionando como esperado.
+
+## `bump-pydantic` { #bump-pydantic }
+
+Em muitos casos, quando você usa modelos Pydantic regulares sem personalizações, será possível automatizar a maior parte do processo de migração do Pydantic v1 para o Pydantic v2.
+
+Você pode usar o `bump-pydantic` da própria equipe do Pydantic.
+
+Essa ferramenta ajuda a alterar automaticamente a maior parte do código que precisa ser modificado.
+
+Depois disso, você pode rodar os testes e verificar se tudo funciona. Se funcionar, está concluído. 😎
+
+## Pydantic v1 no v2 { #pydantic-v1-in-v2 }
+
+O Pydantic v2 inclui tudo do Pydantic v1 como um submódulo `pydantic.v1`.
+
+Isso significa que você pode instalar a versão mais recente do Pydantic v2 e importar e usar os componentes antigos do Pydantic v1 a partir desse submódulo, como se tivesse o Pydantic v1 antigo instalado.
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *}
+
+### Suporte do FastAPI ao Pydantic v1 no v2 { #fastapi-support-for-pydantic-v1-in-v2 }
+
+Desde o FastAPI 0.119.0, há também suporte parcial ao Pydantic v1 a partir de dentro do Pydantic v2, para facilitar a migração para o v2.
+
+Assim, você pode atualizar o Pydantic para a versão 2 mais recente e alterar os imports para usar o submódulo `pydantic.v1`, e em muitos casos tudo simplesmente funcionará.
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *}
+
+/// warning | Atenção
+
+Tenha em mente que, como a equipe do Pydantic não oferece mais suporte ao Pydantic v1 nas versões recentes do Python, a partir do Python 3.14, o uso de `pydantic.v1` também não é suportado no Python 3.14 e superiores.
+
+///
+
+### Pydantic v1 e v2 na mesma aplicação { #pydantic-v1-and-v2-on-the-same-app }
+
+Não é suportado pelo Pydantic ter um modelo do Pydantic v2 com campos próprios definidos como modelos do Pydantic v1, ou vice-versa.
+
+```mermaid
+graph TB
+ subgraph "❌ Not Supported"
+ direction TB
+ subgraph V2["Pydantic v2 Model"]
+ V1Field["Pydantic v1 Model"]
+ end
+ subgraph V1["Pydantic v1 Model"]
+ V2Field["Pydantic v2 Model"]
+ end
+ end
+
+ style V2 fill:#f9fff3
+ style V1 fill:#fff6f0
+ style V1Field fill:#fff6f0
+ style V2Field fill:#f9fff3
+```
+
+...but, you can have separated models using Pydantic v1 and v2 in the same app.
+
+```mermaid
+graph TB
+ subgraph "✅ Supported"
+ direction TB
+ subgraph V2["Pydantic v2 Model"]
+ V2Field["Pydantic v2 Model"]
+ end
+ subgraph V1["Pydantic v1 Model"]
+ V1Field["Pydantic v1 Model"]
+ end
+ end
+
+ style V2 fill:#f9fff3
+ style V1 fill:#fff6f0
+ style V1Field fill:#fff6f0
+ style V2Field fill:#f9fff3
+```
+
+Em alguns casos, é até possível ter modelos Pydantic v1 e v2 na mesma operação de rota na sua aplicação FastAPI:
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *}
+
+No exemplo acima, o modelo de entrada é um modelo Pydantic v1, e o modelo de saída (definido em `response_model=ItemV2`) é um modelo Pydantic v2.
+
+### Parâmetros do Pydantic v1 { #pydantic-v1-parameters }
+
+Se você precisar usar algumas das ferramentas específicas do FastAPI para parâmetros como `Body`, `Query`, `Form` etc. com modelos do Pydantic v1, pode importá-las de `fastapi.temp_pydantic_v1_params` enquanto conclui a migração para o Pydantic v2:
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *}
+
+### Migre em etapas { #migrate-in-steps }
+
+/// tip | Dica
+
+Primeiro tente com o `bump-pydantic`; se seus testes passarem e isso funcionar, então você concluiu tudo com um único comando. ✨
+
+///
+
+Se o `bump-pydantic` não funcionar para o seu caso, você pode usar o suporte a modelos Pydantic v1 e v2 na mesma aplicação para fazer a migração para o Pydantic v2 gradualmente.
+
+Você poderia primeiro atualizar o Pydantic para usar a versão 2 mais recente e alterar os imports para usar `pydantic.v1` para todos os seus modelos.
+
+Depois, você pode começar a migrar seus modelos do Pydantic v1 para o v2 em grupos, em etapas graduais. 🚶
diff --git a/docs/pt/docs/how-to/separate-openapi-schemas.md b/docs/pt/docs/how-to/separate-openapi-schemas.md
index 291b0e1639..8855934fd9 100644
--- a/docs/pt/docs/how-to/separate-openapi-schemas.md
+++ b/docs/pt/docs/how-to/separate-openapi-schemas.md
@@ -1,4 +1,4 @@
-# Esquemas OpenAPI Separados para Entrada e Saída ou Não
+# Esquemas OpenAPI Separados para Entrada e Saída ou Não { #separate-openapi-schemas-for-input-and-output-or-not }
Ao usar **Pydantic v2**, o OpenAPI gerado é um pouco mais exato e **correto** do que antes. 😎
@@ -6,21 +6,21 @@ Inclusive, em alguns casos, ele terá até **dois JSON Schemas** no OpenAPI para
Vamos ver como isso funciona e como alterar se for necessário.
-## Modelos Pydantic para Entrada e Saída
+## Modelos Pydantic para Entrada e Saída { #pydantic-models-for-input-and-output }
Digamos que você tenha um modelo Pydantic com valores padrão, como este:
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *}
-### Modelo para Entrada
+### Modelo para Entrada { #model-for-input }
Se você usar esse modelo como entrada, como aqui:
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *}
-... então o campo `description` não será obrigatório. Porque ele tem um valor padrão de `None`.
+... então o campo `description` **não será obrigatório**. Porque ele tem um valor padrão de `None`.
-### Modelo de Entrada na Documentação
+### Modelo de Entrada na Documentação { #input-model-in-docs }
Você pode confirmar que na documentação, o campo `description` não tem um **asterisco vermelho**, não é marcado como obrigatório:
@@ -28,7 +28,7 @@ Você pode confirmar que na documentação, o campo `description` não tem um **
Framework FastAPI, alta performance, fácil de aprender, fácil de codar, pronto para produção
@@ -27,7 +27,7 @@
---
-**Documentação**: https://fastapi.tiangolo.com
+**Documentação**: https://fastapi.tiangolo.com
**Código fonte**: https://github.com/fastapi/fastapi
@@ -40,32 +40,38 @@ Os recursos chave são:
* **Rápido**: alta performance, equivalente a **NodeJS** e **Go** (graças ao Starlette e Pydantic). [Um dos frameworks mais rápidos disponíveis](#performance).
* **Rápido para codar**: Aumenta a velocidade para desenvolver recursos entre 200% a 300%. *
* **Poucos bugs**: Reduz cerca de 40% de erros induzidos por humanos (desenvolvedores). *
-* **Intuitivo**: Grande suporte a _IDEs_. _Auto-Complete_ em todos os lugares. Menos tempo debugando.
+* **Intuitivo**: Grande suporte a _IDEs_. Preenchimento automático em todos os lugares. Menos tempo debugando.
* **Fácil**: Projetado para ser fácil de aprender e usar. Menos tempo lendo documentação.
-* **Enxuto**: Minimize duplicação de código. Múltiplos recursos para cada declaração de parâmetro. Menos bugs.
+* **Enxuto**: Minimize duplicação de código. Múltiplas funcionalidades para cada declaração de parâmetro. Menos bugs.
* **Robusto**: Tenha código pronto para produção. E com documentação interativa automática.
-* **Baseado em padrões**: Baseado em (e totalmente compatível com) os padrões abertos para APIs: OpenAPI (anteriormente conhecido como Swagger) e _JSON Schema_.
+* **Baseado em padrões**: Baseado em (e totalmente compatível com) os padrões abertos para APIs: OpenAPI (anteriormente conhecido como Swagger) e JSON Schema.
* estimativas baseadas em testes realizados com equipe interna de desenvolvimento, construindo aplicações em produção.
-## Patrocinadores Ouro
+## Patrocinadores { #sponsors }
-{% if sponsors %}
+### Patrocinador Keystone { #keystone-sponsor }
+
+{% for sponsor in sponsors.keystone -%}
+
+{% endfor -%}
+
+### Patrocinadores Ouro e Prata { #gold-and-silver-sponsors }
+
{% for sponsor in sponsors.gold -%}
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
{% endfor %}
-{% endif %}
-Outros patrocinadores
+Outros patrocinadores
-## Opiniões
+## Opiniões { #opinions }
"*[...] Estou usando **FastAPI** muito esses dias. [...] Estou na verdade planejando utilizar ele em todos os times de **serviços _Machine Learning_ na Microsoft**. Alguns deles estão sendo integrados no _core_ do produto **Windows** e alguns produtos **Office**.*"
@@ -111,24 +117,24 @@ Os recursos chave são:
---
-## **Typer**, o FastAPI das interfaces de linhas de comando
+## **Typer**, o FastAPI das interfaces de linhas de comando { #typer-the-fastapi-of-clis }
-Se você estiver construindo uma aplicação _CLI_ para ser utilizada em um terminal ao invés de uma aplicação web, dê uma olhada no **Typer**.
+Se você estiver construindo uma aplicação CLI para ser utilizada em um terminal ao invés de uma aplicação web, dê uma olhada no **Typer**.
**Typer** é o irmão menor do FastAPI. E seu propósito é ser o **FastAPI das _CLIs_**. ⌨️ 🚀
-## Requisitos
+## Requisitos { #requirements }
FastAPI está nos ombros de gigantes:
* Starlette para as partes web.
* Pydantic para a parte de dados.
-## Instalação
+## Instalação { #installation }
-Crie e ative um ambiente virtual, e então instale o FastAPI:
+Crie e ative um ambiente virtual e então instale o FastAPI:
httpx - Obrigatório caso você queira utilizar o `TestClient`.
* jinja2 - Obrigatório se você quer utilizar a configuração padrão de templates.
-* python-multipart - Obrigatório se você deseja suporte a "parsing" de formulário, com `request.form()`.
+* python-multipart - Obrigatório se você deseja suporte a "parsing" de formulário, com `request.form()`.
-Utilizado pelo FastAPI / Starlette:
+Utilizado pelo FastAPI:
* uvicorn - para o servidor que carrega e serve a sua aplicação. Isto inclui `uvicorn[standard]`, que inclui algumas dependências (e.g. `uvloop`) necessárias para servir em alta performance.
-* `fastapi-cli` - que disponibiliza o comando `fastapi`.
+* `fastapi-cli[standard]` - que disponibiliza o comando `fastapi`.
+ * Isso inclui `fastapi-cloud-cli`, que permite implantar sua aplicação FastAPI na FastAPI Cloud.
-### Sem as dependências `standard`
+### Sem as dependências `standard` { #without-standard-dependencies }
Se você não deseja incluir as dependências opcionais `standard`, você pode instalar utilizando `pip install fastapi` ao invés de `pip install "fastapi[standard]"`.
-### Dpendências opcionais adicionais
+### Sem o `fastapi-cloud-cli` { #without-fastapi-cloud-cli }
+
+Se você quiser instalar o FastAPI com as dependências padrão, mas sem o `fastapi-cloud-cli`, você pode instalar com `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
+
+### Dependências opcionais adicionais { #additional-optional-dependencies }
Existem algumas dependências adicionais que você pode querer instalar.
@@ -492,6 +554,6 @@ Dependências opcionais adicionais do FastAPI:
* orjson - Obrigatório se você deseja utilizar o `ORJSONResponse`.
* ujson - Obrigatório se você deseja utilizar o `UJSONResponse`.
-## Licença
+## Licença { #license }
Esse projeto é licenciado sob os termos da licença MIT.
diff --git a/docs/pt/docs/learn/index.md b/docs/pt/docs/learn/index.md
index b9a7f5972f..1f04929f7d 100644
--- a/docs/pt/docs/learn/index.md
+++ b/docs/pt/docs/learn/index.md
@@ -1,5 +1,5 @@
-# Aprender
+# Aprender { #learn }
-Nesta parte da documentação encontramos as seções introdutórias e os tutoriais para aprendermos como usar o **FastAPI**.
+Aqui estão as seções introdutórias e os tutoriais para aprender o **FastAPI**.
-Nós poderíamos considerar isto um **livro**, **curso**, a maneira **oficial** e recomendada de aprender o FastAPI. 😎
+Pode considerar isto um **livro**, um **curso**, a forma **oficial** e recomendada de aprender o FastAPI. 😎
diff --git a/docs/pt/docs/project-generation.md b/docs/pt/docs/project-generation.md
index e337ad762e..419a39879f 100644
--- a/docs/pt/docs/project-generation.md
+++ b/docs/pt/docs/project-generation.md
@@ -1,4 +1,4 @@
-# Full Stack FastAPI Template
+# Full Stack FastAPI Template { #full-stack-fastapi-template }
_Templates_, embora tipicamente venham com alguma configuração específica, são desenhados para serem flexíveis e customizáveis. Isso permite que você os modifique e adapte para as especificações do seu projeto, fazendo-os um excelente ponto de partida. 🏁
@@ -6,15 +6,15 @@ Você pode usar esse _template_ para começar, já que ele inclui várias config
Repositório GitHub: Full Stack FastAPI Template
-## Full Stack FastAPI Template - Pilha de Tecnologias e Recursos
+## Full Stack FastAPI Template - Pilha de Tecnologias e Recursos { #full-stack-fastapi-template-technology-stack-and-features }
-- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) para a API do backend em Python.
+- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/pt) para a API do backend em Python.
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) para as interações do Python com bancos de dados SQL (ORM).
- 🔍 [Pydantic](https://docs.pydantic.dev), usado pelo FastAPI, para validação de dados e gerenciamento de configurações.
- 💾 [PostgreSQL](https://www.postgresql.org) como banco de dados SQL.
- 🚀 [React](https://react.dev) para o frontend.
- - 💃 Usando TypeScript, hooks, [Vite](https://vitejs.dev), e outras partes de uma _stack_ frontend moderna.
- - 🎨 [Chakra UI](https://chakra-ui.com) para os componentes de frontend.
+ - 💃 Usando TypeScript, hooks, Vite, e outras partes de uma _stack_ frontend moderna.
+ - 🎨 [Tailwind CSS](https://tailwindcss.com) e [shadcn/ui](https://ui.shadcn.com) para os componentes de frontend.
- 🤖 Um cliente frontend automaticamente gerado.
- 🧪 [Playwright](https://playwright.dev) para testes Ponta-a-Ponta.
- 🦇 Suporte para modo escuro.
diff --git a/docs/pt/docs/python-types.md b/docs/pt/docs/python-types.md
index 90a361f403..fc983d1df1 100644
--- a/docs/pt/docs/python-types.md
+++ b/docs/pt/docs/python-types.md
@@ -1,4 +1,4 @@
-# Introdução aos tipos Python
+# Introdução aos tipos Python { #python-types-intro }
O Python possui suporte para "dicas de tipo" ou "type hints" (também chamado de "anotações de tipo" ou "type annotations")
@@ -18,11 +18,11 @@ Se você é um especialista em Python e já sabe tudo sobre type hints, pule par
///
-## Motivação
+## Motivação { #motivation }
Vamos começar com um exemplo simples:
-{* ../../docs_src/python_types/tutorial001.py *}
+{* ../../docs_src/python_types/tutorial001_py39.py *}
A chamada deste programa gera:
@@ -36,9 +36,9 @@ A função faz o seguinte:
* Converte a primeira letra de cada uma em maiúsculas com `title()`.
* Concatena com um espaço no meio.
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
+{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
-### Edite-o
+### Edite-o { #edit-it }
É um programa muito simples.
@@ -58,7 +58,7 @@ Mas, infelizmente, você não obtém nada útil:
-### Adicionar tipos
+### Adicionar tipos { #add-types }
Vamos modificar uma única linha da versão anterior.
@@ -78,7 +78,7 @@ para:
Esses são os "type hints":
-{* ../../docs_src/python_types/tutorial002.py hl[1] *}
+{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
Isso não é o mesmo que declarar valores padrão como seria com:
@@ -102,11 +102,11 @@ Com isso, você pode rolar, vendo as opções, até encontrar o que "soa familia
-## Mais motivação
+## Mais motivação { #more-motivation }
Verifique esta função, ela já possui type hints:
-{* ../../docs_src/python_types/tutorial003.py hl[1] *}
+{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}
Como o editor conhece os tipos de variáveis, você não obtém apenas o preenchimento automático, mas também as verificações de erro:
@@ -114,15 +114,15 @@ Como o editor conhece os tipos de variáveis, você não obtém apenas o preench
Agora você sabe que precisa corrigí-lo, converta `age` em uma string com `str(age)`:
-{* ../../docs_src/python_types/tutorial004.py hl[2] *}
+{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
-## Declarando Tipos
+## Declarando Tipos { #declaring-types }
Você acabou de ver o local principal para declarar type hints. Como parâmetros de função.
Este também é o principal local em que você os usaria com o **FastAPI**.
-### Tipos simples
+### Tipos simples { #simple-types }
Você pode declarar todos os tipos padrão de Python, não apenas `str`.
@@ -133,9 +133,9 @@ Você pode usar, por exemplo:
* `bool`
* `bytes`
-{* ../../docs_src/python_types/tutorial005.py hl[1] *}
+{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
-### Tipos genéricos com parâmetros de tipo
+### Tipos genéricos com parâmetros de tipo { #generic-types-with-type-parameters }
Existem algumas estruturas de dados que podem conter outros valores, como `dict`, `list`, `set` e `tuple`. E os valores internos também podem ter seu próprio tipo.
@@ -143,7 +143,7 @@ Estes tipos que possuem tipos internos são chamados de tipos "**genéricos**".
Para declarar esses tipos e os tipos internos, você pode usar o módulo Python padrão `typing`. Ele existe especificamente para suportar esses type hints.
-#### Versões mais recentes do Python
+#### Versões mais recentes do Python { #newer-versions-of-python }
A sintaxe utilizando `typing` é **compatível** com todas as versões, desde o Python 3.6 até as últimas, incluindo o Python 3.9, 3.10, etc.
@@ -157,60 +157,28 @@ Por exemplo, "**Python 3.6+**" significa que é compatível com o Python 3.6 ou
Se você pode utilizar a **versão mais recente do Python**, utilize os exemplos para as últimas versões. Eles terão as **melhores e mais simples sintaxes**, como por exemplo, "**Python 3.10+**".
-#### List
+#### List { #list }
Por exemplo, vamos definir uma variável para ser uma `list` de `str`.
-//// tab | Python 3.9+
+Declare a variável, com a mesma sintaxe com dois pontos (`:`).
-Declare uma variável com a mesma sintaxe com dois pontos (`:`)
+Como o tipo, coloque `list`.
-Como tipo, coloque `list`.
+Como a lista é um tipo que contém tipos internos, você os coloca entre colchetes:
-Como a lista é o tipo que contém algum tipo interno, você coloca o tipo dentro de colchetes:
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-De `typing`, importe `List` (com o `L` maiúsculo):
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-Declare uma variável com a mesma sintaxe com dois pontos (`:`)
-
-Como tipo, coloque o `List` que você importou de `typing`.
-
-Como a lista é o tipo que contém algum tipo interno, você coloca o tipo dentro de colchetes:
-
-```Python hl_lines="4"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
/// info | Informação
Estes tipos internos dentro dos colchetes são chamados "parâmetros de tipo" (type parameters).
-Neste caso, `str` é o parâmetro de tipo passado para `List` (ou `list` no Python 3.9 ou superior).
+Neste caso, `str` é o parâmetro de tipo passado para `list`.
///
Isso significa: "a variável `items` é uma `list`, e cada um dos itens desta lista é uma `str`".
-/// tip | Dica
-
-Se você usa o Python 3.9 ou superior, você não precisa importar `List` de `typing`. Você pode utilizar o mesmo tipo `list` no lugar.
-
-///
-
Ao fazer isso, seu editor pode fornecer suporte mesmo durante o processamento de itens da lista:
@@ -221,32 +189,18 @@ Observe que a variável `item` é um dos elementos da lista `items`.
E, ainda assim, o editor sabe que é um `str` e fornece suporte para isso.
-#### Tuple e Set
+#### Tuple e Set { #tuple-and-set }
Você faria o mesmo para declarar `tuple`s e `set`s:
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial007_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial007.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
Isso significa que:
* A variável `items_t` é uma `tuple` com 3 itens, um `int`, outro `int` e uma `str`.
* A variável `items_s` é um `set`, e cada um de seus itens é do tipo `bytes`.
-#### Dict
+#### Dict { #dict }
Para definir um `dict`, você passa 2 parâmetros de tipo, separados por vírgulas.
@@ -254,35 +208,21 @@ O primeiro parâmetro de tipo é para as chaves do `dict`.
O segundo parâmetro de tipo é para os valores do `dict`:
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
Isso significa que:
-* A variável `prices` é um dict`:
+* A variável `prices` é um `dict`:
* As chaves deste `dict` são do tipo `str` (digamos, o nome de cada item).
* Os valores deste `dict` são do tipo `float` (digamos, o preço de cada item).
-#### Union
+#### Union { #union }
Você pode declarar que uma variável pode ser de qualquer um dentre **diversos tipos**. Por exemplo, um `int` ou um `str`.
No Python 3.6 e superior (incluindo o Python 3.10), você pode utilizar o tipo `Union` de `typing`, e colocar dentro dos colchetes os possíveis tipos aceitáveis.
-No Python 3.10 também existe uma **nova sintaxe** onde você pode colocar os possívels tipos separados por uma barra vertical (`|`).
+No Python 3.10 também existe uma **nova sintaxe** onde você pode colocar os possíveis tipos separados por uma barra vertical (`|`).
//// tab | Python 3.10+
@@ -292,25 +232,24 @@ No Python 3.10 também existe uma **nova sintaxe** onde você pode colocar os po
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008b.py!}
+{!> ../../docs_src/python_types/tutorial008b_py39.py!}
```
////
Em ambos os casos, isso significa que `item` poderia ser um `int` ou um `str`.
-
-#### Possívelmente `None`
+#### Possivelmente `None` { #possibly-none }
Você pode declarar que um valor pode ter um tipo, como `str`, mas que ele também pode ser `None`.
No Python 3.6 e superior (incluindo o Python 3.10) você pode declará-lo importando e utilizando `Optional` do módulo `typing`.
```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
+{!../../docs_src/python_types/tutorial009_py39.py!}
```
O uso de `Optional[str]` em vez de apenas `str` permitirá que o editor o ajude a detectar erros, onde você pode estar assumindo que um valor é sempre um `str`, quando na verdade também pode ser `None`.
@@ -327,23 +266,23 @@ Isso também significa que no Python 3.10, você pode utilizar `Something | None
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009.py!}
+{!> ../../docs_src/python_types/tutorial009_py39.py!}
```
////
-//// tab | Python 3.8+ alternative
+//// tab | Python 3.9+ alternativa
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009b.py!}
+{!> ../../docs_src/python_types/tutorial009b_py39.py!}
```
////
-#### Utilizando `Union` ou `Optional`
+#### Utilizando `Union` ou `Optional` { #using-union-or-optional }
Se você está utilizando uma versão do Python abaixo da 3.10, aqui vai uma dica do meu ponto de vista bem **subjetivo**:
@@ -358,15 +297,15 @@ Isso é apenas sobre palavras e nomes. Mas estas palavras podem afetar como os s
Por exemplo, vamos pegar esta função:
-{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *}
+{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
-O paâmetro `name` é definido como `Optional[str]`, mas ele **não é opcional**, você não pode chamar a função sem o parâmetro:
+O parâmetro `name` é definido como `Optional[str]`, mas ele **não é opcional**, você não pode chamar a função sem o parâmetro:
```Python
say_hi() # Oh, no, this throws an error! 😱
```
-O parâmetro `name` **ainda é obrigatório** (não *opicional*) porque ele não possui um valor padrão. Mesmo assim, `name` aceita `None` como valor:
+O parâmetro `name` **ainda é obrigatório** (não *opcional*) porque ele não possui um valor padrão. Mesmo assim, `name` aceita `None` como valor:
```Python
say_hi(name=None) # This works, None is valid 🎉
@@ -378,7 +317,7 @@ A boa notícia é, quando você estiver no Python 3.10 você não precisará se
E então você não precisará mais se preocupar com nomes como `Optional` e `Union`. 😎
-#### Tipos genéricos
+#### Tipos genéricos { #generic-types }
Esses tipos que usam parâmetros de tipo entre colchetes são chamados **tipos genéricos** ou **genéricos**. Por exemplo:
@@ -391,11 +330,11 @@ Você pode utilizar os mesmos tipos internos como genéricos (com colchetes e ti
* `set`
* `dict`
-E o mesmo como no Python 3.8, do módulo `typing`:
+E o mesmo que com versões anteriores do Python, do módulo `typing`:
* `Union`
-* `Optional` (o mesmo que com o 3.8)
-* ...entro outros.
+* `Optional`
+* ...entre outros.
No Python 3.10, como uma alternativa para a utilização dos genéricos `Union` e `Optional`, você pode usar a barra vertical (`|`) para declarar uniões de tipos. Isso é muito melhor e mais simples.
@@ -410,39 +349,27 @@ Você pode utilizar os mesmos tipos internos como genéricos (com colchetes e ti
* `set`
* `dict`
-E o mesmo como no Python 3.8, do módulo `typing`:
+E genéricos do módulo `typing`:
* `Union`
* `Optional`
-* ...entro outros.
+* ...entre outros.
////
-//// tab | Python 3.8+
-
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Union`
-* `Optional`
-* ...entro outros.
-
-////
-
-### Classes como tipos
+### Classes como tipos { #classes-as-types }
Você também pode declarar uma classe como o tipo de uma variável.
Digamos que você tenha uma classe `Person`, com um nome:
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
+{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
Então você pode declarar que uma variável é do tipo `Person`:
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
+{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
-E então, novamente, você recebe todo o suporte do editor:
+E então, novamente, você recebe todo o apoio do editor:
@@ -450,7 +377,7 @@ Perceba que isso significa que "`one_person` é uma **instância** da classe `Pe
Isso não significa que "`one_person` é a **classe** chamada `Person`".
-## Modelos Pydantic
+## Modelos Pydantic { #pydantic-models }
O Pydantic é uma biblioteca Python para executar a validação de dados.
@@ -462,31 +389,9 @@ Em seguida, você cria uma instância dessa classe com alguns valores e ela os v
E você recebe todo o suporte do editor com esse objeto resultante.
-Retirado dos documentos oficiais dos Pydantic:
+Um exemplo da documentação oficial do Pydantic:
-//// tab | Python 3.10+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py310.py!}
-```
-
-////
-
-//// tab | Python 3.9+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial011_py310.py *}
/// info | Informação
@@ -504,32 +409,13 @@ O Pydantic tem um comportamento especial quando você usa `Optional` ou `Union[S
///
-
-## Type Hints com Metadados de Anotações
+## Type Hints com Metadados de Anotações { #type-hints-with-metadata-annotations }
O Python possui uma funcionalidade que nos permite incluir **metadados adicionais** nos type hints utilizando `Annotated`.
-//// tab | Python 3.9+
+Desde o Python 3.9, `Annotated` faz parte da biblioteca padrão, então você pode importá-lo de `typing`.
-No Python 3.9, `Annotated` é parte da biblioteca padrão, então você pode importá-lo de `typing`.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-Em versões abaixo do Python 3.9, você importa `Annotated` de `typing_extensions`.
-
-Ele já estará instalado com o **FastAPI**.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
O Python em si não faz nada com este `Annotated`. E para editores e outras ferramentas, o tipo ainda é `str`.
@@ -549,8 +435,7 @@ E também que o seu código será muito compatível com diversas outras ferramen
///
-
-## Type hints no **FastAPI**
+## Type hints no **FastAPI** { #type-hints-in-fastapi }
O **FastAPI** aproveita esses type hints para fazer várias coisas.
@@ -574,6 +459,6 @@ O importante é que, usando tipos padrão de Python, em um único local (em vez
/// info | Informação
-Se você já passou por todo o tutorial e voltou para ver mais sobre os tipos, um bom recurso é a "cheat sheet" do `mypy` .
+Se você já passou por todo o tutorial e voltou para ver mais sobre os tipos, um bom recurso é a "cheat sheet" do `mypy` .
///
diff --git a/docs/pt/docs/resources/index.md b/docs/pt/docs/resources/index.md
index 6eff8f9e78..24cea95641 100644
--- a/docs/pt/docs/resources/index.md
+++ b/docs/pt/docs/resources/index.md
@@ -1,3 +1,3 @@
-# Recursos
+# Recursos { #resources }
-Material complementar, links externos, artigos e muito mais. ✈️
+Material complementar, links externos e mais. ✈️
diff --git a/docs/pt/docs/tutorial/background-tasks.md b/docs/pt/docs/tutorial/background-tasks.md
index b8ab58cda3..34805364b4 100644
--- a/docs/pt/docs/tutorial/background-tasks.md
+++ b/docs/pt/docs/tutorial/background-tasks.md
@@ -1,84 +1,85 @@
-# Tarefas em segundo plano
+# Tarefas em segundo plano { #background-tasks }
-Você pode definir tarefas em segundo plano a serem executadas _ após _ retornar uma resposta.
+Você pode definir tarefas em segundo plano para serem executadas *após* retornar uma resposta.
-Isso é útil para operações que precisam acontecer após uma solicitação, mas que o cliente realmente não precisa esperar a operação ser concluída para receber a resposta.
+Isso é útil para operações que precisam acontecer após uma request, mas que o cliente não precisa realmente esperar a operação terminar antes de receber a resposta.
Isso inclui, por exemplo:
-- Envio de notificações por email após a realização de uma ação:
- - Como conectar-se a um servidor de e-mail e enviar um e-mail tende a ser "lento" (vários segundos), você pode retornar a resposta imediatamente e enviar a notificação por e-mail em segundo plano.
-- Processando dados:
- - Por exemplo, digamos que você receba um arquivo que deve passar por um processo lento, você pode retornar uma resposta de "Aceito" (HTTP 202) e processá-lo em segundo plano.
+* Notificações por e-mail enviadas após realizar uma ação:
+ * Como conectar-se a um servidor de e-mail e enviar um e-mail tende a ser “lento” (vários segundos), você pode retornar a resposta imediatamente e enviar a notificação por e-mail em segundo plano.
+* Processamento de dados:
+ * Por exemplo, digamos que você receba um arquivo que precisa passar por um processo lento; você pode retornar uma resposta “Accepted” (HTTP 202) e processar o arquivo em segundo plano.
-## Usando `BackgroundTasks`
+## Usando `BackgroundTasks` { #using-backgroundtasks }
-Primeiro, importe `BackgroundTasks` e defina um parâmetro em sua _função de operação de caminho_ com uma declaração de tipo de `BackgroundTasks`:
+Primeiro, importe `BackgroundTasks` e defina um parâmetro na sua *função de operação de rota* com uma declaração de tipo `BackgroundTasks`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
O **FastAPI** criará o objeto do tipo `BackgroundTasks` para você e o passará como esse parâmetro.
-## Criar uma função de tarefa
+## Crie uma função de tarefa { #create-a-task-function }
-Crie uma função a ser executada como tarefa em segundo plano.
+Crie uma função para ser executada como a tarefa em segundo plano.
É apenas uma função padrão que pode receber parâmetros.
-Pode ser uma função `async def` ou `def` normal, o **FastAPI** saberá como lidar com isso corretamente.
+Pode ser uma função `async def` ou um `def` normal, o **FastAPI** saberá como lidar com isso corretamente.
-Nesse caso, a função de tarefa gravará em um arquivo (simulando o envio de um e-mail).
+Neste caso, a função da tarefa escreverá em um arquivo (simulando o envio de um e-mail).
-E como a operação de gravação não usa `async` e `await`, definimos a função com `def` normal:
+E como a operação de escrita não usa `async` e `await`, definimos a função com um `def` normal:
-{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
-## Adicionar a tarefa em segundo plano
+## Adicione a tarefa em segundo plano { #add-the-background-task }
-Dentro de sua _função de operação de caminho_, passe sua função de tarefa para o objeto _tarefas em segundo plano_ com o método `.add_task()`:
+Dentro da sua *função de operação de rota*, passe sua função de tarefa para o objeto de *tarefas em segundo plano* com o método `.add_task()`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
-`.add_task()` recebe como argumentos:
+O `.add_task()` recebe como argumentos:
-- Uma função de tarefa a ser executada em segundo plano (`write_notification`).
-- Qualquer sequência de argumentos que deve ser passada para a função de tarefa na ordem (`email`).
-- Quaisquer argumentos nomeados que devem ser passados para a função de tarefa (`mensagem = "alguma notificação"`).
+* Uma função de tarefa a ser executada em segundo plano (`write_notification`).
+* Qualquer sequência de argumentos que deve ser passada para a função de tarefa na ordem (`email`).
+* Quaisquer argumentos nomeados que devem ser passados para a função de tarefa (`message="some notification"`).
-## Injeção de dependência
+## Injeção de dependências { #dependency-injection }
-Usar `BackgroundTasks` também funciona com o sistema de injeção de dependência, você pode declarar um parâmetro do tipo `BackgroundTasks` em vários níveis: em uma _função de operação de caminho_, em uma dependência (confiável), em uma subdependência, etc.
+Usar `BackgroundTasks` também funciona com o sistema de injeção de dependências; você pode declarar um parâmetro do tipo `BackgroundTasks` em vários níveis: em uma *função de operação de rota*, em uma dependência (dependable), em uma subdependência, etc.
-O **FastAPI** sabe o que fazer em cada caso e como reutilizar o mesmo objeto, de forma que todas as tarefas em segundo plano sejam mescladas e executadas em segundo plano posteriormente:
+O **FastAPI** sabe o que fazer em cada caso e como reutilizar o mesmo objeto, de forma que todas as tarefas em segundo plano sejam combinadas e executadas em segundo plano depois:
-{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *}
-Neste exemplo, as mensagens serão gravadas no arquivo `log.txt` _após_ o envio da resposta.
+{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *}
-Se houver uma consulta na solicitação, ela será gravada no log em uma tarefa em segundo plano.
+Neste exemplo, as mensagens serão escritas no arquivo `log.txt` *após* o envio da resposta.
-E então outra tarefa em segundo plano gerada na _função de operação de caminho_ escreverá uma mensagem usando o parâmetro de caminho `email`.
+Se houver uma query na request, ela será registrada em uma tarefa em segundo plano.
-## Detalhes técnicos
+E então outra tarefa em segundo plano gerada na *função de operação de rota* escreverá uma mensagem usando o parâmetro de path `email`.
+
+## Detalhes técnicos { #technical-details }
A classe `BackgroundTasks` vem diretamente de `starlette.background`.
-Ela é importada/incluída diretamente no FastAPI para que você possa importá-la do `fastapi` e evitar a importação acidental da alternativa `BackgroundTask` (sem o `s` no final) de `starlette.background`.
+Ela é importada/incluída diretamente no FastAPI para que você possa importá-la de `fastapi` e evitar importar acidentalmente a alternativa `BackgroundTask` (sem o `s` no final) de `starlette.background`.
-Usando apenas `BackgroundTasks` (e não `BackgroundTask`), é então possível usá-la como um parâmetro de _função de operação de caminho_ e deixar o **FastAPI** cuidar do resto para você, assim como ao usar o objeto `Request` diretamente.
+Usando apenas `BackgroundTasks` (e não `BackgroundTask`), é possível usá-la como um parâmetro de *função de operação de rota* e deixar o **FastAPI** cuidar do resto para você, assim como ao usar o objeto `Request` diretamente.
-Ainda é possível usar `BackgroundTask` sozinho no FastAPI, mas você deve criar o objeto em seu código e retornar uma Starlette `Response` incluindo-o.
+Ainda é possível usar `BackgroundTask` sozinho no FastAPI, mas você precisa criar o objeto no seu código e retornar uma `Response` da Starlette incluindo-o.
-Você pode ver mais detalhes na documentação oficiais da Starlette para tarefas em segundo plano .
+Você pode ver mais detalhes na documentação oficial da Starlette para tarefas em segundo plano.
-## Ressalva
+## Ressalva { #caveat }
-Se você precisa realizar cálculos pesados em segundo plano e não necessariamente precisa que seja executado pelo mesmo processo (por exemplo, você não precisa compartilhar memória, variáveis, etc), você pode se beneficiar do uso de outras ferramentas maiores, como Celery .
+Se você precisar realizar computação pesada em segundo plano e não necessariamente precisar que seja executada pelo mesmo processo (por exemplo, você não precisa compartilhar memória, variáveis, etc.), pode se beneficiar do uso de outras ferramentas maiores, como o Celery.
-Eles tendem a exigir configurações mais complexas, um gerenciador de fila de mensagens/tarefas, como RabbitMQ ou Redis, mas permitem que você execute tarefas em segundo plano em vários processos e, especialmente, em vários servidores.
+Elas tendem a exigir configurações mais complexas, um gerenciador de fila de mensagens/tarefas, como RabbitMQ ou Redis, mas permitem executar tarefas em segundo plano em vários processos e, especialmente, em vários servidores.
-Mas se você precisa acessar variáveis e objetos do mesmo aplicativo **FastAPI**, ou precisa realizar pequenas tarefas em segundo plano (como enviar uma notificação por e-mail), você pode simplesmente usar `BackgroundTasks`.
+Mas se você precisa acessar variáveis e objetos da mesma aplicação **FastAPI**, ou precisa realizar pequenas tarefas em segundo plano (como enviar uma notificação por e-mail), você pode simplesmente usar `BackgroundTasks`.
-## Recapitulando
+## Recapitulando { #recap }
-Importe e use `BackgroundTasks` com parâmetros em _funções de operação de caminho_ e dependências para adicionar tarefas em segundo plano.
+Importe e use `BackgroundTasks` com parâmetros em *funções de operação de rota* e dependências para adicionar tarefas em segundo plano.
diff --git a/docs/pt/docs/tutorial/bigger-applications.md b/docs/pt/docs/tutorial/bigger-applications.md
index b621f3c726..9dec7b1968 100644
--- a/docs/pt/docs/tutorial/bigger-applications.md
+++ b/docs/pt/docs/tutorial/bigger-applications.md
@@ -1,4 +1,4 @@
-# Aplicações Maiores - Múltiplos Arquivos
+# Aplicações Maiores - Múltiplos Arquivos { #bigger-applications-multiple-files }
Se você está construindo uma aplicação ou uma API web, é raro que você possa colocar tudo em um único arquivo.
@@ -10,7 +10,7 @@ Se você vem do Flask, isso seria o equivalente aos Blueprints do Flask.
///
-## Um exemplo de estrutura de arquivos
+## Um exemplo de estrutura de arquivos { #an-example-file-structure }
Digamos que você tenha uma estrutura de arquivos como esta:
@@ -71,7 +71,7 @@ A mesma estrutura de arquivos com comentários:
│ └── admin.py # "admin" submódulo, e.g. import app.internal.admin
```
-## `APIRouter`
+## `APIRouter` { #apirouter }
Vamos supor que o arquivo dedicado a lidar apenas com usuários seja o submódulo em `/app/routers/users.py`.
@@ -81,23 +81,19 @@ Mas ele ainda faz parte da mesma aplicação/web API **FastAPI** (faz parte do m
Você pode criar as *operações de rotas* para esse módulo usando o `APIRouter`.
-### Importar `APIRouter`
+### Importe `APIRouter` { #import-apirouter }
você o importa e cria uma "instância" da mesma maneira que faria com a classe `FastAPI`:
-```Python hl_lines="1 3" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
-### *Operações de Rota* com `APIRouter`
+### *Operações de Rota* com `APIRouter` { #path-operations-with-apirouter }
E então você o utiliza para declarar suas *operações de rota*.
Utilize-o da mesma maneira que utilizaria a classe `FastAPI`:
-```Python hl_lines="6 11 16" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
Você pode pensar em `APIRouter` como uma classe "mini `FastAPI`".
@@ -113,7 +109,7 @@ Neste exemplo, a variável é chamada de `router`, mas você pode nomeá-la como
Vamos incluir este `APIRouter` na aplicação principal `FastAPI`, mas primeiro, vamos verificar as dependências e outro `APIRouter`.
-## Dependências
+## Dependências { #dependencies }
Vemos que precisaremos de algumas dependências usadas em vários lugares da aplicação.
@@ -121,35 +117,7 @@ Então, as colocamos em seu próprio módulo de `dependencies` (`app/dependencie
Agora usaremos uma dependência simples para ler um cabeçalho `X-Token` personalizado:
-//// tab | Python 3.9+
-
-```Python hl_lines="3 6-8" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 5-7" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app_an/dependencies.py!}
-```
-
-////
-
-//// tab | Python 3.8+ non-Annotated
-
-/// tip | Dica
-
-Prefira usar a versão `Annotated` se possível.
-
-///
-
-```Python hl_lines="1 4-6" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app/dependencies.py!}
-```
-
-////
+{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
/// tip | Dica
@@ -159,7 +127,7 @@ Mas em casos reais, você obterá melhores resultados usando os [Utilitários de
///
-## Outro módulo com `APIRouter`
+## Outro módulo com `APIRouter` { #another-module-with-apirouter }
Digamos que você também tenha os endpoints dedicados a manipular "itens" do seu aplicativo no módulo em `app/routers/items.py`.
@@ -177,13 +145,11 @@ Sabemos que todas as *operações de rota* neste módulo têm o mesmo:
* Path `prefix`: `/items`.
* `tags`: (apenas uma tag: `items`).
* Extra `responses`.
-* `dependências`: todas elas precisam da dependência `X-Token` que criamos.
+* `dependencies`: todas elas precisam da dependência `X-Token` que criamos.
Então, em vez de adicionar tudo isso a cada *operação de rota*, podemos adicioná-lo ao `APIRouter`.
-```Python hl_lines="5-10 16 21" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
Como o caminho de cada *operação de rota* deve começar com `/`, como em:
@@ -199,7 +165,7 @@ Então, o prefixo neste caso é `/items`.
Também podemos adicionar uma lista de `tags` e `responses` extras que serão aplicadas a todas as *operações de rota* incluídas neste roteador.
-E podemos adicionar uma lista de `dependencies` que serão adicionadas a todas as *operações de rota* no roteador e serão executadas/resolvidas para cada solicitação feita a elas.
+E podemos adicionar uma lista de `dependencies` que serão adicionadas a todas as *operações de rota* no roteador e serão executadas/resolvidas para cada request feita a elas.
/// tip | Dica
@@ -224,17 +190,17 @@ O resultado final é que os caminhos dos itens agora são:
/// tip | Dica
-Ter `dependências` no `APIRouter` pode ser usado, por exemplo, para exigir autenticação para um grupo inteiro de *operações de rota*. Mesmo que as dependências não sejam adicionadas individualmente a cada uma delas.
+Ter `dependencies` no `APIRouter` pode ser usado, por exemplo, para exigir autenticação para um grupo inteiro de *operações de rota*. Mesmo que as dependências não sejam adicionadas individualmente a cada uma delas.
///
-/// check
+/// check | Verifique
Os parâmetros `prefix`, `tags`, `responses` e `dependencies` são (como em muitos outros casos) apenas um recurso do **FastAPI** para ajudar a evitar duplicação de código.
///
-### Importar as dependências
+### Importe as dependências { #import-the-dependencies }
Este código reside no módulo `app.routers.items`, o arquivo `app/routers/items.py`.
@@ -242,11 +208,9 @@ E precisamos obter a função de dependência do módulo `app.dependencies`, o a
Então usamos uma importação relativa com `..` para as dependências:
-```Python hl_lines="3" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
-#### Como funcionam as importações relativas
+#### Como funcionam as importações relativas { #how-relative-imports-work }
/// tip | Dica
@@ -309,15 +273,13 @@ Isso se referiria a algum pacote acima de `app/`, com seu próprio arquivo `__in
Mas agora você sabe como funciona, então você pode usar importações relativas em seus próprios aplicativos, não importa o quão complexos eles sejam. 🤓
-### Adicione algumas `tags`, `respostas` e `dependências` personalizadas
+### Adicione algumas `tags`, `responses` e `dependencies` personalizadas { #add-some-custom-tags-responses-and-dependencies }
Não estamos adicionando o prefixo `/items` nem `tags=["items"]` a cada *operação de rota* porque os adicionamos ao `APIRouter`.
-Mas ainda podemos adicionar _mais_ `tags` que serão aplicadas a uma *operação de rota* específica, e também algumas `respostas` extras específicas para essa *operação de rota*:
+Mas ainda podemos adicionar _mais_ `tags` que serão aplicadas a uma *operação de rota* específica, e também algumas `responses` extras específicas para essa *operação de rota*:
-```Python hl_lines="30-31" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *}
/// tip | Dica
@@ -327,7 +289,7 @@ E também terá ambas as respostas na documentação, uma para `404` e uma para
///
-## O principal `FastAPI`
+## O principal `FastAPI` { #the-main-fastapi }
Agora, vamos ver o módulo em `app/main.py`.
@@ -337,27 +299,23 @@ Este será o arquivo principal em seu aplicativo que une tudo.
E como a maior parte de sua lógica agora viverá em seu próprio módulo específico, o arquivo principal será bem simples.
-### Importar `FastAPI`
+### Importe o `FastAPI` { #import-fastapi }
Você importa e cria uma classe `FastAPI` normalmente.
E podemos até declarar [dependências globais](dependencies/global-dependencies.md){.internal-link target=_blank} que serão combinadas com as dependências para cada `APIRouter`:
-```Python hl_lines="1 3 7" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
-### Importe o `APIRouter`
+### Importe o `APIRouter` { #import-the-apirouter }
Agora importamos os outros submódulos que possuem `APIRouter`s:
-```Python hl_lines="4-5" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
Como os arquivos `app/routers/users.py` e `app/routers/items.py` são submódulos que fazem parte do mesmo pacote Python `app`, podemos usar um único ponto `.` para importá-los usando "importações relativas".
-### Como funciona a importação
+### Como funciona a importação { #how-the-importing-works }
A seção:
@@ -399,7 +357,7 @@ Para saber mais sobre pacotes e módulos Python, leia
```console
-$ uvicorn app.main:app --reload
+$ fastapi dev app/main.py
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
@@ -537,7 +485,7 @@ Você verá a documentação automática da API, incluindo os caminhos de todos
-## Incluir o mesmo roteador várias vezes com `prefixos` diferentes
+## Inclua o mesmo roteador várias vezes com `prefix` diferentes { #include-the-same-router-multiple-times-with-different-prefix }
Você também pode usar `.include_router()` várias vezes com o *mesmo* roteador usando prefixos diferentes.
@@ -545,7 +493,7 @@ Isso pode ser útil, por exemplo, para expor a mesma API sob prefixos diferentes
Esse é um uso avançado que você pode não precisar, mas está lá caso precise.
-## Incluir um `APIRouter` em outro
+## Inclua um `APIRouter` em outro { #include-an-apirouter-in-another }
Da mesma forma que você pode incluir um `APIRouter` em um aplicativo `FastAPI`, você pode incluir um `APIRouter` em outro `APIRouter` usando:
diff --git a/docs/pt/docs/tutorial/body-fields.md b/docs/pt/docs/tutorial/body-fields.md
index e7dfb07f2a..25e11189e7 100644
--- a/docs/pt/docs/tutorial/body-fields.md
+++ b/docs/pt/docs/tutorial/body-fields.md
@@ -1,28 +1,28 @@
-# Corpo - Campos
+# Corpo - Campos { #body-fields }
-Da mesma forma que você pode declarar validações adicionais e metadados nos parâmetros de *funções de operações de rota* com `Query`, `Path` e `Body`, você pode declarar validações e metadados dentro de modelos do Pydantic usando `Field` do Pydantic.
+Da mesma forma que você pode declarar validações adicionais e metadados nos parâmetros de uma *função de operação de rota* com `Query`, `Path` e `Body`, você pode declarar validações e metadados dentro de modelos do Pydantic usando `Field` do Pydantic.
-## Importe `Field`
+## Importe `Field` { #import-field }
Primeiro, você tem que importá-lo:
-{* ../../docs_src/body_fields/tutorial001.py hl[4] *}
+{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *}
-/// warning | Aviso
+/// warning | Atenção
Note que `Field` é importado diretamente do `pydantic`, não do `fastapi` como todo o resto (`Query`, `Path`, `Body`, etc).
///
-## Declare atributos do modelo
+## Declare atributos do modelo { #declare-model-attributes }
Você pode então utilizar `Field` com atributos do modelo:
-{* ../../docs_src/body_fields/tutorial001.py hl[11:14] *}
+{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *}
`Field` funciona da mesma forma que `Query`, `Path` e `Body`, ele possui todos os mesmos parâmetros, etc.
-/// note | Detalhes técnicos
+/// note | Detalhes Técnicos
Na realidade, `Query`, `Path` e outros que você verá em seguida, criam objetos de subclasses de uma classe `Param` comum, que é ela mesma uma subclasse da classe `FieldInfo` do Pydantic.
@@ -40,13 +40,20 @@ Note como cada atributo do modelo com um tipo, valor padrão e `Field` possuem a
///
-## Adicione informações extras
+## Adicione informações extras { #add-extra-information }
Você pode declarar informação extra em `Field`, `Query`, `Body`, etc. E isso será incluído no JSON Schema gerado.
Você irá aprender mais sobre adicionar informações extras posteriormente nessa documentação, quando estiver aprendendo a declarar exemplos.
-## Recapitulando
+/// warning | Atenção
+
+Chaves extras passadas para `Field` também estarão presentes no schema OpenAPI resultante da sua aplicação.
+Como essas chaves podem não fazer necessariamente parte da especificação OpenAPI, algumas ferramentas de OpenAPI, por exemplo [o validador do OpenAPI](https://validator.swagger.io/), podem não funcionar com o schema gerado.
+
+///
+
+## Recapitulando { #recap }
Você pode usar `Field` do Pydantic para declarar validações extras e metadados para atributos do modelo.
diff --git a/docs/pt/docs/tutorial/body-multiple-params.md b/docs/pt/docs/tutorial/body-multiple-params.md
index eda9b4dfff..3cba04912c 100644
--- a/docs/pt/docs/tutorial/body-multiple-params.md
+++ b/docs/pt/docs/tutorial/body-multiple-params.md
@@ -1,14 +1,14 @@
-# Corpo - Múltiplos parâmetros
+# Corpo - Múltiplos parâmetros { #body-multiple-parameters }
Agora que nós vimos como usar `Path` e `Query`, veremos usos mais avançados de declarações no corpo da requisição.
-## Misture `Path`, `Query` e parâmetros de corpo
+## Misture `Path`, `Query` e parâmetros de corpo { #mix-path-query-and-body-parameters }
Primeiro, é claro, você pode misturar `Path`, `Query` e declarações de parâmetro no corpo da requisição livremente e o **FastAPI** saberá o que fazer.
E você também pode declarar parâmetros de corpo como opcionais, definindo o valor padrão com `None`:
-{* ../../docs_src/body_multiple_params/tutorial001_py310.py hl[17:19] *}
+{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *}
/// note | Nota
@@ -16,7 +16,7 @@ Repare que, neste caso, o `item` que seria capturado a partir do corpo é opcion
///
-## Múltiplos parâmetros de corpo
+## Múltiplos parâmetros de corpo { #multiple-body-parameters }
No exemplo anterior, as *operações de rota* esperariam um JSON no corpo contendo os atributos de um `Item`, exemplo:
@@ -62,7 +62,7 @@ O **FastAPI** fará a conversão automática a partir da requisição, assim ess
Ele executará a validação dos dados compostos e irá documentá-los de maneira compatível com o esquema OpenAPI e documentação automática.
-## Valores singulares no corpo
+## Valores singulares no corpo { #singular-values-in-body }
Assim como existem uma `Query` e uma `Path` para definir dados adicionais para parâmetros de consulta e de rota, o **FastAPI** provê o equivalente para `Body`.
@@ -72,7 +72,7 @@ Se você declará-lo como é, porque é um valor singular, o **FastAPI** assumir
Mas você pode instruir o **FastAPI** para tratá-lo como outra chave do corpo usando `Body`:
-{* ../../docs_src/body_multiple_params/tutorial003.py hl[22] *}
+{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *}
Neste caso, o **FastAPI** esperará um corpo como:
@@ -94,7 +94,7 @@ Neste caso, o **FastAPI** esperará um corpo como:
Mais uma vez, ele converterá os tipos de dados, validar, documentar, etc.
-## Múltiplos parâmetros de corpo e consulta
+## Múltiplos parâmetros de corpo e consulta { #multiple-body-params-and-query }
Obviamente, você também pode declarar parâmetros de consulta assim que você precisar, de modo adicional a quaisquer parâmetros de corpo.
@@ -112,7 +112,7 @@ q: str | None = None
Por exemplo:
-{* ../../docs_src/body_multiple_params/tutorial004_py310.py hl[26] *}
+{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *}
/// info | Informação
@@ -120,7 +120,7 @@ Por exemplo:
///
-## Declare um único parâmetro de corpo indicando sua chave
+## Declare um único parâmetro de corpo indicando sua chave { #embed-a-single-body-parameter }
Suponha que você tem um único parâmetro de corpo `item`, a partir de um modelo Pydantic `Item`.
@@ -134,7 +134,7 @@ item: Item = Body(embed=True)
como em:
-{* ../../docs_src/body_multiple_params/tutorial005_py310.py hl[15] *}
+{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *}
Neste caso o **FastAPI** esperará um corpo como:
@@ -160,7 +160,7 @@ ao invés de:
}
```
-## Recapitulando
+## Recapitulando { #recap }
Você pode adicionar múltiplos parâmetros de corpo para sua *função de operação de rota*, mesmo que a requisição possa ter somente um único corpo.
diff --git a/docs/pt/docs/tutorial/body-nested-models.md b/docs/pt/docs/tutorial/body-nested-models.md
index 2954ae3db3..f2bec19a2f 100644
--- a/docs/pt/docs/tutorial/body-nested-models.md
+++ b/docs/pt/docs/tutorial/body-nested-models.md
@@ -1,36 +1,26 @@
-# Corpo - Modelos aninhados
+# Corpo - Modelos aninhados { #body-nested-models }
-Com o **FastAPI**, você pode definir, validar, documentar e usar modelos profundamente aninhados de forma arbitrária (graças ao Pydantic).
+Com o **FastAPI**, você pode definir, validar, documentar e usar modelos arbitrariamente e profundamente aninhados (graças ao Pydantic).
-## Campos do tipo Lista
+## Campos do tipo Lista { #list-fields }
Você pode definir um atributo como um subtipo. Por exemplo, uma `list` do Python:
-{* ../../docs_src/body_nested_models/tutorial001.py hl[14] *}
+{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *}
Isso fará com que tags seja uma lista de itens mesmo sem declarar o tipo dos elementos desta lista.
-## Campos do tipo Lista com um parâmetro de tipo
+## Campos do tipo Lista com um parâmetro de tipo { #list-fields-with-type-parameter }
Mas o Python tem uma maneira específica de declarar listas com tipos internos ou "parâmetros de tipo":
-### Importe `List` do typing
+### Declare uma `list` com um parâmetro de tipo { #declare-a-list-with-a-type-parameter }
-Primeiramente, importe `List` do módulo `typing` que já vem por padrão no Python:
-
-{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
-
-### Declare a `List` com um parâmetro de tipo
-
-Para declarar tipos que têm parâmetros de tipo(tipos internos), como `list`, `dict`, `tuple`:
-
-* Importe os do modulo `typing`
-* Passe o(s) tipo(s) interno(s) como "parâmetros de tipo" usando colchetes: `[` e `]`
+Para declarar tipos que têm parâmetros de tipo (tipos internos), como `list`, `dict`, `tuple`,
+passe o(s) tipo(s) interno(s) como "parâmetros de tipo" usando colchetes: `[` e `]`
```Python
-from typing import List
-
-my_list: List[str]
+my_list: list[str]
```
Essa é a sintaxe padrão do Python para declarações de tipo.
@@ -39,20 +29,17 @@ Use a mesma sintaxe padrão para atributos de modelo com tipos internos.
Portanto, em nosso exemplo, podemos fazer com que `tags` sejam especificamente uma "lista de strings":
+{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}
-{* ../../docs_src/body_nested_models/tutorial002.py hl[14] *}
-
-## Tipo "set"
-
+## Tipos "set" { #set-types }
Mas então, quando nós pensamos mais, percebemos que as tags não devem se repetir, elas provavelmente devem ser strings únicas.
E que o Python tem um tipo de dados especial para conjuntos de itens únicos, o `set`.
-Então podemos importar `Set` e declarar `tags` como um `set` de `str`s:
+Então podemos declarar `tags` como um conjunto de strings:
-
-{* ../../docs_src/body_nested_models/tutorial003.py hl[1,14] *}
+{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *}
Com isso, mesmo que você receba uma requisição contendo dados duplicados, ela será convertida em um conjunto de itens exclusivos.
@@ -60,7 +47,7 @@ E sempre que você enviar esses dados como resposta, mesmo se a fonte tiver dupl
E também teremos anotações/documentação em conformidade.
-## Modelos aninhados
+## Modelos aninhados { #nested-models }
Cada atributo de um modelo Pydantic tem um tipo.
@@ -70,17 +57,17 @@ Portanto, você pode declarar "objects" JSON profundamente aninhados com nomes,
Tudo isso, aninhado arbitrariamente.
-### Defina um sub-modelo
+### Defina um sub-modelo { #define-a-submodel }
Por exemplo, nós podemos definir um modelo `Image`:
-{* ../../docs_src/body_nested_models/tutorial004.py hl[9:11] *}
+{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *}
-### Use o sub-modelo como um tipo
+### Use o sub-modelo como um tipo { #use-the-submodel-as-a-type }
E então podemos usa-lo como o tipo de um atributo:
-{* ../../docs_src/body_nested_models/tutorial004.py hl[20] *}
+{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *}
Isso significa que o **FastAPI** vai esperar um corpo similar à:
@@ -100,28 +87,28 @@ Isso significa que o **FastAPI** vai esperar um corpo similar à:
Novamente, apenas fazendo essa declaração, com o **FastAPI**, você ganha:
-* Suporte do editor de texto (compleção, etc), inclusive para modelos aninhados
+* Suporte do editor (preenchimento automático, etc.), inclusive para modelos aninhados
* Conversão de dados
* Validação de dados
* Documentação automatica
-## Tipos especiais e validação
+## Tipos especiais e validação { #special-types-and-validation }
Além dos tipos singulares normais como `str`, `int`, `float`, etc. Você também pode usar tipos singulares mais complexos que herdam de `str`.
-Para ver todas as opções possíveis, cheque a documentação para ostipos exoticos do Pydantic. Você verá alguns exemplos no próximo capitulo.
+Para ver todas as opções possíveis, consulte a Visão geral dos tipos do Pydantic. Você verá alguns exemplos no próximo capítulo.
Por exemplo, no modelo `Image` nós temos um campo `url`, nós podemos declara-lo como um `HttpUrl` do Pydantic invés de como uma `str`:
-{* ../../docs_src/body_nested_models/tutorial005.py hl[4,10] *}
+{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *}
-A string será verificada para se tornar uma URL válida e documentada no esquema JSON/1OpenAPI como tal.
+A string será verificada para se tornar uma URL válida e documentada no JSON Schema / OpenAPI como tal.
-## Atributos como listas de submodelos
+## Atributos como listas de submodelos { #attributes-with-lists-of-submodels }
Você também pode usar modelos Pydantic como subtipos de `list`, `set`, etc:
-{* ../../docs_src/body_nested_models/tutorial006.py hl[20] *}
+{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *}
Isso vai esperar(converter, validar, documentar, etc) um corpo JSON tal qual:
@@ -149,38 +136,37 @@ Isso vai esperar(converter, validar, documentar, etc) um corpo JSON tal qual:
}
```
-/// info | informação
+/// info | Informação
-Note como o campo `images` agora tem uma lista de objetos de image.
+Observe como a chave `images` agora tem uma lista de objetos de imagem.
///
-## Modelos profundamente aninhados
+## Modelos profundamente aninhados { #deeply-nested-models }
Você pode definir modelos profundamente aninhados de forma arbitrária:
-{* ../../docs_src/body_nested_models/tutorial007.py hl[9,14,20,23,27] *}
+{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *}
-/// info | informação
+/// info | Informação
-Note como `Offer` tem uma lista de `Item`s, que por sua vez possui opcionalmente uma lista `Image`s
+Observe como `Offer` tem uma lista de `Item`s, que por sua vez têm uma lista opcional de `Image`s
///
-## Corpos de listas puras
+## Corpos de listas puras { #bodies-of-pure-lists }
Se o valor de primeiro nível do corpo JSON que você espera for um `array` do JSON (uma` lista` do Python), você pode declarar o tipo no parâmetro da função, da mesma forma que nos modelos do Pydantic:
-
```Python
-images: List[Image]
+images: list[Image]
```
como em:
-{* ../../docs_src/body_nested_models/tutorial008.py hl[15] *}
+{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}
-## Suporte de editor em todo canto
+## Suporte de editor em todo canto { #editor-support-everywhere }
E você obtém suporte do editor em todos os lugares.
@@ -192,7 +178,7 @@ Você não conseguiria este tipo de suporte de editor se estivesse trabalhando d
Mas você também não precisa se preocupar com eles, os dicts de entrada são convertidos automaticamente e sua saída é convertida automaticamente para JSON também.
-## Corpos de `dict`s arbitrários
+## Corpos de `dict`s arbitrários { #bodies-of-arbitrary-dicts }
Você também pode declarar um corpo como um `dict` com chaves de algum tipo e valores de outro tipo.
@@ -208,7 +194,7 @@ Outro caso útil é quando você deseja ter chaves de outro tipo, por exemplo, `
Neste caso, você aceitaria qualquer `dict`, desde que tenha chaves` int` com valores `float`:
-{* ../../docs_src/body_nested_models/tutorial009.py hl[9] *}
+{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}
/// tip | Dica
@@ -222,14 +208,14 @@ E o `dict` que você recebe como `weights` terá, na verdade, chaves `int` e val
///
-## Recapitulação
+## Recapitulação { #recap }
Com **FastAPI** você tem a flexibilidade máxima fornecida pelos modelos Pydantic, enquanto seu código é mantido simples, curto e elegante.
Mas com todos os benefícios:
-* Suporte do editor (compleção em todo canto!)
-* Conversão de dados (leia-se parsing/serialização)
+* Suporte do editor (preenchimento automático em todo canto!)
+* Conversão de dados (parsing/serialização)
* Validação de dados
* Documentação dos esquemas
* Documentação automática
diff --git a/docs/pt/docs/tutorial/body-updates.md b/docs/pt/docs/tutorial/body-updates.md
index bf38aeb9e9..67bf684925 100644
--- a/docs/pt/docs/tutorial/body-updates.md
+++ b/docs/pt/docs/tutorial/body-updates.md
@@ -1,6 +1,6 @@
-# Corpo - Atualizações
+# Corpo - Atualizações { #body-updates }
-## Atualização de dados existentes com `PUT`
+## Atualização de dados existentes com `PUT` { #update-replacing-with-put }
Para atualizar um item, você pode usar a operação HTTP `PUT`.
@@ -10,7 +10,7 @@ Você pode usar `jsonable_encoder` para converter os dados de entrada em dados q
`PUT` é usado para receber dados que devem substituir os dados existentes.
-### Aviso sobre a substituição
+### Aviso sobre a substituição { #warning-about-replacing }
Isso significa que, se você quiser atualizar o item `bar` usando `PUT` com um corpo contendo:
@@ -26,9 +26,9 @@ Como ele não inclui o atributo já armazenado `"tax": 20.2`, o modelo de entrad
E os dados seriam salvos com esse "novo" `tax` de `10.5`.
-## Atualizações parciais com `PATCH`
+## Atualizações parciais com `PATCH` { #partial-updates-with-patch }
-Você também pode usar a operação HTTP `PATCH` para *atualizar* parcialmente os dados.
+Você também pode usar a operação HTTP `PATCH` para atualizar parcialmente os dados.
Isso significa que você pode enviar apenas os dados que deseja atualizar, deixando o restante intacto.
@@ -44,7 +44,7 @@ Mas este guia te dá uma ideia de como eles são destinados a serem usados.
///
-### Usando o parâmetro `exclude_unset` do Pydantic
+### Usando o parâmetro `exclude_unset` do Pydantic { #using-pydantics-exclude-unset-parameter }
Se você quiser receber atualizações parciais, é muito útil usar o parâmetro `exclude_unset` no método `.model_dump()` do modelo do Pydantic.
@@ -52,7 +52,7 @@ Como `item.model_dump(exclude_unset=True)`.
/// info | Informação
-No Pydantic v1, o método que era chamado `.dict()` e foi depreciado (mas ainda suportado) no Pydantic v2. Agora, deve-se usar o método `.model_dump()`.
+No Pydantic v1, o método que era chamado `.dict()` e foi descontinuado (mas ainda suportado) no Pydantic v2. Agora, deve-se usar o método `.model_dump()`.
Os exemplos aqui usam `.dict()` para compatibilidade com o Pydantic v1, mas você deve usar `.model_dump()` a partir do Pydantic v2.
@@ -64,13 +64,13 @@ Então, você pode usar isso para gerar um `dict` com apenas os dados definidos
{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *}
-### Usando o parâmetro `update` do Pydantic
+### Usando o parâmetro `update` do Pydantic { #using-pydantics-update-parameter }
Agora, você pode criar uma cópia do modelo existente usando `.model_copy()`, e passar o parâmetro `update` com um `dict` contendo os dados para atualizar.
/// info | Informação
-No Pydantic v1, o método era chamado `.copy()`, ele foi depreciado (mas ainda suportado) no Pydantic v2, e renomeado para `.model_copy()`.
+No Pydantic v1, o método era chamado `.copy()`, ele foi descontinuado (mas ainda suportado) no Pydantic v2, e renomeado para `.model_copy()`.
Os exemplos aqui usam `.copy()` para compatibilidade com o Pydantic v1, mas você deve usar `.model_copy()` com o Pydantic v2.
@@ -80,7 +80,7 @@ Como `stored_item_model.model_copy(update=update_data)`:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
-### Recapitulando as atualizações parciais
+### Recapitulando as atualizações parciais { #partial-updates-recap }
Resumindo, para aplicar atualizações parciais você pode:
diff --git a/docs/pt/docs/tutorial/body.md b/docs/pt/docs/tutorial/body.md
index 2508d7981b..1330f4458f 100644
--- a/docs/pt/docs/tutorial/body.md
+++ b/docs/pt/docs/tutorial/body.md
@@ -1,16 +1,16 @@
-# Corpo da Requisição
+# Corpo da requisição { #request-body }
-Quando você precisa enviar dados de um cliente (como de um navegador web) para sua API, você o envia como um **corpo da requisição**.
+Quando você precisa enviar dados de um cliente (como de um navegador) para sua API, você os envia como um **corpo da requisição**.
O corpo da **requisição** é a informação enviada pelo cliente para sua API. O corpo da **resposta** é a informação que sua API envia para o cliente.
-Sua API quase sempre irá enviar um corpo na **resposta**. Mas os clientes não necessariamente precisam enviar um corpo em toda **requisição**.
+Sua API quase sempre precisa enviar um corpo na **resposta**. Mas os clientes não necessariamente precisam enviar **corpos de requisição** o tempo todo, às vezes eles apenas requisitam um path, talvez com alguns parâmetros de consulta, mas não enviam um corpo.
Para declarar um corpo da **requisição**, você utiliza os modelos do Pydantic com todos os seus poderes e benefícios.
/// info | Informação
-Para enviar dados, você deve usar utilizar um dos métodos: `POST` (Mais comum), `PUT`, `DELETE` ou `PATCH`.
+Para enviar dados, você deve usar um dos: `POST` (o mais comum), `PUT`, `DELETE` ou `PATCH`.
Enviar um corpo em uma requisição `GET` não tem um comportamento definido nas especificações, porém é suportado pelo FastAPI, apenas para casos de uso bem complexos/extremos.
@@ -18,19 +18,19 @@ Como é desencorajado, a documentação interativa com Swagger UI não irá most
///
-## Importe o `BaseModel` do Pydantic
+## Importe o `BaseModel` do Pydantic { #import-pydantics-basemodel }
Primeiro, você precisa importar `BaseModel` do `pydantic`:
-{* ../../docs_src/body/tutorial001.py hl[4] *}
+{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
-## Crie seu modelo de dados
+## Crie seu modelo de dados { #create-your-data-model }
Então você declara seu modelo de dados como uma classe que herda `BaseModel`.
Utilize os tipos Python padrão para todos os atributos:
-{* ../../docs_src/body/tutorial001.py hl[7:11] *}
+{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
Assim como quando declaramos parâmetros de consulta, quando um atributo do modelo possui um valor padrão, ele se torna opcional. Caso contrário, se torna obrigatório. Use `None` para torná-lo opcional.
@@ -39,13 +39,13 @@ Por exemplo, o modelo acima declara um JSON "`object`" (ou `dict` no Python) com
```JSON
{
"name": "Foo",
- "description": "Uma descrição opcional",
+ "description": "An optional description",
"price": 45.2,
"tax": 3.5
}
```
-...como `description` e `tax` são opcionais (Com um valor padrão de `None`), esse JSON "`object`" também é válido:
+...como `description` e `tax` são opcionais (com um valor padrão de `None`), esse JSON "`object`" também é válido:
```JSON
{
@@ -54,40 +54,40 @@ Por exemplo, o modelo acima declara um JSON "`object`" (ou `dict` no Python) com
}
```
-## Declare como um parâmetro
+## Declare como um parâmetro { #declare-it-as-a-parameter }
-Para adicionar o corpo na *função de operação de rota*, declare-o da mesma maneira que você declarou parâmetros de rota e consulta:
+Para adicioná-lo à sua *operação de rota*, declare-o da mesma maneira que você declarou parâmetros de rota e de consulta:
-{* ../../docs_src/body/tutorial001.py hl[18] *}
+{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
-...E declare o tipo como o modelo que você criou, `Item`.
+...e declare o seu tipo como o modelo que você criou, `Item`.
-## Resultados
+## Resultados { #results }
-Apenas com esse declaração de tipos do Python, o **FastAPI** irá:
+Apenas com essa declaração de tipos do Python, o **FastAPI** irá:
* Ler o corpo da requisição como um JSON.
* Converter os tipos correspondentes (se necessário).
* Validar os dados.
- * Se algum dados for inválido, irá retornar um erro bem claro, indicando exatamente onde e o que está incorreto.
+ * Se algum dado for inválido, irá retornar um erro bem claro, indicando exatamente onde e o que estava incorreto.
* Entregar a você a informação recebida no parâmetro `item`.
* Como você o declarou na função como do tipo `Item`, você também terá o suporte do editor (completação, etc) para todos os atributos e seus tipos.
-* Gerar um Esquema JSON com as definições do seu modelo, você também pode utilizá-lo em qualquer lugar que quiser, se fizer sentido para seu projeto.
-* Esses esquemas farão parte do esquema OpenAPI, e utilizados nas UIs de documentação automática.
+* Gerar definições de JSON Schema para o seu modelo; você também pode usá-las em qualquer outro lugar se fizer sentido para o seu projeto.
+* Esses schemas farão parte do esquema OpenAPI gerado, e serão usados pelas UIs de documentação automática.
-## Documentação automática
+## Documentação automática { #automatic-docs }
-Os esquemas JSON dos seus modelos farão parte do esquema OpenAPI gerado para sua aplicação, e aparecerão na documentação interativa da API:
+Os JSON Schemas dos seus modelos farão parte do esquema OpenAPI gerado para sua aplicação, e aparecerão na documentação interativa da API:
-E também serão utilizados em cada *função de operação de rota* que utilizá-los:
+E também serão utilizados na documentação da API dentro de cada *operação de rota* que precisar deles:
-## Suporte do editor de texto:
+## Suporte do editor { #editor-support }
-No seu editor de texto, dentro da função você receberá dicas de tipos e completação em todo lugar (isso não aconteceria se você recebesse um `dict` em vez de um modelo Pydantic):
+No seu editor, dentro da função você receberá dicas de tipos e completação em todo lugar (isso não aconteceria se você recebesse um `dict` em vez de um modelo Pydantic):
@@ -111,9 +111,9 @@ Mas você terá o mesmo suporte do editor no PyCharm como editor, você pode utilizar o Plugin do Pydantic para o PyCharm .
-Melhora o suporte do editor para seus modelos Pydantic com::
+Melhora o suporte do editor para seus modelos Pydantic com:
-* completação automática
+* preenchimento automático
* verificação de tipos
* refatoração
* buscas
@@ -121,42 +121,52 @@ Melhora o suporte do editor para seus modelos Pydantic com::
///
-## Use o modelo
+## Use o modelo { #use-the-model }
Dentro da função, você pode acessar todos os atributos do objeto do modelo diretamente:
-{* ../../docs_src/body/tutorial002.py hl[21] *}
+{* ../../docs_src/body/tutorial002_py310.py *}
-## Corpo da requisição + parâmetros de rota
+/// info | Informação
+
+No Pydantic v1 o método se chamava `.dict()`, ele foi descontinuado (mas ainda é suportado) no Pydantic v2, e renomeado para `.model_dump()`.
+
+Os exemplos aqui usam `.dict()` para compatibilidade com o Pydantic v1, mas você deve usar `.model_dump()` se puder usar o Pydantic v2.
+
+///
+
+## Corpo da requisição + parâmetros de rota { #request-body-path-parameters }
Você pode declarar parâmetros de rota e corpo da requisição ao mesmo tempo.
-O **FastAPI** irá reconhecer que os parâmetros da função que combinam com parâmetros de rota devem ser **retirados da rota**, e parâmetros da função que são declarados como modelos Pydantic sejam **retirados do corpo da requisição**.
+O **FastAPI** irá reconhecer que os parâmetros da função que combinam com parâmetros de rota devem ser **retirados da rota**, e que parâmetros da função que são declarados como modelos Pydantic sejam **retirados do corpo da requisição**.
-{* ../../docs_src/body/tutorial003.py hl[17:18] *}
+{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
-## Corpo da requisição + parâmetros de rota + parâmetros de consulta
+## Corpo da requisição + parâmetros de rota + parâmetros de consulta { #request-body-path-query-parameters }
Você também pode declarar parâmetros de **corpo**, **rota** e **consulta**, ao mesmo tempo.
O **FastAPI** irá reconhecer cada um deles e retirar a informação do local correto.
-{* ../../docs_src/body/tutorial004.py hl[18] *}
+{* ../../docs_src/body/tutorial004_py310.py hl[16] *}
Os parâmetros da função serão reconhecidos conforme abaixo:
-* Se o parâmetro também é declarado na **rota**, será utilizado como um parâmetro de rota.
+* Se o parâmetro também é declarado no **path**, será utilizado como um parâmetro de rota.
* Se o parâmetro é de um **tipo único** (como `int`, `float`, `str`, `bool`, etc) será interpretado como um parâmetro de **consulta**.
* Se o parâmetro é declarado como um **modelo Pydantic**, será interpretado como o **corpo** da requisição.
-/// note | Observação
+/// note | Nota
O FastAPI saberá que o valor de `q` não é obrigatório por causa do valor padrão `= None`.
-O `Union` em `Union[str, None]` não é utilizado pelo FastAPI, mas permite ao seu editor de texto lhe dar um suporte melhor e detectar erros.
+O `str | None` (Python 3.10+) ou o `Union` em `Union[str, None]` (Python 3.9+) não é utilizado pelo FastAPI para determinar que o valor não é obrigatório, ele saberá que não é obrigatório porque tem um valor padrão `= None`.
+
+Mas adicionar as anotações de tipo permitirá ao seu editor oferecer um suporte melhor e detectar erros.
///
-## Sem o Pydantic
+## Sem o Pydantic { #without-pydantic }
-Se você não quer utilizar os modelos Pydantic, você também pode utilizar o parâmetro **Body**. Veja a documentação para [Body - Parâmetros múltiplos: Valores singulares no body](body-multiple-params.md#valores-singulares-no-corpo){.internal-link target=_blank}.
+Se você não quer utilizar os modelos Pydantic, você também pode utilizar o parâmetro **Body**. Veja a documentação para [Body - Parâmetros múltiplos: Valores singulares no body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
diff --git a/docs/pt/docs/tutorial/cookie-param-models.md b/docs/pt/docs/tutorial/cookie-param-models.md
index 3d46ba44cb..73c94050fb 100644
--- a/docs/pt/docs/tutorial/cookie-param-models.md
+++ b/docs/pt/docs/tutorial/cookie-param-models.md
@@ -1,4 +1,4 @@
-# Modelos de Parâmetros de Cookie
+# Modelos de Parâmetros de Cookie { #cookie-parameter-models }
Se você possui um grupo de **cookies** que estão relacionados, você pode criar um **modelo Pydantic** para declará-los. 🍪
@@ -16,7 +16,7 @@ Essa mesma técnica se aplica para `Query`, `Cookie`, e `Header`. 😎
///
-## Cookies com Modelos Pydantic
+## Cookies com Modelos Pydantic { #cookies-with-a-pydantic-model }
Declare o parâmetro de **cookie** que você precisa em um **modelo Pydantic**, e depois declare o parâmetro como um `Cookie`:
@@ -24,9 +24,9 @@ Declare o parâmetro de **cookie** que você precisa em um **modelo Pydantic**,
O **FastAPI** irá **extrair** os dados para **cada campo** dos **cookies** recebidos na requisição e lhe fornecer o modelo Pydantic que você definiu.
-## Verifique os Documentos
+## Verifique a Documentação { #check-the-docs }
-Você pode ver os cookies definidos na IU dos documentos em `/docs`:
+Você pode ver os cookies definidos na IU da documentação em `/docs`:
@@ -36,29 +36,27 @@ Você pode ver os cookies definidos na IU dos documentos em `/docs`:
Tenha em mente que, como os **navegadores lidam com cookies** de maneira especial e por baixo dos panos, eles **não** permitem facilmente que o **JavaScript** lidem com eles.
-Se você for na **IU de documentos da API** em `/docs` você poderá ver a **documentação** para cookies das suas *operações de rotas*.
+Se você for na **IU da documentação da API** em `/docs` você poderá ver a **documentação** para cookies das suas *operações de rotas*.
-Mas mesmo que você **adicionar os dados** e clicar em "Executar", pelo motivo da IU dos documentos trabalharem com **JavaScript**, os cookies não serão enviados, e você verá uma mensagem de **erro** como se você não tivesse escrito nenhum dado.
+Mas mesmo que você **adicionar os dados** e clicar em "Executar", pelo motivo da IU da documentação trabalhar com **JavaScript**, os cookies não serão enviados, e você verá uma mensagem de **erro** como se você não tivesse escrito nenhum dado.
///
-## Proibir Cookies Adicionais
+## Proibir Cookies Adicionais { #forbid-extra-cookies }
Em alguns casos especiais (provavelmente não muito comuns), você pode querer **restringir** os cookies que você deseja receber.
-Agora a sua API possui o poder de contrar o seu próprio consentimento de cookie. 🤪🍪
+Agora a sua API possui o poder de controlar o seu próprio consentimento de cookie. 🤪🍪
+Você pode utilizar a configuração do modelo Pydantic para `proibir` qualquer campo `extra`:
- Você pode utilizar a configuração do modelo Pydantic para `proibir` qualquer campo `extra`.
-
-
-{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
+{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *}
Se o cliente tentar enviar alguns **cookies extras**, eles receberão um retorno de **erro**.
Coitados dos banners de cookies com todo o seu esforço para obter o seu consentimento para a API rejeitá-lo. 🍪
-Por exemplo, se o cliente tentar enviar um cookie `santa_tracker` com o valor de `good-list-please`, o cliente receberá uma resposta de **erro** informando que o cookie `santa_tracker` is not allowed:
+Por exemplo, se o cliente tentar enviar um cookie `santa_tracker` com o valor de `good-list-please`, o cliente receberá uma resposta de **erro** informando que o `santa_tracker` cookie não é permitido:
```json
{
@@ -73,6 +71,6 @@ Por exemplo, se o cliente tentar enviar um cookie `santa_tracker` com o valor de
}
```
-## Resumo
+## Resumo { #summary }
Você consegue utilizar **modelos Pydantic** para declarar **cookies** no **FastAPI**. 😎
diff --git a/docs/pt/docs/tutorial/cookie-params.md b/docs/pt/docs/tutorial/cookie-params.md
index da85d796ea..5540a67d20 100644
--- a/docs/pt/docs/tutorial/cookie-params.md
+++ b/docs/pt/docs/tutorial/cookie-params.md
@@ -1,20 +1,19 @@
-# Parâmetros de Cookie
+# Parâmetros de Cookie { #cookie-parameters }
-Você pode definir parâmetros de Cookie da mesma maneira que define paramêtros com `Query` e `Path`.
+Você pode definir parâmetros de Cookie da mesma maneira que define parâmetros com `Query` e `Path`.
-## Importe `Cookie`
+## Importe `Cookie` { #import-cookie }
Primeiro importe `Cookie`:
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
-## Declare parâmetros de `Cookie`
+## Declare parâmetros de `Cookie` { #declare-cookie-parameters }
-Então declare os paramêtros de cookie usando a mesma estrutura que em `Path` e `Query`.
+Então declare os parâmetros de cookie usando a mesma estrutura que em `Path` e `Query`.
Você pode definir o valor padrão, assim como todas as validações extras ou parâmetros de anotação:
-
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *}
/// note | Detalhes Técnicos
@@ -31,6 +30,16 @@ Para declarar cookies, você precisa usar `Cookie`, pois caso contrário, os par
///
-## Recapitulando
+/// info | Informação
+
+Tenha em mente que, como os **navegadores lidam com cookies** de maneiras especiais e nos bastidores, eles **não** permitem facilmente que o **JavaScript** os acesse.
+
+Se você for à **interface de documentação da API** em `/docs`, poderá ver a **documentação** de cookies para suas *operações de rota*.
+
+Mas mesmo que você **preencha os dados** e clique em "Execute", como a interface de documentação funciona com **JavaScript**, os cookies não serão enviados e você verá uma mensagem de **erro** como se você não tivesse escrito nenhum valor.
+
+///
+
+## Recapitulando { #recap }
Declare cookies com `Cookie`, usando o mesmo padrão comum que utiliza-se em `Query` e `Path`.
diff --git a/docs/pt/docs/tutorial/cors.md b/docs/pt/docs/tutorial/cors.md
index 0ab07a3c21..0f99db888e 100644
--- a/docs/pt/docs/tutorial/cors.md
+++ b/docs/pt/docs/tutorial/cors.md
@@ -1,8 +1,8 @@
-# CORS (Cross-Origin Resource Sharing)
+# CORS (Cross-Origin Resource Sharing) { #cors-cross-origin-resource-sharing }
CORS ou "Cross-Origin Resource Sharing" refere-se às situações em que um frontend rodando em um navegador possui um código JavaScript que se comunica com um backend, e o backend está em uma "origem" diferente do frontend.
-## Origem
+## Origem { #origin }
Uma origem é a combinação de protocolo (`http`, `https`), domínio (`myapp.com`, `localhost`, `localhost.tiangolo.com`), e porta (`80`, `443`, `8080`).
@@ -12,27 +12,27 @@ Então, todos estes são origens diferentes:
* `https://localhost`
* `http://localhost:8080`
-Mesmo se todos estiverem em `localhost`, eles usam diferentes protocolos e portas, portanto, são "origens" diferentes.
+Mesmo se todos estiverem em `localhost`, eles usam diferentes protocolos ou portas, portanto, são "origens" diferentes.
-## Passos
+## Passos { #steps }
-Então, digamos que você tenha um frontend rodando no seu navegador em `http://localhost:8080`, e seu JavaScript esteja tentando se comunicar com um backend rodando em http://localhost (como não especificamos uma porta, o navegador assumirá a porta padrão `80`).
+Então, digamos que você tenha um frontend rodando no seu navegador em `http://localhost:8080`, e seu JavaScript esteja tentando se comunicar com um backend rodando em `http://localhost` (como não especificamos uma porta, o navegador assumirá a porta padrão `80`).
-Portanto, o navegador irá enviar uma requisição HTTP `OPTIONS` ao backend, e se o backend enviar os cabeçalhos apropriados autorizando a comunicação a partir de uma origem diferente (`http://localhost:8080`) então o navegador deixará o JavaScript no frontend enviar sua requisição para o backend.
+Portanto, o navegador enviará uma requisição HTTP `OPTIONS` ao backend `:80`, e se o backend enviar os cabeçalhos apropriados autorizando a comunicação a partir dessa origem diferente (`http://localhost:8080`), então o navegador `:8080` permitirá que o JavaScript no frontend envie sua requisição para o backend `:80`.
-Para conseguir isso, o backend deve ter uma lista de "origens permitidas".
+Para conseguir isso, o backend `:80` deve ter uma lista de "origens permitidas".
-Neste caso, ele terá que incluir `http://localhost:8080` para o frontend funcionar corretamente.
+Neste caso, a lista terá que incluir `http://localhost:8080` para o frontend `:8080` funcionar corretamente.
-## Curingas
+## Curingas { #wildcards }
-É possível declarar uma lista com `"*"` (um "curinga") para dizer que tudo está permitido.
+É possível declarar a lista como `"*"` (um "curinga") para dizer que tudo está permitido.
Mas isso só permitirá certos tipos de comunicação, excluindo tudo que envolva credenciais: cookies, cabeçalhos de autorização como aqueles usados com Bearer Tokens, etc.
Então, para que tudo funcione corretamente, é melhor especificar explicitamente as origens permitidas.
-## Usar `CORSMiddleware`
+## Usar `CORSMiddleware` { #use-corsmiddleware }
Você pode configurá-lo em sua aplicação **FastAPI** usando o `CORSMiddleware`.
@@ -46,9 +46,9 @@ Você também pode especificar se o seu backend permite:
* Métodos HTTP específicos (`POST`, `PUT`) ou todos eles com o curinga `"*"`.
* Cabeçalhos HTTP específicos ou todos eles com o curinga `"*"`.
-{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
+{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *}
-Os parâmetros padrão usados pela implementação `CORSMiddleware` são restritivos por padrão, então você precisará habilitar explicitamente as origens, métodos ou cabeçalhos específicos para que os navegadores tenham permissão para usá-los em um contexto de domínios diferentes.
+Os parâmetros padrão usados pela implementação `CORSMiddleware` são restritivos por padrão, então você precisará habilitar explicitamente as origens, métodos ou cabeçalhos específicos para que os navegadores tenham permissão para usá-los em um contexto cross domain.
Os seguintes argumentos são suportados:
@@ -56,27 +56,30 @@ Os seguintes argumentos são suportados:
* `allow_origin_regex` - Uma string regex para corresponder às origens que devem ter permissão para fazer requisições de origem cruzada. Por exemplo, `'https://.*\.example\.org'`.
* `allow_methods` - Uma lista de métodos HTTP que devem ser permitidos para requisições de origem cruzada. O padrão é `['GET']`. Você pode usar `['*']` para permitir todos os métodos padrão.
* `allow_headers` - Uma lista de cabeçalhos de solicitação HTTP que devem ter suporte para requisições de origem cruzada. O padrão é `[]`. Você pode usar `['*']` para permitir todos os cabeçalhos. Os cabeçalhos `Accept`, `Accept-Language`, `Content-Language` e `Content-Type` são sempre permitidos para requisições CORS simples.
-* `allow_credentials` - Indica que os cookies devem ser suportados para requisições de origem cruzada. O padrão é `False`. Além disso, `allow_origins` não pode ser definido como `['*']` para que as credenciais sejam permitidas, as origens devem ser especificadas.
+* `allow_credentials` - Indica que os cookies devem ser suportados para requisições de origem cruzada. O padrão é `False`.
+
+ Nenhum de `allow_origins`, `allow_methods` e `allow_headers` pode ser definido como `['*']` se `allow_credentials` estiver definido como `True`. Todos eles devem ser especificados explicitamente.
+
* `expose_headers` - Indica quaisquer cabeçalhos de resposta que devem ser disponibilizados ao navegador. O padrão é `[]`.
* `max_age` - Define um tempo máximo em segundos para os navegadores armazenarem em cache as respostas CORS. O padrão é `600`.
O middleware responde a dois tipos específicos de solicitação HTTP...
-### Requisições CORS pré-voo (preflight)
+### Requisições CORS pré-voo (preflight) { #cors-preflight-requests }
Estas são quaisquer solicitações `OPTIONS` com cabeçalhos `Origin` e `Access-Control-Request-Method`.
Nesse caso, o middleware interceptará a solicitação recebida e responderá com cabeçalhos CORS apropriados e uma resposta `200` ou `400` para fins informativos.
-### Requisições Simples
+### Requisições Simples { #simple-requests }
Qualquer solicitação com um cabeçalho `Origin`. Neste caso, o middleware passará a solicitação normalmente, mas incluirá cabeçalhos CORS apropriados na resposta.
-## Mais informações
+## Mais informações { #more-info }
-Para mais informações CORS, acesse Mozilla CORS documentation.
+Para mais informações sobre CORS, consulte a documentação do CORS da Mozilla.
-/// note | Detalhes técnicos
+/// note | Detalhes Técnicos
Você também pode usar `from starlette.middleware.cors import CORSMiddleware`.
diff --git a/docs/pt/docs/tutorial/debugging.md b/docs/pt/docs/tutorial/debugging.md
index 67b7644577..e39c7d1280 100644
--- a/docs/pt/docs/tutorial/debugging.md
+++ b/docs/pt/docs/tutorial/debugging.md
@@ -1,14 +1,14 @@
-# Depuração
+# Depuração { #debugging }
Você pode conectar o depurador no seu editor, por exemplo, com o Visual Studio Code ou PyCharm.
-## Chamar `uvicorn`
+## Chamar `uvicorn` { #call-uvicorn }
-Em seu aplicativo FastAPI, importe e execute `uvicorn` diretamente:
+Em sua aplicação FastAPI, importe e execute `uvicorn` diretamente:
-{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
+{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *}
-### Sobre `__name__ == "__main__"`
+### Sobre `__name__ == "__main__"` { #about-name-main }
O objetivo principal de `__name__ == "__main__"` é ter algum código que seja executado quando seu arquivo for chamado com:
@@ -26,7 +26,7 @@ mas não é chamado quando outro arquivo o importa, como em:
from myapp import app
```
-#### Mais detalhes
+#### Mais detalhes { #more-details }
Digamos que seu arquivo se chama `myapp.py`.
@@ -78,9 +78,9 @@ Para mais informações, consulte
-## Pegando um Atalho
+## Pegando um Atalho { #shortcut }
Mas você pode ver que temos uma repetição do código neste exemplo, escrevendo `CommonQueryParams` duas vezes:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ non-Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Dica
@@ -225,7 +225,7 @@ Para esses casos específicos, você pode fazer o seguinte:
Em vez de escrever:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ non-Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Dica
@@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
...escreva:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends()]
@@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()]
////
-//// tab | Python 3.8 non-Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Dica
diff --git a/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
index d7d31bb45e..ee8a58dc24 100644
--- a/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
+++ b/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
@@ -1,4 +1,4 @@
-# Dependências em decoradores de operações de rota
+# Dependências em decoradores de operações de rota { #dependencies-in-path-operation-decorators }
Em alguns casos você não precisa necessariamente retornar o valor de uma dependência dentro de uma *função de operação de rota*.
@@ -8,7 +8,7 @@ Mas você ainda precisa que ela seja executada/resolvida.
Para esses casos, em vez de declarar um parâmetro em uma *função de operação de rota* com `Depends`, você pode adicionar um argumento `dependencies` do tipo `list` ao decorador da operação de rota.
-## Adicionando `dependencies` ao decorador da operação de rota
+## Adicione `dependencies` ao decorador da operação de rota { #add-dependencies-to-the-path-operation-decorator }
O *decorador da operação de rota* recebe um argumento opcional `dependencies`.
@@ -22,7 +22,7 @@ Essas dependências serão executadas/resolvidas da mesma forma que dependência
Alguns editores de texto checam parâmetros de funções não utilizados, e os mostram como erros.
-Utilizando `dependencies` no *decorador da operação de rota* você pode garantir que elas serão executadas enquanto evita errors de editores/ferramentas.
+Utilizando `dependencies` no *decorador da operação de rota* você pode garantir que elas serão executadas enquanto evita erros de editores/ferramentas.
Isso também pode ser útil para evitar confundir novos desenvolvedores que ao ver um parâmetro não usado no seu código podem pensar que ele é desnecessário.
@@ -30,29 +30,29 @@ Isso também pode ser útil para evitar confundir novos desenvolvedores que ao v
/// info | Informação
-Neste exemplo utilizamos cabeçalhos personalizados inventados `X-Keys` e `X-Token`.
+Neste exemplo utilizamos cabeçalhos personalizados inventados `X-Key` e `X-Token`.
Mas em situações reais, como implementações de segurança, você pode obter mais vantagens em usar as [Ferramentas de segurança integradas (o próximo capítulo)](../security/index.md){.internal-link target=_blank}.
///
-## Erros das dependências e valores de retorno
+## Erros das dependências e valores de retorno { #dependencies-errors-and-return-values }
Você pode utilizar as mesmas *funções* de dependências que você usaria normalmente.
-### Requisitos de Dependências
+### Requisitos de Dependências { #dependency-requirements }
Dependências podem declarar requisitos de requisições (como cabeçalhos) ou outras subdependências:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *}
-### Levantando exceções
+### Levantar exceções { #raise-exceptions }
-Essas dependências podem levantar exceções, da mesma forma que dependências comuns:
+Essas dependências podem `raise` exceções, da mesma forma que dependências comuns:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *}
-### Valores de retorno
+### Valores de retorno { #return-values }
E elas também podem ou não retornar valores, eles não serão utilizados.
@@ -60,10 +60,10 @@ Então, você pode reutilizar uma dependência comum (que retorna um valor) que
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *}
-## Dependências para um grupo de *operações de rota*
+## Dependências para um grupo de *operações de rota* { #dependencies-for-a-group-of-path-operations }
-Mais a frente, quando você ler sobre como estruturar aplicações maiores ([Bigger Applications - Multiple Files](../../tutorial/bigger-applications.md){.internal-link target=_blank}), possivelmente com múltiplos arquivos, você aprenderá a declarar um único parâmetro `dependencies` para um grupo de *operações de rota*.
+Mais a frente, quando você ler sobre como estruturar aplicações maiores ([Aplicações maiores - Múltiplos arquivos](../../tutorial/bigger-applications.md){.internal-link target=_blank}), possivelmente com múltiplos arquivos, você aprenderá a declarar um único parâmetro `dependencies` para um grupo de *operações de rota*.
-## Dependências globais
+## Dependências globais { #global-dependencies }
-No próximo passo veremos como adicionar dependências para uma aplicação `FastAPI` inteira, para que ela seja aplicada em toda *operação de rota*.
+No próximo passo veremos como adicionar dependências para uma aplicação `FastAPI` inteira, para que elas sejam aplicadas em toda *operação de rota*.
diff --git a/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md
index eaf711197f..3678730139 100644
--- a/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md
+++ b/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md
@@ -1,12 +1,12 @@
-# Dependências com yield
+# Dependências com yield { #dependencies-with-yield }
-O FastAPI possui suporte para dependências que realizam alguns passos extras ao finalizar.
+O **FastAPI** possui suporte para dependências que realizam alguns passos extras ao finalizar.
Para fazer isso, utilize `yield` em vez de `return`, e escreva os passos extras (código) depois.
/// tip | Dica
-Garanta que `yield` é utilizado apenas uma vez.
+Garanta utilizar `yield` apenas uma vez por dependência.
///
@@ -23,21 +23,21 @@ Na realidade, o FastAPI utiliza esses dois decoradores internamente.
///
-## Uma dependência de banco de dados com `yield`
+## Uma dependência de banco de dados com `yield` { #a-database-dependency-with-yield }
-Por exemplo, você poderia utilizar isso para criar uma sessão do banco de dados, e fechá-la após terminar sua operação.
+Por exemplo, você poderia utilizar isso para criar uma sessão do banco de dados, e fechá-la após terminar.
-Apenas o código anterior a declaração com `yield` e o código contendo essa declaração são executados antes de criar uma resposta.
+Apenas o código anterior à declaração com `yield` e o código contendo essa declaração são executados antes de criar uma resposta:
-{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *}
-O valor gerado (yielded) é o que é injetado nas *operações de rota* e outras dependências.
+O valor gerado (yielded) é o que é injetado nas *operações de rota* e outras dependências:
-{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *}
-O código após o `yield` é executado após a resposta ser entregue:
+O código após o `yield` é executado após a resposta:
-{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *}
/// tip | Dica
@@ -47,21 +47,19 @@ O **FastAPI** saberá o que fazer com cada uma, da mesma forma que as dependênc
///
-## Uma dependência com `yield` e `try`
+## Uma dependência com `yield` e `try` { #a-dependency-with-yield-and-try }
Se você utilizar um bloco `try` em uma dependência com `yield`, você irá capturar qualquer exceção que for lançada enquanto a dependência é utilizada.
-Por exemplo, se algum código em um certo momento no meio da operação, em outra dependência ou em uma *operação de rota*, fizer um "rollback" de uma transação de banco de dados ou causar qualquer outro erro, você irá capturar a exceção em sua dependência.
+Por exemplo, se algum código em um certo momento no meio, em outra dependência ou em uma *operação de rota*, fizer um "rollback" de uma transação de banco de dados ou causar qualquer outra exceção, você irá capturar a exceção em sua dependência.
Então, você pode procurar por essa exceção específica dentro da dependência com `except AlgumaExcecao`.
Da mesma forma, você pode utilizar `finally` para garantir que os passos de saída são executados, com ou sem exceções.
-```python hl_lines="3 5"
-{!../../docs_src/dependencies/tutorial007.py!}
-```
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *}
-## Subdependências com `yield`
+## Subdependências com `yield` { #sub-dependencies-with-yield }
Você pode ter subdependências e "árvores" de subdependências de qualquer tamanho e forma, e qualquer uma ou todas elas podem utilizar `yield`.
@@ -69,73 +67,17 @@ O **FastAPI** garantirá que o "código de saída" em cada dependência com `yie
Por exemplo, `dependency_c` pode depender de `dependency_b`, e `dependency_b` depender de `dependency_a`:
-//// tab | python 3.9+
-
-```python hl_lines="6 14 22"
-{!> ../../docs_src/dependencies/tutorial008_an_py39.py!}
-```
-
-////
-
-//// tab | python 3.8+
-
-```python hl_lines="5 13 21"
-{!> ../../docs_src/dependencies/tutorial008_an.py!}
-```
-
-////
-
-//// tab | python 3.8+ non-annotated
-
-/// tip | Dica
-
-Utilize a versão com `Annotated` se possível.
-
-///
-
-```python hl_lines="4 12 20"
-{!> ../../docs_src/dependencies/tutorial008.py!}
-```
-
-////
+{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *}
E todas elas podem utilizar `yield`.
-Neste caso, `dependency_c` precisa que o valor de `dependency_b` (nomeada de `dep_b` aqui) continue disponível para executar seu código de saída.
+Neste caso, `dependency_c`, para executar seu código de saída, precisa que o valor de `dependency_b` (nomeado de `dep_b` aqui) continue disponível.
-E, por outro lado, `dependency_b` precisa que o valor de `dependency_a` (nomeada de `dep_a`) continue disponível para executar seu código de saída.
+E, por outro lado, `dependency_b` precisa que o valor de `dependency_a` (nomeado de `dep_a`) esteja disponível para executar seu código de saída.
-//// tab | python 3.9+
+{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
-```python hl_lines="18-19 26-27"
-{!> ../../docs_src/dependencies/tutorial008_an_py39.py!}
-```
-
-////
-
-//// tab | python 3.8+
-
-```python hl_lines="17-18 25-26"
-{!> ../../docs_src/dependencies/tutorial008_an.py!}
-```
-
-////
-
-//// tab | python 3.8+ non-annotated
-
-/// tip | Dica
-
-Utilize a versão com `Annotated` se possível.
-
-///
-
-```python hl_lines="16-17 24-25"
-{!> ../../docs_src/dependencies/tutorial008.py!}
-```
-
-////
-
-Da mesma forma, você pode ter algumas dependências com `yield` e outras com `return` e ter uma relação de dependência entre algumas dos dois tipos.
+Da mesma forma, você pode ter algumas dependências com `yield` e outras com `return` e ter uma relação de dependência entre algumas das duas.
E você poderia ter uma única dependência que precisa de diversas outras dependências com `yield`, etc.
@@ -151,83 +93,45 @@ O **FastAPI** utiliza eles internamente para alcançar isso.
///
-## Dependências com `yield` e `httpexception`
+## Dependências com `yield` e `HTTPException` { #dependencies-with-yield-and-httpexception }
-Você viu que dependências podem ser utilizadas com `yield` e podem incluir blocos `try` para capturar exceções.
+Você viu que pode usar dependências com `yield` e ter blocos `try` que tentam executar algum código e depois executar algum código de saída com `finally`.
-Da mesma forma, você pode lançar uma `httpexception` ou algo parecido no código de saída, após o `yield`
+Você também pode usar `except` para capturar a exceção que foi levantada e fazer algo com ela.
+
+Por exemplo, você pode levantar uma exceção diferente, como `HTTPException`.
/// tip | Dica
-Essa é uma técnica relativamente avançada, e na maioria dos casos você não precisa dela totalmente, já que você pode lançar exceções (incluindo `httpexception`) dentro do resto do código da sua aplicação, por exemplo, em uma *função de operação de rota*.
+Essa é uma técnica relativamente avançada, e na maioria dos casos você não vai precisar dela, já que você pode levantar exceções (incluindo `HTTPException`) dentro do resto do código da sua aplicação, por exemplo, na *função de operação de rota*.
Mas ela existe para ser utilizada caso você precise. 🤓
///
-//// tab | python 3.9+
+{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *}
-```python hl_lines="18-22 31"
-{!> ../../docs_src/dependencies/tutorial008b_an_py39.py!}
-```
+Se você quiser capturar exceções e criar uma resposta personalizada com base nisso, crie um [Manipulador de Exceções Customizado](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
-////
+## Dependências com `yield` e `except` { #dependencies-with-yield-and-except }
-//// tab | python 3.8+
-
-```python hl_lines="17-21 30"
-{!> ../../docs_src/dependencies/tutorial008b_an.py!}
-```
-
-////
-
-//// tab | python 3.8+ non-annotated
-
-/// tip | Dica
-
-Utilize a versão com `Annotated` se possível.
-
-///
-
-```python hl_lines="16-20 29"
-{!> ../../docs_src/dependencies/tutorial008b.py!}
-```
-
-////
-
-Uma alternativa que você pode utilizar para capturar exceções (e possivelmente lançar outra HTTPException) é criar um [Manipulador de Exceções Customizado](../handling-errors.md#instalando-manipuladores-de-excecoes-customizados){.internal-link target=_blank}.
-
-## Dependências com `yield` e `except`
-
-Se você capturar uma exceção com `except` em uma dependência que utilize `yield` e ela não for levantada novamente (ou uma nova exceção for levantada), o FastAPI não será capaz de identifcar que houve uma exceção, da mesma forma que aconteceria com Python puro:
+Se você capturar uma exceção com `except` em uma dependência que utilize `yield` e ela não for levantada novamente (ou uma nova exceção for levantada), o FastAPI não será capaz de identificar que houve uma exceção, da mesma forma que aconteceria com Python puro:
{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *}
Neste caso, o cliente irá ver uma resposta *HTTP 500 Internal Server Error* como deveria acontecer, já que não estamos levantando nenhuma `HTTPException` ou coisa parecida, mas o servidor **não terá nenhum log** ou qualquer outra indicação de qual foi o erro. 😱
-### Sempre levante (`raise`) exceções em Dependências com `yield` e `except`
+### Sempre levante (`raise`) em Dependências com `yield` e `except` { #always-raise-in-dependencies-with-yield-and-except }
-Se você capturar uma exceção em uma dependência com `yield`, a menos que você esteja levantando outra `HTTPException` ou coisa parecida, você deveria relançar a exceção original.
+Se você capturar uma exceção em uma dependência com `yield`, a menos que você esteja levantando outra `HTTPException` ou coisa parecida, **você deve relançar a exceção original**.
Você pode relançar a mesma exceção utilizando `raise`:
{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *}
-//// tab | python 3.8+ non-annotated
-
-/// tip | Dica
-
-Utilize a versão com `Annotated` se possível.
-
-///
-
-{* ../../docs_src/dependencies/tutorial008d.py hl[15] *}
-
-////
-
Agora o cliente irá receber a mesma resposta *HTTP 500 Internal Server Error*, mas o servidor terá nosso `InternalError` personalizado nos logs. 😎
-## Execução de dependências com `yield`
+## Execução de dependências com `yield` { #execution-of-dependencies-with-yield }
A sequência de execução é mais ou menos como esse diagrama. O tempo passa do topo para baixo. E cada coluna é uma das partes interagindo ou executando código.
@@ -270,57 +174,69 @@ participant tasks as Tarefas de Background
Apenas **uma resposta** será enviada para o cliente. Ela pode ser uma das respostas de erro, ou então a resposta da *operação de rota*.
-Após uma dessas respostas ser enviada, nenhuma outra resposta pode ser enviada
+Após uma dessas respostas ser enviada, nenhuma outra resposta pode ser enviada.
///
/// tip | Dica
-Esse diagrama mostra `HttpException`, mas você pode levantar qualquer outra exceção que você capture em uma dependência com `yield` ou um [Manipulador de exceções personalizado](../handling-errors.md#instalando-manipuladores-de-excecoes-customizados){.internal-link target=_blank}.
-
-Se você lançar qualquer exceção, ela será passada para as dependências com yield, inlcuindo a `HTTPException`. Na maioria dos casos você vai querer relançar essa mesma exceção ou uma nova a partir da dependência com `yield` para garantir que ela seja tratada adequadamente.
+Se você levantar qualquer exceção no código da *função de operação de rota*, ela será passada para as dependências com `yield`, incluindo `HTTPException`. Na maioria dos casos, você vai querer relançar essa mesma exceção ou uma nova a partir da dependência com `yield` para garantir que ela seja tratada adequadamente.
///
-## Dependências com `yield`, `HTTPException`, `except` e Tarefas de Background
+## Saída antecipada e `scope` { #early-exit-and-scope }
-/// warning | Aviso
+Normalmente, o código de saída das dependências com `yield` é executado **após a resposta** ser enviada ao cliente.
-Você provavelmente não precisa desses detalhes técnicos, você pode pular essa seção e continuar na próxima seção abaixo.
+Mas se você sabe que não precisará usar a dependência depois de retornar da *função de operação de rota*, você pode usar `Depends(scope="function")` para dizer ao FastAPI que deve fechar a dependência depois que a *função de operação de rota* retornar, mas **antes** de a **resposta ser enviada**.
-Esses detalhes são úteis principalmente se você estiver usando uma versão do FastAPI anterior à 0.106.0 e utilizando recursos de dependências com `yield` em tarefas de background.
+{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *}
-///
+`Depends()` recebe um parâmetro `scope` que pode ser:
-### Dependências com `yield` e `except`, Detalhes Técnicos
+* `"function"`: iniciar a dependência antes da *função de operação de rota* que trata a requisição, encerrar a dependência depois que a *função de operação de rota* termina, mas **antes** de a resposta ser enviada de volta ao cliente. Assim, a função da dependência será executada **em torno** da *função de operação de rota*.
+* `"request"`: iniciar a dependência antes da *função de operação de rota* que trata a requisição (semelhante a quando se usa `"function"`), mas encerrar **depois** que a resposta é enviada de volta ao cliente. Assim, a função da dependência será executada **em torno** do ciclo de **requisição** e resposta.
-Antes do FastAPI 0.110.0, se você utilizasse uma dependência com `yield`, e então capturasse uma dependência com `except` nessa dependência, caso a exceção não fosse relançada, ela era automaticamente lançada para qualquer manipulador de exceções ou o manipulador de erros interno do servidor.
+Se não for especificado e a dependência tiver `yield`, ela terá `scope` igual a `"request"` por padrão.
-Isso foi modificado na versão 0.110.0 para consertar o consumo de memória não controlado das exceções relançadas automaticamente sem um manipulador (erros internos do servidor), e para manter o comportamento consistente com o código Python tradicional.
+### `scope` para subdependências { #scope-for-sub-dependencies }
-### Tarefas de Background e Dependências com `yield`, Detalhes Técnicos
+Quando você declara uma dependência com `scope="request"` (o padrão), qualquer subdependência também precisa ter `scope` igual a `"request"`.
-Antes do FastAPI 0.106.0, levantar exceções após um `yield` não era possível, o código de saída nas dependências com `yield` era executado *após* a resposta ser enviada, então os [Manipuladores de Exceções](../handling-errors.md#instalando-manipuladores-de-excecoes-customizados){.internal-link target=_blank} já teriam executado.
+Mas uma dependência com `scope` igual a `"function"` pode ter dependências com `scope` igual a `"function"` e com `scope` igual a `"request"`.
-Isso foi implementado dessa forma principalmente para permitir que os mesmos objetos fornecidos ("yielded") pelas dependências dentro de tarefas de background fossem reutilizados, por que o código de saída era executado antes das tarefas de background serem finalizadas.
+Isso porque qualquer dependência precisa conseguir executar seu código de saída antes das subdependências, pois pode ainda precisar usá-las durante seu código de saída.
-Ainda assim, como isso exigiria esperar que a resposta navegasse pela rede enquanto mantia ativo um recurso desnecessário na dependência com yield (por exemplo, uma conexão com banco de dados), isso mudou na versão 0.106.0 do FastAPI.
+```mermaid
+sequenceDiagram
-/// tip | Dica
+participant client as Cliente
+participant dep_req as Dep scope="request"
+participant dep_func as Dep scope="function"
+participant operation as Operação de Rota
-Adicionalmente, uma tarefa de background é, normalmente, um conjunto de lógicas independentes que devem ser manipuladas separadamente, com seus próprios recursos (e.g. sua própria conexão com banco de dados).
+ client ->> dep_req: Iniciar requisição
+ Note over dep_req: Executar código até o yield
+ dep_req ->> dep_func: Passar dependência
+ Note over dep_func: Executar código até o yield
+ dep_func ->> operation: Executar operação de rota com dependência
+ operation ->> dep_func: Retornar da operação de rota
+ Note over dep_func: Executar código após o yield
+ Note over dep_func: ✅ Dependência fechada
+ dep_func ->> client: Enviar resposta ao cliente
+ Note over client: Resposta enviada
+ Note over dep_req: Executar código após o yield
+ Note over dep_req: ✅ Dependência fechada
+```
-Então, dessa forma você provavelmente terá um código mais limpo.
+## Dependências com `yield`, `HTTPException`, `except` e Tarefas de Background { #dependencies-with-yield-httpexception-except-and-background-tasks }
-///
+Dependências com `yield` evoluíram ao longo do tempo para cobrir diferentes casos de uso e corrigir alguns problemas.
-Se você costumava depender desse comportamento, agora você precisa criar os recursos para uma tarefa de background dentro dela mesma, e usar internamente apenas dados que não dependam de recursos de dependências com `yield`.
+Se você quiser ver o que mudou em diferentes versões do FastAPI, você pode ler mais sobre isso no guia avançado, em [Dependências Avançadas - Dependências com `yield`, `HTTPException`, `except` e Tarefas de Background](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}.
+## Gerenciadores de contexto { #context-managers }
-Por exemplo, em vez de utilizar a mesma sessão do banco de dados, você criaria uma nova sessão dentro da tarefa de background, e você obteria os objetos do banco de dados utilizando essa nova sessão. E então, em vez de passar o objeto obtido do banco de dados como um parâmetro para a função da tarefa de background, você passaria o ID desse objeto e buscaria ele novamente dentro da função da tarefa de background.
-
-## Gerenciadores de contexto
-
-### O que são gerenciadores de contexto
+### O que são "Gerenciadores de Contexto" { #what-are-context-managers }
"Gerenciadores de Contexto" são qualquer um dos objetos Python que podem ser utilizados com a declaração `with`.
@@ -338,9 +254,9 @@ Quando o bloco `with` finaliza, ele se certifica de fechar o arquivo, mesmo que
Quando você cria uma dependência com `yield`, o **FastAPI** irá criar um gerenciador de contexto internamente para ela, e combiná-lo com algumas outras ferramentas relacionadas.
-### Utilizando gerenciadores de contexto em dependências com `yield`
+### Utilizando gerenciadores de contexto em dependências com `yield` { #using-context-managers-in-dependencies-with-yield }
-/// warning | Aviso
+/// warning | Atenção
Isso é uma ideia mais ou menos "avançada".
@@ -348,18 +264,18 @@ Se você está apenas iniciando com o **FastAPI** você pode querer pular isso p
///
-Em python, você pode criar Gerenciadores de Contexto ao criar uma classe com dois métodos: `__enter__()` e `__exit__()`.
+Em Python, você pode criar Gerenciadores de Contexto ao criar uma classe com dois métodos: `__enter__()` e `__exit__()`.
-Você também pode usá-los dentro de dependências com `yield` do **FastAPI** ao utilizar `with` ou `async with` dentro da função da dependência:
+Você também pode usá-los dentro de dependências com `yield` do **FastAPI** ao utilizar
+`with` ou `async with` dentro da função da dependência:
-{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *}
+{* ../../docs_src/dependencies/tutorial010_py39.py hl[1:9,13] *}
/// tip | Dica
Outra forma de criar um gerenciador de contexto é utilizando:
* `@contextlib.contextmanager` ou
-
* `@contextlib.asynccontextmanager`
Para decorar uma função com um único `yield`.
diff --git a/docs/pt/docs/tutorial/dependencies/global-dependencies.md b/docs/pt/docs/tutorial/dependencies/global-dependencies.md
index a9a7e3b899..4d6e8a69a0 100644
--- a/docs/pt/docs/tutorial/dependencies/global-dependencies.md
+++ b/docs/pt/docs/tutorial/dependencies/global-dependencies.md
@@ -1,15 +1,16 @@
-# Dependências Globais
+# Dependências Globais { #global-dependencies }
-Para alguns tipos de aplicação específicos você pode querer adicionar dependências para toda a aplicação.
+Para alguns tipos de aplicação você pode querer adicionar dependências para toda a aplicação.
-De forma semelhante a [adicionar dependências (`dependencies`) em *decoradores de operação de rota*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, você pode adicioná-las à aplicação `FastAPI`.
+De forma semelhante a [adicionar `dependencies` aos *decoradores de operação de rota*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, você pode adicioná-las à aplicação `FastAPI`.
Nesse caso, elas serão aplicadas a todas as *operações de rota* da aplicação:
-{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[16] *}
+{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[17] *}
-E todos os conceitos apresentados na sessão sobre [adicionar dependências em *decoradores de operação de rota*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} ainda se aplicam, mas nesse caso, para todas as *operações de rota* da aplicação.
-## Dependências para conjuntos de *operações de rota*
+E todos os conceitos apresentados na seção sobre [adicionar `dependencies` aos *decoradores de operação de rota*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} ainda se aplicam, mas nesse caso, para todas as *operações de rota* da aplicação.
-Mais para a frente, quando você ler sobre como estruturar aplicações maiores ([Bigger Applications - Multiple Files](../../tutorial/bigger-applications.md){.internal-link target=_blank}), possivelmente com múltiplos arquivos, você irá aprender a declarar um único parâmetro `dependencies` para um conjunto de *operações de rota*.
+## Dependências para conjuntos de *operações de rota* { #dependencies-for-groups-of-path-operations }
+
+Mais para a frente, quando você ler sobre como estruturar aplicações maiores ([Aplicações Maiores - Múltiplos Arquivos](../../tutorial/bigger-applications.md){.internal-link target=_blank}), possivelmente com múltiplos arquivos, você irá aprender a declarar um único parâmetro `dependencies` para um conjunto de *operações de rota*.
diff --git a/docs/pt/docs/tutorial/dependencies/index.md b/docs/pt/docs/tutorial/dependencies/index.md
index 1500b715a7..bdfe1ac39e 100644
--- a/docs/pt/docs/tutorial/dependencies/index.md
+++ b/docs/pt/docs/tutorial/dependencies/index.md
@@ -1,10 +1,10 @@
-# Dependências
+# Dependências { #dependencies }
-O **FastAPI** possui um poderoso, mas intuitivo sistema de **Injeção de Dependência**.
+O **FastAPI** possui um poderoso, mas intuitivo sistema de **Injeção de Dependência**.
Esse sistema foi pensado para ser fácil de usar, e permitir que qualquer desenvolvedor possa integrar facilmente outros componentes ao **FastAPI**.
-## O que é "Injeção de Dependência"
+## O que é "Injeção de Dependência" { #what-is-dependency-injection }
**"Injeção de Dependência"** no mundo da programação significa, que existe uma maneira de declarar no seu código (nesse caso, suas *funções de operação de rota*) para declarar as coisas que ele precisa para funcionar e que serão utilizadas: "dependências".
@@ -19,13 +19,13 @@ Isso é bastante útil quando você precisa:
Tudo isso, enquanto minimizamos a repetição de código.
-## Primeiros passos
+## Primeiros passos { #first-steps }
Vamos ver um exemplo simples. Tão simples que não será muito útil, por enquanto.
Mas dessa forma podemos focar em como o sistema de **Injeção de Dependência** funciona.
-### Criando uma dependência, ou "injetável"
+### Criando uma dependência, ou "injetável" { #create-a-dependency-or-dependable }
Primeiro vamos focar na dependência.
@@ -57,15 +57,15 @@ FastAPI passou a suportar a notação `Annotated` (e começou a recomendá-la) n
Se você utiliza uma versão anterior, ocorrerão erros ao tentar utilizar `Annotated`.
-Certifique-se de [Atualizar a versão do FastAPI](../../deployment/versions.md#atualizando-as-versoes-do-fastapi){.internal-link target=_blank} para pelo menos 0.95.1 antes de usar `Annotated`.
+Certifique-se de [Atualizar a versão do FastAPI](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} para pelo menos 0.95.1 antes de usar `Annotated`.
///
-### Importando `Depends`
+### Importando `Depends` { #import-depends }
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *}
-### Declarando a dependência, no "dependente"
+### Declarando a dependência, no "dependente" { #declare-the-dependency-in-the-dependant }
Da mesma forma que você utiliza `Body`, `Query`, etc. Como parâmetros de sua *função de operação de rota*, utilize `Depends` com um novo parâmetro:
@@ -106,7 +106,7 @@ common_parameters --> read_users
Assim, você escreve um código compartilhado apenas uma vez e o **FastAPI** se encarrega de chamá-lo em suas *operações de rota*.
-/// check | Checando
+/// check | Verifique
Perceba que você não precisa criar uma classe especial e enviar a dependência para algum outro lugar em que o **FastAPI** a "registre" ou realize qualquer operação similar.
@@ -114,7 +114,7 @@ Você apenas envia para `Depends` e o **FastAPI** sabe como fazer o resto.
///
-## Compartilhando dependências `Annotated`
+## Compartilhando dependências `Annotated` { #share-annotated-dependencies }
Nos exemplos acima, você pode ver que existe uma pequena **duplicação de código**.
@@ -140,7 +140,7 @@ As dependências continuarão funcionando como esperado, e a **melhor parte** é
Isso é especialmente útil para uma **base de código grande** onde **as mesmas dependências** são utilizadas repetidamente em **muitas *operações de rota***.
-## `Async` ou não, eis a questão
+## `Async` ou não, eis a questão { #to-async-or-not-to-async }
Como as dependências também serão chamadas pelo **FastAPI** (da mesma forma que *funções de operação de rota*), as mesmas regras se aplicam ao definir suas funções.
@@ -152,11 +152,11 @@ Não faz diferença. O **FastAPI** sabe o que fazer.
/// note | Nota
-Caso você não conheça, veja em [Async: *"Com Pressa?"*](../../async.md#com-pressa){.internal-link target=_blank} a sessão acerca de `async` e `await` na documentação.
+Caso você não conheça, veja em [Async: *"Com Pressa?"*](../../async.md#in-a-hurry){.internal-link target=_blank} a sessão acerca de `async` e `await` na documentação.
///
-## Integrando com OpenAPI
+## Integrando com OpenAPI { #integrated-with-openapi }
Todas as declarações de requisições, validações e requisitos para suas dependências (e sub-dependências) serão integradas em um mesmo esquema OpenAPI.
@@ -164,7 +164,7 @@ Então, a documentação interativa também terá toda a informação sobre essa
-## Caso de Uso Simples
+## Caso de Uso Simples { #simple-usage }
Se você parar para ver, *funções de operação de rota* são declaradas para serem usadas sempre que uma *rota* e uma *operação* se encaixam, e então o **FastAPI** se encarrega de chamar a função correspondente com os argumentos corretos, extraindo os dados da requisição.
@@ -182,7 +182,7 @@ Outros termos comuns para essa mesma ideia de "injeção de dependência" são:
* injetáveis
* componentes
-## Plug-ins em **FastAPI**
+## Plug-ins em **FastAPI** { #fastapi-plug-ins }
Integrações e "plug-ins" podem ser construídos com o sistema de **Injeção de Dependência**. Mas na verdade, **não há necessidade de criar "plug-ins"**, já que utilizando dependências é possível declarar um número infinito de integrações e interações que se tornam disponíveis para as suas *funções de operação de rota*.
@@ -190,7 +190,7 @@ E as dependências pode ser criadas de uma forma bastante simples e intuitiva qu
Você verá exemplos disso nos próximos capítulos, acerca de bancos de dados relacionais e NoSQL, segurança, etc.
-## Compatibilidade do **FastAPI**
+## Compatibilidade do **FastAPI** { #fastapi-compatibility }
A simplicidade do sistema de injeção de dependência do **FastAPI** faz ele compatível com:
@@ -203,7 +203,7 @@ A simplicidade do sistema de injeção de dependência do **FastAPI** faz ele co
* sistemas de injeção de dados de resposta
* etc.
-## Simples e Poderoso
+## Simples e Poderoso { #simple-and-powerful }
Mesmo que o sistema hierárquico de injeção de dependência seja simples de definir e utilizar, ele ainda é bastante poderoso.
@@ -243,7 +243,7 @@ admin_user --> activate_user
paying_user --> pro_items
```
-## Integração com **OpenAPI**
+## Integração com **OpenAPI** { #integrated-with-openapi_1 }
Todas essas dependências, ao declarar os requisitos para suas *operações de rota*, também adicionam parâmetros, validações, etc.
diff --git a/docs/pt/docs/tutorial/dependencies/sub-dependencies.md b/docs/pt/docs/tutorial/dependencies/sub-dependencies.md
index 3975ce182e..e2dc9bbf9a 100644
--- a/docs/pt/docs/tutorial/dependencies/sub-dependencies.md
+++ b/docs/pt/docs/tutorial/dependencies/sub-dependencies.md
@@ -1,4 +1,4 @@
-# Subdependências
+# Subdependências { #sub-dependencies }
Você pode criar dependências que possuem **subdependências**.
@@ -6,9 +6,9 @@ Elas podem ter o nível de **profundidade** que você achar necessário.
O **FastAPI** se encarrega de resolver essas dependências.
-## Primeira dependência "injetável"
+## Primeira dependência "dependable" { #first-dependency-dependable }
-Você pode criar uma primeira dependência (injetável) dessa forma:
+Você pode criar uma primeira dependência ("dependable") dessa forma:
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *}
@@ -16,20 +16,20 @@ Esse código declara um parâmetro de consulta opcional, `q`, com o tipo `str`,
Isso é bastante simples (e não muito útil), mas irá nos ajudar a focar em como as subdependências funcionam.
-## Segunda dependência, "injetável" e "dependente"
+## Segunda dependência, "dependable" e "dependente" { #second-dependency-dependable-and-dependant }
-Então, você pode criar uma outra função para uma dependência (um "injetável") que ao mesmo tempo declara sua própria dependência (o que faz dela um "dependente" também):
+Então, você pode criar uma outra função para uma dependência (um "dependable") que ao mesmo tempo declara sua própria dependência (o que faz dela um "dependente" também):
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *}
Vamos focar nos parâmetros declarados:
-* Mesmo que essa função seja uma dependência ("injetável") por si mesma, ela também declara uma outra dependência (ela "depende" de outra coisa).
+* Mesmo que essa função seja uma dependência ("dependable") por si mesma, ela também declara uma outra dependência (ela "depende" de outra coisa).
* Ela depende do `query_extractor`, e atribui o valor retornado pela função ao parâmetro `q`.
* Ela também declara um cookie opcional `last_query`, do tipo `str`.
* Se o usuário não passou nenhuma consulta `q`, a última consulta é utilizada, que foi salva em um cookie anteriormente.
-## Utilizando a dependência
+## Utilizando a dependência { #use-the-dependency }
Então podemos utilizar a dependência com:
@@ -54,7 +54,7 @@ read_query["/items/"]
query_extractor --> query_or_cookie_extractor --> read_query
```
-## Utilizando a mesma dependência múltiplas vezes
+## Utilizando a mesma dependência múltiplas vezes { #using-the-same-dependency-multiple-times }
Se uma de suas dependências é declarada várias vezes para a mesma *operação de rota*, por exemplo, múltiplas dependências com uma mesma subdependência, o **FastAPI** irá chamar essa subdependência uma única vez para cada requisição.
@@ -62,7 +62,7 @@ E o valor retornado é salvo em um ISO.
+Então, um objeto `datetime` teria que ser convertido em um `str` contendo os dados no formato ISO.
Da mesma forma, este banco de dados não receberia um modelo Pydantic (um objeto com atributos), apenas um `dict`.
diff --git a/docs/pt/docs/tutorial/extra-data-types.md b/docs/pt/docs/tutorial/extra-data-types.md
index 09c838be0a..97e4cc4757 100644
--- a/docs/pt/docs/tutorial/extra-data-types.md
+++ b/docs/pt/docs/tutorial/extra-data-types.md
@@ -1,4 +1,4 @@
-# Tipos de dados extras
+# Tipos de dados extras { #extra-data-types }
Até agora, você tem usado tipos de dados comuns, tais como:
@@ -17,7 +17,7 @@ E você ainda terá os mesmos recursos que viu até agora:
* Validação de dados.
* Anotação e documentação automáticas.
-## Outros tipos de dados
+## Outros tipos de dados { #other-data-types }
Aqui estão alguns dos tipos de dados adicionais que você pode usar:
@@ -36,7 +36,7 @@ Aqui estão alguns dos tipos de dados adicionais que você pode usar:
* `datetime.timedelta`:
* O `datetime.timedelta` do Python.
* Em requisições e respostas será representado como um `float` de segundos totais.
- * O Pydantic também permite representá-lo como uma "codificação ISO 8601 diferença de tempo", cheque a documentação para mais informações.
+ * O Pydantic também permite representá-lo como uma "codificação ISO 8601 diferença de tempo", cheque a documentação para mais informações.
* `frozenset`:
* Em requisições e respostas, será tratado da mesma forma que um `set`:
* Nas requisições, uma lista será lida, eliminando duplicadas e convertendo-a em um `set`.
@@ -49,14 +49,14 @@ Aqui estão alguns dos tipos de dados adicionais que você pode usar:
* `Decimal`:
* O `Decimal` padrão do Python.
* Em requisições e respostas será representado como um `float`.
-* Você pode checar todos os tipos de dados válidos do Pydantic aqui: Tipos de dados do Pydantic.
+* Você pode checar todos os tipos de dados válidos do Pydantic aqui: Tipos de dados do Pydantic.
-## Exemplo
+## Exemplo { #example }
Aqui está um exemplo de *operação de rota* com parâmetros utilizando-se de alguns dos tipos acima.
-{* ../../docs_src/extra_data_types/tutorial001.py hl[1,3,12:16] *}
+{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *}
Note que os parâmetros dentro da função tem seu tipo de dados natural, e você pode, por exemplo, realizar manipulações normais de data, como:
-{* ../../docs_src/extra_data_types/tutorial001.py hl[18:19] *}
+{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[18:19] *}
diff --git a/docs/pt/docs/tutorial/extra-models.md b/docs/pt/docs/tutorial/extra-models.md
index cccef16e37..c0d22df573 100644
--- a/docs/pt/docs/tutorial/extra-models.md
+++ b/docs/pt/docs/tutorial/extra-models.md
@@ -1,4 +1,4 @@
-# Modelos Adicionais
+# Modelos Adicionais { #extra-models }
Continuando com o exemplo anterior, será comum ter mais de um modelo relacionado.
@@ -6,9 +6,9 @@ Isso é especialmente o caso para modelos de usuários, porque:
* O **modelo de entrada** precisa ser capaz de ter uma senha.
* O **modelo de saída** não deve ter uma senha.
-* O **modelo de banco de dados** provavelmente precisaria ter uma senha criptografada.
+* O **modelo de banco de dados** provavelmente precisaria ter uma senha com hash.
-/// danger
+/// danger | Cuidado
Nunca armazene senhas em texto simples dos usuários. Sempre armazene uma "hash segura" que você pode verificar depois.
@@ -16,15 +16,23 @@ Se não souber, você aprenderá o que é uma "senha hash" nos [capítulos de se
///
-## Múltiplos modelos
+## Múltiplos modelos { #multiple-models }
Aqui está uma ideia geral de como os modelos poderiam parecer com seus campos de senha e os lugares onde são usados:
-{* ../../docs_src/extra_models/tutorial001.py hl[9,11,16,22,24,29:30,33:35,40:41] *}
+{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
-### Sobre `**user_in.dict()`
+/// info | Informação
-#### O `.dict()` do Pydantic
+No Pydantic v1 o método se chamava `.dict()`, ele foi descontinuado (mas ainda é suportado) no Pydantic v2 e renomeado para `.model_dump()`.
+
+Os exemplos aqui usam `.dict()` por compatibilidade com o Pydantic v1, mas você deve usar `.model_dump()` se puder usar o Pydantic v2.
+
+///
+
+### Sobre `**user_in.dict()` { #about-user-in-dict }
+
+#### O `.dict()` do Pydantic { #pydantics-dict }
`user_in` é um modelo Pydantic da classe `UserIn`.
@@ -61,7 +69,7 @@ teríamos um `dict` Python com:
}
```
-#### Desembrulhando um `dict`
+#### Desembrulhando um `dict` { #unpacking-a-dict }
Se tomarmos um `dict` como `user_dict` e passarmos para uma função (ou classe) com `**user_dict`, o Python irá "desembrulhá-lo". Ele passará as chaves e valores do `user_dict` diretamente como argumentos chave-valor.
@@ -93,7 +101,7 @@ UserInDB(
)
```
-#### Um modelo Pydantic a partir do conteúdo de outro
+#### Um modelo Pydantic a partir do conteúdo de outro { #a-pydantic-model-from-the-contents-of-another }
Como no exemplo acima, obtivemos o `user_dict` a partir do `user_in.dict()`, este código:
@@ -108,11 +116,11 @@ seria equivalente a:
UserInDB(**user_in.dict())
```
-...porque `user_in.dict()` é um `dict`, e depois fazemos o Python "desembrulhá-lo" passando-o para UserInDB precedido por `**`.
+...porque `user_in.dict()` é um `dict`, e depois fazemos o Python "desembrulhá-lo" passando-o para `UserInDB` precedido por `**`.
Então, obtemos um modelo Pydantic a partir dos dados em outro modelo Pydantic.
-#### Desembrulhando um `dict` e palavras-chave extras
+#### Desembrulhando um `dict` e palavras-chave extras { #unpacking-a-dict-and-extra-keywords }
E, então, adicionando o argumento de palavra-chave extra `hashed_password=hashed_password`, como em:
@@ -132,13 +140,13 @@ UserInDB(
)
```
-/// warning
+/// warning | Atenção
-As funções adicionais de suporte são apenas para demonstração de um fluxo possível dos dados, mas é claro que elas não fornecem segurança real.
+As funções adicionais de suporte `fake_password_hasher` e `fake_save_user` servem apenas para demonstrar um fluxo possível dos dados, mas é claro que elas não fornecem segurança real.
///
-## Reduzir duplicação
+## Reduzir duplicação { #reduce-duplication }
Reduzir a duplicação de código é uma das ideias principais no **FastAPI**.
@@ -154,25 +162,25 @@ Toda conversão de dados, validação, documentação, etc. ainda funcionará no
Dessa forma, podemos declarar apenas as diferenças entre os modelos (com `password` em texto claro, com `hashed_password` e sem senha):
-{* ../../docs_src/extra_models/tutorial002.py hl[9,15:16,19:20,23:24] *}
+{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
-## `Union` ou `anyOf`
+## `Union` ou `anyOf` { #union-or-anyof }
-Você pode declarar uma resposta como o `Union` de dois tipos, o que significa que a resposta seria qualquer um dos dois.
+Você pode declarar uma resposta como o `Union` de dois ou mais tipos, o que significa que a resposta seria qualquer um deles.
Isso será definido no OpenAPI com `anyOf`.
-Para fazer isso, use a dica de tipo padrão do Python `typing.Union`:
+Para fazer isso, use a anotação de tipo padrão do Python `typing.Union`:
-/// note
+/// note | Nota
Ao definir um `Union`, inclua o tipo mais específico primeiro, seguido pelo tipo menos específico. No exemplo abaixo, o tipo mais específico `PlaneItem` vem antes de `CarItem` em `Union[PlaneItem, CarItem]`.
///
-{* ../../docs_src/extra_models/tutorial003.py hl[1,14:15,18:20,33] *}
+{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
-### `Union` no Python 3.10
+### `Union` no Python 3.10 { #union-in-python-3-10 }
Neste exemplo, passamos `Union[PlaneItem, CarItem]` como o valor do argumento `response_model`.
@@ -184,27 +192,27 @@ Se estivesse em uma anotação de tipo, poderíamos ter usado a barra vertical,
some_variable: PlaneItem | CarItem
```
-Mas se colocarmos isso em `response_model=PlaneItem | CarItem` teríamos um erro, pois o Python tentaria executar uma **operação inválida** entre `PlaneItem` e `CarItem` em vez de interpretar isso como uma anotação de tipo.
+Mas se colocarmos isso na atribuição `response_model=PlaneItem | CarItem`, teríamos um erro, pois o Python tentaria executar uma **operação inválida** entre `PlaneItem` e `CarItem` em vez de interpretar isso como uma anotação de tipo.
-## Lista de modelos
+## Lista de modelos { #list-of-models }
Da mesma forma, você pode declarar respostas de listas de objetos.
Para isso, use o padrão Python `typing.List` (ou simplesmente `list` no Python 3.9 e superior):
-{* ../../docs_src/extra_models/tutorial004.py hl[1,20] *}
+{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
-## Resposta com `dict` arbitrário
+## Resposta com `dict` arbitrário { #response-with-arbitrary-dict }
Você também pode declarar uma resposta usando um simples `dict` arbitrário, declarando apenas o tipo das chaves e valores, sem usar um modelo Pydantic.
Isso é útil se você não souber os nomes de campo / atributo válidos (que seriam necessários para um modelo Pydantic) antecipadamente.
-Neste caso, você pode usar `typing.Dict` (ou simplesmente dict no Python 3.9 e superior):
+Neste caso, você pode usar `typing.Dict` (ou simplesmente `dict` no Python 3.9 e superior):
-{* ../../docs_src/extra_models/tutorial005.py hl[1,8] *}
+{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
-## Em resumo
+## Recapitulação { #recap }
Use vários modelos Pydantic e herde livremente para cada caso.
diff --git a/docs/pt/docs/tutorial/first-steps.md b/docs/pt/docs/tutorial/first-steps.md
index e696bbbb7d..86cddde5da 100644
--- a/docs/pt/docs/tutorial/first-steps.md
+++ b/docs/pt/docs/tutorial/first-steps.md
@@ -1,8 +1,8 @@
-# Primeiros Passos
+# Primeiros Passos { #first-steps }
O arquivo FastAPI mais simples pode se parecer com:
-{* ../../docs_src/first_steps/tutorial001.py *}
+{* ../../docs_src/first_steps/tutorial001_py39.py *}
Copie o conteúdo para um arquivo `main.py`.
@@ -48,15 +48,15 @@ $ fastapi dev http://127.0.0.1:8000.
@@ -66,7 +66,7 @@ Você verá essa resposta em JSON:
{"message": "Hello World"}
```
-### Documentação Interativa de APIs
+### Documentação Interativa de APIs { #interactive-api-docs }
Agora vá para http://127.0.0.1:8000/docs.
@@ -74,7 +74,7 @@ Você verá a documentação interativa automática da API (fornecida por http://127.0.0.1:8000/redoc.
@@ -82,31 +82,31 @@ Você verá a documentação alternativa automática (fornecida por OpenAPI é uma especificação que determina como definir um *schema* da sua API.
-Esta definição de *schema* inclui as rotas da sua API, os parâmetros possíveis que elas usam, etc.
+Esta definição de *schema* inclui os paths da sua API, os parâmetros possíveis que eles usam, etc.
-#### "*Schema*" de dados
+#### "*Schema*" de dados { #data-schema }
O termo "*schema*" também pode se referir à forma de alguns dados, como um conteúdo JSON.
Nesse caso, significaria os atributos JSON e os tipos de dados que eles possuem, etc.
-#### OpenAPI e JSON *Schema*
+#### OpenAPI e JSON Schema { #openapi-and-json-schema }
-OpenAPI define um *schema* de API para sua API. E esse *schema* inclui definições (ou "*schemas*") dos dados enviados e recebidos por sua API usando **JSON *Schema***, o padrão para *schemas* de dados JSON.
+OpenAPI define um *schema* de API para sua API. E esse *schema* inclui definições (ou "*schemas*") dos dados enviados e recebidos por sua API usando **JSON Schema**, o padrão para *schemas* de dados JSON.
-#### Verifique o `openapi.json`
+#### Verifique o `openapi.json` { #check-the-openapi-json }
Se você está curioso(a) sobre a aparência do *schema* bruto OpenAPI, o FastAPI gera automaticamente um JSON (*schema*) com as descrições de toda a sua API.
@@ -116,7 +116,7 @@ Ele mostrará um JSON começando com algo como:
```JSON
{
- "openapi": "3.0.2",
+ "openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
@@ -135,7 +135,7 @@ Ele mostrará um JSON começando com algo como:
...
```
-#### Para que serve o OpenAPI
+#### Para que serve o OpenAPI { #what-is-openapi-for }
O *schema* OpenAPI é o que possibilita os dois sistemas de documentação interativos mostrados.
@@ -143,15 +143,51 @@ E existem dezenas de alternativas, todas baseadas em OpenAPI. Você pode facilme
Você também pode usá-lo para gerar código automaticamente para clientes que se comunicam com sua API. Por exemplo, aplicativos front-end, móveis ou IoT.
-## Recapitulando, passo a passo
+### Faça o deploy da sua aplicação (opcional) { #deploy-your-app-optional }
-### Passo 1: importe `FastAPI`
+Você pode, opcionalmente, fazer o deploy da sua aplicação FastAPI na FastAPI Cloud; acesse e entre na lista de espera, se ainda não entrou. 🚀
-{* ../../docs_src/first_steps/tutorial001.py hl[1] *}
+Se você já tem uma conta na **FastAPI Cloud** (nós convidamos você da lista de espera 😉), pode fazer o deploy da sua aplicação com um único comando.
+
+Antes do deploy, certifique-se de que está autenticado:
+
+get
+* o path `/`
+* usando uma operação get
-/// info | `@decorador`
+/// info | Informações sobre `@decorator`
Essa sintaxe `@alguma_coisa` em Python é chamada de "decorador".
@@ -245,9 +281,9 @@ Você o coloca em cima de uma função. Como um chapéu decorativo (acho que é
Um "decorador" pega a função abaixo e faz algo com ela.
-Em nosso caso, este decorador informa ao **FastAPI** que a função abaixo corresponde a **rota** `/` com uma **operação** `get`.
+Em nosso caso, este decorador informa ao **FastAPI** que a função abaixo corresponde ao **path** `/` com uma **operação** `get`.
-É o "**decorador de rota**".
+É o "**decorador de operação de rota**".
///
@@ -276,48 +312,69 @@ Por exemplo, ao usar GraphQL, você normalmente executa todas as ações usando
///
-### Passo 4: defina uma **função de rota**
+### Passo 4: defina a função de operação de rota { #step-4-define-the-path-operation-function }
-Esta é a nossa "**função de rota**":
+Esta é a nossa "**função de operação de rota**":
-* **rota**: é `/`.
+* **path**: é `/`.
* **operação**: é `get`.
* **função**: é a função abaixo do "decorador" (abaixo do `@app.get("/")`).
-{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
+{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *}
Esta é uma função Python.
-Ela será chamada pelo **FastAPI** sempre que receber uma requisição para a URL "`/ `" usando uma operação `GET`.
+Ela será chamada pelo **FastAPI** sempre que receber uma requisição para a URL "`/`" usando uma operação `GET`.
-Neste caso, é uma função `assíncrona`.
+Neste caso, é uma função `async`.
---
Você também pode defini-la como uma função normal em vez de `async def`:
-{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
+{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *}
/// note | Nota
-Se você não sabe a diferença, verifique o [Async: *"Com pressa?"*](../async.md#com-pressa){.internal-link target=_blank}.
+Se você não sabe a diferença, verifique o [Async: *"Com pressa?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
///
-### Passo 5: retorne o conteúdo
+### Passo 5: retorne o conteúdo { #step-5-return-the-content }
-{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
+{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *}
Você pode retornar um `dict`, `list` e valores singulares como `str`, `int`, etc.
-Você também pode devolver modelos Pydantic (você verá mais sobre isso mais tarde).
+Você também pode devolver modelos Pydantic ( você verá mais sobre isso mais tarde).
Existem muitos outros objetos e modelos que serão convertidos automaticamente para JSON (incluindo ORMs, etc). Tente usar seus favoritos, é altamente provável que já sejam compatíveis.
-## Recapitulando
+### Passo 6: Faça o deploy { #step-6-deploy-it }
+
+Faça o deploy da sua aplicação para a **FastAPI Cloud** com um comando: `fastapi deploy`. 🎉
+
+#### Sobre o FastAPI Cloud { #about-fastapi-cloud }
+
+A **FastAPI Cloud** é construída pelo mesmo autor e equipe por trás do **FastAPI**.
+
+Ela simplifica o processo de **construir**, **fazer deploy** e **acessar** uma API com o mínimo de esforço.
+
+Traz a mesma **experiência do desenvolvedor** de criar aplicações com FastAPI para **fazer o deploy** delas na nuvem. 🎉
+
+A FastAPI Cloud é a principal patrocinadora e financiadora dos projetos open source do ecossistema *FastAPI and friends*. ✨
+
+#### Faça o deploy em outros provedores de nuvem { #deploy-to-other-cloud-providers }
+
+FastAPI é open source e baseado em padrões. Você pode fazer deploy de aplicações FastAPI em qualquer provedor de nuvem que preferir.
+
+Siga os tutoriais do seu provedor de nuvem para fazer deploy de aplicações FastAPI com eles. 🤓
+
+## Recapitulando { #recap }
* Importe `FastAPI`.
* Crie uma instância do `app`.
-* Coloque o **decorador que define a operação** (como `@app.get("/")`).
-* Escreva uma **função para a operação da rota** (como `def root(): ...`) abaixo.
-* Execute o servidor de desenvolvimento (como `uvicorn main:app --reload`).
+* Escreva um **decorador de operação de rota** usando decoradores como `@app.get("/")`.
+* Defina uma **função de operação de rota**; por exemplo, `def root(): ...`.
+* Execute o servidor de desenvolvimento usando o comando `fastapi dev`.
+* Opcionalmente, faça o deploy da sua aplicação com `fastapi deploy`.
diff --git a/docs/pt/docs/tutorial/handling-errors.md b/docs/pt/docs/tutorial/handling-errors.md
index 5cb92c744f..1c162c205f 100644
--- a/docs/pt/docs/tutorial/handling-errors.md
+++ b/docs/pt/docs/tutorial/handling-errors.md
@@ -1,4 +1,4 @@
-# Manipulação de erros
+# Manipulação de erros { #handling-errors }
Há diversas situações em que você precisa notificar um erro a um cliente que está utilizando a sua API.
@@ -11,7 +11,6 @@ Pode ser que você precise comunicar ao cliente que:
* O item que o cliente está tentando acessar não existe.
* etc.
-
Nesses casos, você normalmente retornaria um **HTTP status code** próximo ao status code na faixa do status code **400** (do 400 ao 499).
Isso é bastante similar ao caso do HTTP status code 200 (do 200 ao 299). Esses "200" status codes significam que, de algum modo, houve sucesso na requisição.
@@ -20,15 +19,15 @@ Os status codes na faixa dos 400 significam que houve um erro por parte do clien
Você se lembra de todos aqueles erros (e piadas) a respeito do "**404 Not Found**"?
-## Use o `HTTPException`
+## Use o `HTTPException` { #use-httpexception }
Para retornar ao cliente *responses* HTTP com erros, use o `HTTPException`.
-### Import `HTTPException`
+### Import `HTTPException` { #import-httpexception }
-{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
+{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *}
-### Lance o `HTTPException` no seu código.
+### Lance o `HTTPException` no seu código { #raise-an-httpexception-in-your-code }
`HTTPException`, ao fundo, nada mais é do que a conjunção entre uma exceção comum do Python e informações adicionais relevantes para APIs.
@@ -40,15 +39,14 @@ O benefício de lançar uma exceção em vez de retornar um valor ficará mais e
Neste exemplo, quando o cliente pede, na requisição, por um item cujo ID não existe, a exceção com o status code `404` é lançada:
-{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
-
-### A response resultante
+{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *}
+### A response resultante { #the-resulting-response }
Se o cliente faz uma requisição para `http://example.com/items/foo` (um `item_id` `"foo"`), esse cliente receberá um HTTP status code 200, e uma resposta JSON:
-```
+```JSON
{
"item": "The Foo Wrestlers"
}
@@ -71,7 +69,7 @@ Esses tipos de dados são manipulados automaticamente pelo **FastAPI** e convert
///
-## Adicione headers customizados
+## Adicione headers customizados { #add-custom-headers }
Há certas situações em que é bastante útil poder adicionar headers customizados no HTTP error. Exemplo disso seria adicionar headers customizados para tipos de segurança.
@@ -79,17 +77,17 @@ Você provavelmente não precisará utilizar esses headers diretamente no seu c
Mas caso você precise, para um cenário mais complexo, você pode adicionar headers customizados:
-{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
+{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *}
-## Instalando manipuladores de exceções customizados
+## Instale manipuladores de exceções customizados { #install-custom-exception-handlers }
-Você pode adicionar manipuladores de exceção customizados com a mesma seção de utilidade de exceções presentes no Starlette
+Você pode adicionar manipuladores de exceção customizados com a mesma seção de utilidade de exceções presentes no Starlette.
Digamos que você tenha uma exceção customizada `UnicornException` que você (ou uma biblioteca que você use) precise lançar (`raise`).
Nesse cenário, se você precisa manipular essa exceção de modo global com o FastAPI, você pode adicionar um manipulador de exceção customizada com `@app.exception_handler()`.
-{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *}
+{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *}
Nesse cenário, se você fizer uma requisição para `/unicorns/yolo`, a *operação de caminho* vai lançar (`raise`) o `UnicornException`.
@@ -103,27 +101,31 @@ Dessa forma você receberá um erro "limpo", com o HTTP status code `418` e um J
/// note | Detalhes Técnicos
-Você também pode usar `from starlette.requests import Request` and `from starlette.responses import JSONResponse`.
+Você também pode usar `from starlette.requests import Request` e `from starlette.responses import JSONResponse`.
**FastAPI** disponibiliza o mesmo `starlette.responses` através do `fastapi.responses` por conveniência ao desenvolvedor. Contudo, a maior parte das respostas disponíveis vem diretamente do Starlette. O mesmo acontece com o `Request`.
///
-## Sobrescreva o manipulador padrão de exceções
+## Sobrescreva os manipuladores de exceções padrão { #override-the-default-exception-handlers }
**FastAPI** tem alguns manipuladores padrão de exceções.
-Esses manipuladores são os responsáveis por retornar o JSON padrão de respostas quando você lança (`raise`) o `HTTPException` e quando a requisição tem dados invalidos.
+Esses manipuladores são os responsáveis por retornar o JSON padrão de respostas quando você lança (`raise`) o `HTTPException` e quando a requisição tem dados inválidos.
Você pode sobrescrever esses manipuladores de exceção com os seus próprios manipuladores.
-## Sobrescreva exceções de validação da requisição
+### Sobrescreva exceções de validação da requisição { #override-request-validation-exceptions }
Quando a requisição contém dados inválidos, **FastAPI** internamente lança para o `RequestValidationError`.
+E também inclui um manipulador de exceções padrão para ele.
+
Para sobrescrevê-lo, importe o `RequestValidationError` e use-o com o `@app.exception_handler(RequestValidationError)` para decorar o manipulador de exceções.
-{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *}
+O manipulador de exceções receberá um `Request` e a exceção.
+
+{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *}
Se você for ao `/items/foo`, em vez de receber o JSON padrão com o erro:
@@ -145,34 +147,17 @@ Se você for ao `/items/foo`, em vez de receber o JSON padrão com o erro:
você receberá a versão em texto:
```
-1 validation error
-path -> item_id
- value is not a valid integer (type=type_error.integer)
+Validation errors:
+Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer
```
-### `RequestValidationError` vs `ValidationError`
+### Sobrescreva o manipulador de erro `HTTPException` { #override-the-httpexception-error-handler }
-/// warning | Aviso
-
-Você pode pular estes detalhes técnicos caso eles não sejam importantes para você neste momento.
-
-///
-
-`RequestValidationError` é uma subclasse do `ValidationError` existente no Pydantic.
-
-**FastAPI** faz uso dele para que você veja o erro no seu log, caso você utilize um modelo de Pydantic em `response_model`, e seus dados tenham erro.
-
-Contudo, o cliente ou usuário não terão acesso a ele. Ao contrário, o cliente receberá um "Internal Server Error" com o HTTP status code `500`.
-
-E assim deve ser porque seria um bug no seu código ter o `ValidationError` do Pydantic na sua *response*, ou em qualquer outro lugar do seu código (que não na requisição do cliente).
-
-E enquanto você conserta o bug, os clientes / usuários não deveriam ter acesso às informações internas do erro, porque, desse modo, haveria exposição de uma vulnerabilidade de segurança.
-
-Do mesmo modo, você pode sobreescrever o `HTTPException`.
+Do mesmo modo, você pode sobrescrever o `HTTPException`.
Por exemplo, você pode querer retornar uma *response* em *plain text* ao invés de um JSON para os seguintes erros:
-{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}
+{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *}
/// note | Detalhes Técnicos
@@ -182,11 +167,21 @@ Você pode usar `from starlette.responses import PlainTextResponse`.
///
-### Use o body do `RequestValidationError`.
+/// warning | Atenção
+
+Tenha em mente que o `RequestValidationError` contém as informações do nome do arquivo e da linha onde o erro de validação acontece, para que você possa mostrá-las nos seus logs com as informações relevantes, se quiser.
+
+Mas isso significa que, se você simplesmente convertê-lo para uma string e retornar essa informação diretamente, você pode acabar vazando um pouco de informação sobre o seu sistema; por isso, aqui o código extrai e mostra cada erro de forma independente.
+
+///
+
+### Use o body do `RequestValidationError` { #use-the-requestvalidationerror-body }
O `RequestValidationError` contém o `body` que ele recebeu de dados inválidos.
-Você pode utilizá-lo enquanto desenvolve seu app para conectar o *body* e debugá-lo, e assim retorná-lo ao usuário, etc.
+Você pode utilizá-lo enquanto desenvolve seu app para registrar o *body* e debugá-lo, e assim retorná-lo ao usuário, etc.
+
+{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *}
Tente enviar um item inválido como este:
@@ -197,7 +192,7 @@ Tente enviar um item inválido como este:
}
```
-Você receberá uma *response* informando-o de que a data é inválida, e contendo o *body* recebido:
+Você receberá uma *response* informando-o de que os dados são inválidos, e contendo o *body* recebido:
```JSON hl_lines="12-15"
{
@@ -218,30 +213,30 @@ Você receberá uma *response* informando-o de que a data é inválida, e conten
}
```
-#### O `HTTPException` do FastAPI vs o `HTTPException` do Starlette.
+#### O `HTTPException` do FastAPI vs o `HTTPException` do Starlette { #fastapis-httpexception-vs-starlettes-httpexception }
O **FastAPI** tem o seu próprio `HTTPException`.
E a classe de erro `HTTPException` do **FastAPI** herda da classe de erro do `HTTPException` do Starlette.
-A diferença entre os dois é a de que o `HTTPException` do **FastAPI** permite que você adicione *headers* que serão incluídos nas *responses*.
-
-Esses *headers* são necessários/utilizados internamente pelo OAuth 2.0 e também por outras utilidades de segurança.
+A única diferença é que o `HTTPException` do **FastAPI** aceita qualquer dado que possa ser convertido em JSON para o campo `detail`, enquanto o `HTTPException` do Starlette aceita apenas strings para esse campo.
Portanto, você pode continuar lançando o `HTTPException` do **FastAPI** normalmente no seu código.
Porém, quando você registrar um manipulador de exceção, você deve registrá-lo através do `HTTPException` do Starlette.
-Dessa forma, se qualquer parte do código interno, extensão ou plug-in do Starlette lançar o `HTTPException`, o seu manipulador de exceção poderá capturar esse lançamento e tratá-lo.
+Dessa forma, se qualquer parte do código interno, extensão ou plug-in do Starlette lançar um `HTTPException` do Starlette, o seu manipulador poderá capturar e tratá-lo.
+
+Neste exemplo, para poder ter ambos os `HTTPException` no mesmo código, a exceção do Starlette é renomeada para `StarletteHTTPException`:
```Python
from starlette.exceptions import HTTPException as StarletteHTTPException
```
-### Re-use os manipulares de exceção do **FastAPI**
+### Reutilize os manipuladores de exceção do **FastAPI** { #reuse-fastapis-exception-handlers }
Se você quer usar a exceção em conjunto com o mesmo manipulador de exceção *default* do **FastAPI**, você pode importar e re-usar esses manipuladores de exceção do `fastapi.exception_handlers`:
-{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *}
+{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *}
Nesse exemplo você apenas imprime (`print`) o erro com uma mensagem expressiva. Mesmo assim, dá para pegar a ideia. Você pode usar a exceção e então apenas re-usar o manipulador de exceção *default*.
diff --git a/docs/pt/docs/tutorial/header-param-models.md b/docs/pt/docs/tutorial/header-param-models.md
index 9a88dbfec4..046c99c291 100644
--- a/docs/pt/docs/tutorial/header-param-models.md
+++ b/docs/pt/docs/tutorial/header-param-models.md
@@ -1,8 +1,8 @@
-# Modelos de Parâmetros do Cabeçalho
+# Modelos de Parâmetros do Cabeçalho { #header-parameter-models }
Se você possui um grupo de **parâmetros de cabeçalho** relacionados, você pode criar um **modelo do Pydantic** para declará-los.
-Isso vai lhe permitir **reusar o modelo** em **múltiplos lugares** e também declarar validações e metadadados para todos os parâmetros de uma vez. 😎
+Isso vai lhe permitir **reusar o modelo** em **múltiplos lugares** e também declarar validações e metadados para todos os parâmetros de uma vez. 😎
/// note | Nota
@@ -10,7 +10,7 @@ Isso é possível desde a versão `0.115.0` do FastAPI. 🤓
///
-## Parâmetros do Cabeçalho com um Modelo Pydantic
+## Parâmetros do Cabeçalho com um Modelo Pydantic { #header-parameters-with-a-pydantic-model }
Declare os **parâmetros de cabeçalho** que você precisa em um **modelo do Pydantic**, e então declare o parâmetro como `Header`:
@@ -18,7 +18,7 @@ Declare os **parâmetros de cabeçalho** que você precisa em um **modelo do Pyd
O **FastAPI** irá **extrair** os dados de **cada campo** a partir dos **cabeçalhos** da requisição e te retornará o modelo do Pydantic que você definiu.
-### Checando a documentação
+## Checando a documentação { #check-the-docs }
Você pode ver os headers necessários na interface gráfica da documentação em `/docs`:
@@ -26,7 +26,7 @@ Você pode ver os headers necessários na interface gráfica da documentação e
-## Identificador de Licença
+## Identificador de Licença { #license-identifier }
Desde o OpenAPI 3.1.0 e FastAPI 0.99.0, você também pode definir o license_info com um identifier em vez de uma url.
Por exemplo:
-{* ../../docs_src/metadata/tutorial001_1.py hl[31] *}
+{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *}
-## Metadados para tags
+## Metadados para tags { #metadata-for-tags }
Você também pode adicionar metadados adicionais para as diferentes tags usadas para agrupar suas operações de rota com o parâmetro `openapi_tags`.
@@ -52,13 +52,13 @@ Cada dicionário pode conter:
* `description`: uma `str` com uma breve descrição da documentação externa.
* `url` (**obrigatório**): uma `str` com a URL da documentação externa.
-### Criar Metadados para tags
+### Criar Metadados para tags { #create-metadata-for-tags }
Vamos tentar isso em um exemplo com tags para `users` e `items`.
Crie metadados para suas tags e passe-os para o parâmetro `openapi_tags`:
-{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
+{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *}
Observe que você pode usar Markdown dentro das descrições. Por exemplo, "login" será exibido em negrito (**login**) e "fancy" será exibido em itálico (_fancy_).
@@ -68,31 +68,31 @@ Você não precisa adicionar metadados para todas as tags que você usa.
///
-### Use suas tags
+### Use suas tags { #use-your-tags }
Use o parâmetro `tags` com suas *operações de rota* (e `APIRouter`s) para atribuí-los a diferentes tags:
-{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
+{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *}
-/// info | Informação
+/// info | Informação
-Leia mais sobre tags em [Configuração de Operação de Caminho](path-operation-configuration.md#tags){.internal-link target=_blank}.
+Leia mais sobre tags em [Configuração de operação de rota](path-operation-configuration.md#tags){.internal-link target=_blank}.
///
-### Cheque os documentos
+### Cheque os documentos { #check-the-docs }
Agora, se você verificar a documentação, ela exibirá todos os metadados adicionais:
-### Ordem das tags
+### Ordem das tags { #order-of-tags }
A ordem de cada dicionário de metadados de tag também define a ordem exibida na interface de documentação.
Por exemplo, embora `users` apareça após `items` em ordem alfabética, ele é exibido antes deles, porque adicionamos seus metadados como o primeiro dicionário na lista.
-## URL da OpenAPI
+## URL da OpenAPI { #openapi-url }
Por padrão, o esquema OpenAPI é servido em `/openapi.json`.
@@ -100,11 +100,11 @@ Mas você pode configurá-lo com o parâmetro `openapi_url`.
Por exemplo, para defini-lo para ser servido em `/api/v1/openapi.json`:
-{* ../../docs_src/metadata/tutorial002.py hl[3] *}
+{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *}
Se você quiser desativar completamente o esquema OpenAPI, pode definir `openapi_url=None`, o que também desativará as interfaces de documentação que o utilizam.
-## URLs da Documentação
+## URLs da Documentação { #docs-urls }
Você pode configurar as duas interfaces de documentação incluídas:
@@ -117,4 +117,4 @@ Você pode configurar as duas interfaces de documentação incluídas:
Por exemplo, para definir o Swagger UI para ser servido em `/documentation` e desativar o ReDoc:
-{* ../../docs_src/metadata/tutorial003.py hl[3] *}
+{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *}
diff --git a/docs/pt/docs/tutorial/middleware.md b/docs/pt/docs/tutorial/middleware.md
index 0f5009b6db..b49c1eaa11 100644
--- a/docs/pt/docs/tutorial/middleware.md
+++ b/docs/pt/docs/tutorial/middleware.md
@@ -1,4 +1,4 @@
-# Middleware
+# Middleware { #middleware }
Você pode adicionar middleware à suas aplicações **FastAPI**.
@@ -11,15 +11,15 @@ Um "middleware" é uma função que manipula cada **requisição** antes de ser
* Ele pode fazer algo com essa **resposta** ou executar qualquer código necessário.
* Então ele retorna a **resposta**.
-/// note | Detalhes técnicos
+/// note | Detalhes Técnicos
Se você tiver dependências com `yield`, o código de saída será executado *depois* do middleware.
-Se houver alguma tarefa em segundo plano (documentada posteriormente), ela será executada *depois* de todo o middleware.
+Se houver alguma tarefa em segundo plano (abordada na seção [Tarefas em segundo plano](background-tasks.md){.internal-link target=_blank}, que você verá mais adiante), ela será executada *depois* de todo o middleware.
///
-## Criar um middleware
+## Criar um middleware { #create-a-middleware }
Para criar um middleware, use o decorador `@app.middleware("http")` logo acima de uma função.
@@ -31,17 +31,17 @@ A função middleware recebe:
* Então ela retorna a `response` gerada pela *operação de rota* correspondente.
* Você pode então modificar ainda mais o `response` antes de retorná-lo.
-{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
+{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *}
/// tip | Dica
-Tenha em mente que cabeçalhos proprietários personalizados podem ser adicionados usando o prefixo 'X-'.
+Tenha em mente que cabeçalhos proprietários personalizados podem ser adicionados usando o prefixo `X-`.
Mas se você tiver cabeçalhos personalizados desejando que um cliente em um navegador esteja apto a ver, você precisa adicioná-los às suas configurações CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) usando o parâmetro `expose_headers` documentado em Documentos CORS da Starlette.
///
-/// note | Detalhes técnicos
+/// note | Detalhes Técnicos
Você também pode usar `from starlette.requests import Request`.
@@ -49,7 +49,7 @@ Você também pode usar `from starlette.requests import Request`.
///
-### Antes e depois da `response`
+### Antes e depois da `response` { #before-and-after-the-response }
Você pode adicionar código para ser executado com a `request`, antes que qualquer *operação de rota* o receba.
@@ -57,9 +57,38 @@ E também depois que a `response` é gerada, antes de retorná-la.
Por exemplo, você pode adicionar um cabeçalho personalizado `X-Process-Time` contendo o tempo em segundos que levou para processar a solicitação e gerar uma resposta:
-{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
+{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *}
-## Outros middlewares
+/// tip | Dica
+
+Aqui usamos `time.perf_counter()` em vez de `time.time()` porque ele pode ser mais preciso para esses casos de uso. 🤓
+
+///
+
+## Ordem de execução de múltiplos middlewares { #multiple-middleware-execution-order }
+
+Quando você adiciona múltiplos middlewares usando o decorador `@app.middleware()` ou o método `app.add_middleware()`, cada novo middleware envolve a aplicação, formando uma pilha. O último middleware adicionado é o mais externo, e o primeiro é o mais interno.
+
+No caminho da requisição, o middleware mais externo roda primeiro.
+
+No caminho da resposta, ele roda por último.
+
+Por exemplo:
+
+```Python
+app.add_middleware(MiddlewareA)
+app.add_middleware(MiddlewareB)
+```
+
+Isso resulta na seguinte ordem de execução:
+
+* **Requisição**: MiddlewareB → MiddlewareA → rota
+
+* **Resposta**: rota → MiddlewareA → MiddlewareB
+
+Esse comportamento de empilhamento garante que os middlewares sejam executados em uma ordem previsível e controlável.
+
+## Outros middlewares { #other-middlewares }
Mais tarde, você pode ler mais sobre outros middlewares no [Guia do usuário avançado: Middleware avançado](../advanced/middleware.md){.internal-link target=_blank}.
diff --git a/docs/pt/docs/tutorial/path-operation-configuration.md b/docs/pt/docs/tutorial/path-operation-configuration.md
index f183c9d23b..84eebc1285 100644
--- a/docs/pt/docs/tutorial/path-operation-configuration.md
+++ b/docs/pt/docs/tutorial/path-operation-configuration.md
@@ -1,14 +1,14 @@
-# Configuração da Operação de Rota
+# Configuração da Operação de Rota { #path-operation-configuration }
Existem vários parâmetros que você pode passar para o seu *decorador de operação de rota* para configurá-lo.
-/// warning | Aviso
+/// warning | Atenção
Observe que esses parâmetros são passados diretamente para o *decorador de operação de rota*, não para a sua *função de operação de rota*.
///
-## Código de Status da Resposta
+## Código de Status da Resposta { #response-status-code }
Você pode definir o `status_code` (HTTP) para ser usado na resposta da sua *operação de rota*.
@@ -16,7 +16,7 @@ Você pode passar diretamente o código `int`, como `404`.
Mas se você não se lembrar o que cada código numérico significa, pode usar as constantes de atalho em `status`:
-{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *}
+{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *}
Esse código de status será usado na resposta e será adicionado ao esquema OpenAPI.
@@ -28,17 +28,17 @@ Você também poderia usar `from starlette import status`.
///
-## Tags
+## Tags { #tags }
Você pode adicionar tags para sua *operação de rota*, passe o parâmetro `tags` com uma `list` de `str` (comumente apenas um `str`):
-{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *}
+{* ../../docs_src/path_operation_configuration/tutorial002_py310.py hl[15,20,25] *}
Eles serão adicionados ao esquema OpenAPI e usados pelas interfaces de documentação automática:
-### Tags com Enums
+### Tags com Enums { #tags-with-enums }
Se você tem uma grande aplicação, você pode acabar acumulando **várias tags**, e você gostaria de ter certeza de que você sempre usa a **mesma tag** para *operações de rota* relacionadas.
@@ -46,32 +46,31 @@ Nestes casos, pode fazer sentido armazenar as tags em um `Enum`.
**FastAPI** suporta isso da mesma maneira que com strings simples:
-{* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *}
+{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *}
-## Resumo e descrição
+## Resumo e descrição { #summary-and-description }
Você pode adicionar um `summary` e uma `description`:
-{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *}
+{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *}
-## Descrição do docstring
+## Descrição do docstring { #description-from-docstring }
Como as descrições tendem a ser longas e cobrir várias linhas, você pode declarar a descrição da *operação de rota* na docstring da função e o **FastAPI** irá lê-la de lá.
Você pode escrever Markdown na docstring, ele será interpretado e exibido corretamente (levando em conta a indentação da docstring).
-{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *}
+{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *}
Ela será usada nas documentações interativas:
-
-## Descrição da resposta
+## Descrição da resposta { #response-description }
Você pode especificar a descrição da resposta com o parâmetro `response_description`:
-{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *}
+{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *}
/// info | Informação
@@ -79,7 +78,7 @@ Note que `response_description` se refere especificamente à resposta, a `descri
///
-/// check
+/// check | Verifique
OpenAPI especifica que cada *operação de rota* requer uma descrição de resposta.
@@ -89,11 +88,11 @@ Então, se você não fornecer uma, o **FastAPI** irá gerar automaticamente uma
-## Depreciar uma *operação de rota*
+## Descontinuar uma *operação de rota* { #deprecate-a-path-operation }
Se você precisar marcar uma *operação de rota* como descontinuada, mas sem removê-la, passe o parâmetro `deprecated`:
-{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *}
+{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *}
Ela será claramente marcada como descontinuada nas documentações interativas:
@@ -103,6 +102,6 @@ Verifique como *operações de rota* descontinuadas e não descontinuadas se par
-## Resumindo
+## Resumindo { #recap }
Você pode configurar e adicionar metadados para suas *operações de rota* facilmente passando parâmetros para os *decoradores de operação de rota*.
diff --git a/docs/pt/docs/tutorial/path-params-numeric-validations.md b/docs/pt/docs/tutorial/path-params-numeric-validations.md
index 3aea1188dd..9f12ba38fe 100644
--- a/docs/pt/docs/tutorial/path-params-numeric-validations.md
+++ b/docs/pt/docs/tutorial/path-params-numeric-validations.md
@@ -1,91 +1,128 @@
-# Parâmetros da Rota e Validações Numéricas
+# Parâmetros de path e validações numéricas { #path-parameters-and-numeric-validations }
-Do mesmo modo que você pode declarar mais validações e metadados para parâmetros de consulta com `Query`, você pode declarar os mesmos tipos de validações e metadados para parâmetros de rota com `Path`.
+Da mesma forma que você pode declarar mais validações e metadados para parâmetros de consulta com `Query`, você pode declarar o mesmo tipo de validações e metadados para parâmetros de path com `Path`.
-## Importe `Path`
+## Importe `Path` { #import-path }
-Primeiro, importe `Path` de `fastapi`:
+Primeiro, importe `Path` de `fastapi`, e importe `Annotated`:
-{* ../../docs_src/path_params_numeric_validations/tutorial001_py310.py hl[1] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *}
-## Declare metadados
+/// info | Informação
-Você pode declarar todos os parâmetros da mesma maneira que na `Query`.
+O FastAPI adicionou suporte a `Annotated` (e passou a recomendá-lo) na versão 0.95.0.
-Por exemplo para declarar um valor de metadado `title` para o parâmetro de rota `item_id` você pode digitar:
+Se você tiver uma versão mais antiga, verá erros ao tentar usar `Annotated`.
-{* ../../docs_src/path_params_numeric_validations/tutorial001_py310.py hl[8] *}
-
-/// note | Nota
-
-Um parâmetro de rota é sempre obrigatório, como se fizesse parte da rota.
-
-Então, você deve declará-lo com `...` para marcá-lo como obrigatório.
-
-Mesmo que você declare-o como `None` ou defina um valor padrão, isso não teria efeito algum, o parâmetro ainda seria obrigatório.
+Certifique-se de [Atualizar a versão do FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} para pelo menos 0.95.1 antes de usar `Annotated`.
///
-## Ordene os parâmetros de acordo com sua necessidade
+## Declare metadados { #declare-metadata }
-Suponha que você queira declarar o parâmetro de consulta `q` como uma `str` obrigatória.
+Você pode declarar todos os mesmos parâmetros que em `Query`.
-E você não precisa declarar mais nada em relação a este parâmetro, então você não precisa necessariamente usar `Query`.
+Por exemplo, para declarar um valor de metadado `title` para o parâmetro de path `item_id` você pode digitar:
-Mas você ainda precisa usar `Path` para o parâmetro de rota `item_id`.
+{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *}
-O Python irá acusar se você colocar um elemento com um valor padrão definido antes de outro que não tenha um valor padrão.
+/// note | Nota
-Mas você pode reordená-los, colocando primeiro o elemento sem o valor padrão (o parâmetro de consulta `q`).
+Um parâmetro de path é sempre obrigatório, pois precisa fazer parte do path. Mesmo que você o declare como `None` ou defina um valor padrão, isso não afetaria nada, ele ainda seria sempre obrigatório.
-Isso não faz diferença para o **FastAPI**. Ele vai detectar os parâmetros pelos seus nomes, tipos e definições padrão (`Query`, `Path`, etc), sem se importar com a ordem.
+///
+
+## Ordene os parâmetros de acordo com sua necessidade { #order-the-parameters-as-you-need }
+
+/// tip | Dica
+
+Isso provavelmente não é tão importante ou necessário se você usar `Annotated`.
+
+///
+
+Vamos supor que você queira declarar o parâmetro de consulta `q` como uma `str` obrigatória.
+
+E você não precisa declarar mais nada para esse parâmetro, então você realmente não precisa usar `Query`.
+
+Mas você ainda precisa usar `Path` para o parâmetro de path `item_id`. E você não quer usar `Annotated` por algum motivo.
+
+O Python vai reclamar se você colocar um valor com “padrão” antes de um valor que não tem “padrão”.
+
+Mas você pode reordená-los e colocar primeiro o valor sem padrão (o parâmetro de consulta `q`).
+
+Isso não faz diferença para o **FastAPI**. Ele vai detectar os parâmetros pelos seus nomes, tipos e declarações de padrão (`Query`, `Path`, etc.), sem se importar com a ordem.
Então, você pode declarar sua função assim:
-{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *}
-## Ordene os parâmetros de a acordo com sua necessidade, truques
+Mas tenha em mente que, se você usar `Annotated`, você não terá esse problema, não fará diferença, pois você não está usando valores padrão de parâmetros de função para `Query()` ou `Path()`.
-Se você quiser declarar o parâmetro de consulta `q` sem um `Query` nem um valor padrão, e o parâmetro de rota `item_id` usando `Path`, e definí-los em uma ordem diferente, Python tem um pequeno truque na sintaxe para isso.
+{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
+
+## Ordene os parâmetros de acordo com sua necessidade, truques { #order-the-parameters-as-you-need-tricks }
+
+/// tip | Dica
+
+Isso provavelmente não é tão importante ou necessário se você usar `Annotated`.
+
+///
+
+Aqui vai um pequeno truque que pode ser útil, mas você não vai precisar dele com frequência.
+
+Se você quiser:
+
+* declarar o parâmetro de consulta `q` sem um `Query` nem qualquer valor padrão
+* declarar o parâmetro de path `item_id` usando `Path`
+* tê-los em uma ordem diferente
+* não usar `Annotated`
+
+...o Python tem uma pequena sintaxe especial para isso.
Passe `*`, como o primeiro parâmetro da função.
-O Python não vai fazer nada com esse `*`, mas ele vai saber que a partir dali os parâmetros seguintes deverão ser chamados argumentos nomeados (pares chave-valor), também conhecidos como kwargs. Mesmo que eles não possuam um valor padrão.
+O Python não fará nada com esse `*`, mas saberá que todos os parâmetros seguintes devem ser chamados como argumentos nomeados (pares chave-valor), também conhecidos como kwargs. Mesmo que eles não tenham um valor padrão.
-{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
-## Validações numéricas: maior que ou igual
+### Melhor com `Annotated` { #better-with-annotated }
-Com `Query` e `Path` (e outras que você verá mais tarde) você pode declarar restrições numéricas.
+Tenha em mente que, se você usar `Annotated`, como você não está usando valores padrão de parâmetros de função, você não terá esse problema e provavelmente não precisará usar `*`.
-Aqui, com `ge=1`, `item_id` precisará ser um número inteiro maior que ("`g`reater than") ou igual ("`e`qual") a 1.
+{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
-{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *}
+## Validações numéricas: maior que ou igual { #number-validations-greater-than-or-equal }
-## Validações numéricas: maior que e menor que ou igual
+Com `Query` e `Path` (e outras que você verá depois) você pode declarar restrições numéricas.
-O mesmo se aplica para:
+Aqui, com `ge=1`, `item_id` precisará ser um número inteiro “`g`reater than or `e`qual” a `1`.
+
+{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
+
+## Validações numéricas: maior que e menor que ou igual { #number-validations-greater-than-and-less-than-or-equal }
+
+O mesmo se aplica a:
* `gt`: maior que (`g`reater `t`han)
* `le`: menor que ou igual (`l`ess than or `e`qual)
-{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
-## Validações numéricas: valores do tipo float, maior que e menor que
+## Validações numéricas: floats, maior que e menor que { #number-validations-floats-greater-than-and-less-than }
-Validações numéricas também funcionam para valores do tipo `float`.
+Validações numéricas também funcionam para valores `float`.
-Aqui é onde se torna importante a possibilidade de declarar gt e não apenas ge. Com isso você pode especificar, por exemplo, que um valor deve ser maior que `0`, ainda que seja menor que `1`.
+Aqui é onde se torna importante poder declarar gt e não apenas ge. Com isso você pode exigir, por exemplo, que um valor seja maior que `0`, mesmo que seja menor que `1`.
-Assim, `0.5` seria um valor válido. Mas `0.0` ou `0` não seria.
+Assim, `0.5` seria um valor válido. Mas `0.0` ou `0` não seriam.
-E o mesmo para lt.
+E o mesmo para lt.
-{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *}
-## Recapitulando
+## Recapitulando { #recap }
-Com `Query`, `Path` (e outras que você ainda não viu) você pode declarar metadados e validações de texto do mesmo modo que com [Parâmetros de consulta e validações de texto](query-params-str-validations.md){.internal-link target=_blank}.
+Com `Query`, `Path` (e outras que você ainda não viu) você pode declarar metadados e validações de string do mesmo modo que em [Parâmetros de consulta e validações de string](query-params-str-validations.md){.internal-link target=_blank}.
E você também pode declarar validações numéricas:
@@ -96,7 +133,7 @@ E você também pode declarar validações numéricas:
/// info | Informação
-`Query`, `Path` e outras classes que você verá a frente são subclasses de uma classe comum `Param`.
+`Query`, `Path` e outras classes que você verá depois são subclasses de uma classe comum `Param`.
Todas elas compartilham os mesmos parâmetros para validação adicional e metadados que você viu.
@@ -106,12 +143,12 @@ Todas elas compartilham os mesmos parâmetros para validação adicional e metad
Quando você importa `Query`, `Path` e outras de `fastapi`, elas são na verdade funções.
-Que quando chamadas, retornam instâncias de classes de mesmo nome.
+Que, quando chamadas, retornam instâncias de classes de mesmo nome.
Então, você importa `Query`, que é uma função. E quando você a chama, ela retorna uma instância de uma classe também chamada `Query`.
-Estas funções são assim (ao invés de apenas usar as classes diretamente) para que seu editor não acuse erros sobre seus tipos.
+Essas funções existem (em vez de usar diretamente as classes) para que seu editor não marque erros sobre seus tipos.
-Dessa maneira você pode user seu editor e ferramentas de desenvolvimento sem precisar adicionar configurações customizadas para ignorar estes erros.
+Dessa forma, você pode usar seu editor e ferramentas de codificação normais sem precisar adicionar configurações personalizadas para desconsiderar esses erros.
///
diff --git a/docs/pt/docs/tutorial/path-params.md b/docs/pt/docs/tutorial/path-params.md
index ecf77d676d..1f47ca6e59 100644
--- a/docs/pt/docs/tutorial/path-params.md
+++ b/docs/pt/docs/tutorial/path-params.md
@@ -1,207 +1,184 @@
-# Parâmetros da rota da URL
+# Parâmetros de path { #path-parameters }
-Você pode declarar os "parâmetros" ou "variáveis" com a mesma sintaxe utilizada pelo formato de strings do Python:
+Você pode declarar "parâmetros" ou "variáveis" de path com a mesma sintaxe usada por strings de formatação do Python:
-{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
+{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
-O valor do parâmetro que foi passado à `item_id` será passado para a sua função como o argumento `item_id`.
+O valor do parâmetro de path `item_id` será passado para a sua função como o argumento `item_id`.
-Então, se você rodar este exemplo e for até http://127.0.0.1:8000/items/foo, você verá a seguinte resposta:
+Então, se você executar este exemplo e acessar http://127.0.0.1:8000/items/foo, você verá uma resposta:
```JSON
{"item_id":"foo"}
```
-## Parâmetros da rota com tipos
+## Parâmetros de path com tipos { #path-parameters-with-types }
-Você pode declarar o tipo de um parâmetro na função usando as anotações padrões do Python:
+Você pode declarar o tipo de um parâmetro de path na função, usando as anotações de tipo padrão do Python:
-{* ../../docs_src/path_params/tutorial002.py hl[7] *}
+{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *}
-Nesse caso, `item_id` está sendo declarado como um `int`.
+Neste caso, `item_id` é declarado como um `int`.
/// check | Verifique
-
-
-
+Isso fornecerá suporte do editor dentro da sua função, com verificações de erros, preenchimento automático, etc.
///
- Isso vai dar à você suporte do seu editor dentro das funções, com verificações de erros, autocompletar, etc.
+## Dados conversão { #data-conversion }
-## Conversão de dados
-
-Se você rodar esse exemplo e abrir o seu navegador em http://127.0.0.1:8000/items/3, você verá a seguinte resposta:
+Se você executar este exemplo e abrir seu navegador em http://127.0.0.1:8000/items/3, você verá uma resposta:
```JSON
{"item_id":3}
```
/// check | Verifique
+Perceba que o valor que sua função recebeu (e retornou) é `3`, como um `int` do Python, não uma string `"3"`.
-
-
+Então, com essa declaração de tipo, o **FastAPI** fornece "parsing" automático do request.
///
- Observe que o valor recebido pela função (e também retornado por ela) é `3`, como um Python `int`, não como uma string `"3"`.
+## Validação de dados { #data-validation }
- Então, com essa declaração de tipo, o **FastAPI** dá pra você um "parsing" automático no request .
-
-## Validação de dados
-
-Mas se você abrir o seu navegador em http://127.0.0.1:8000/items/foo, você verá um belo erro HTTP:
+Mas se você for no navegador para http://127.0.0.1:8000/items/foo, verá um bom erro HTTP:
```JSON
{
- "detail": [
- {
- "loc": [
- "path",
- "item_id"
- ],
- "msg": "value is not a valid integer",
- "type": "type_error.integer"
- }
- ]
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": [
+ "path",
+ "item_id"
+ ],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo"
+ }
+ ]
}
```
-devido ao parâmetro da rota `item_id` ter um valor `"foo"`, que não é um `int`.
+porque o parâmetro de path `item_id` tinha o valor `"foo"`, que não é um `int`.
-O mesmo erro apareceria se você tivesse fornecido um `float` ao invés de um `int`, como em: http://127.0.0.1:8000/items/4.2
+O mesmo erro apareceria se você fornecesse um `float` em vez de um `int`, como em: http://127.0.0.1:8000/items/4.2
/// check | Verifique
+Então, com a mesma declaração de tipo do Python, o **FastAPI** fornece validação de dados.
+Observe que o erro também declara claramente exatamente o ponto onde a validação não passou.
-
+Isso é incrivelmente útil ao desenvolver e depurar código que interage com sua API.
///
- Então, com a mesma declaração de tipo do Python, o **FastAPI** dá pra você validação de dados.
+## Documentação { #documentation }
- Observe que o erro também mostra claramente o ponto exato onde a validação não passou.
-
- Isso é incrivelmente útil enquanto se desenvolve e debuga o código que interage com a sua API.
-
-## Documentação
-
-Quando você abrir o seu navegador em http://127.0.0.1:8000/docs, você verá de forma automática e interativa a documentação da API como:
+E quando você abrir seu navegador em http://127.0.0.1:8000/docs, você verá documentação automática, interativa, da API como:
/// check | Verifique
+Novamente, apenas com a mesma declaração de tipo do Python, o **FastAPI** fornece documentação automática e interativa (integrando o Swagger UI).
-
-
+Observe que o parâmetro de path está declarado como um inteiro.
///
- Novamente, apenas com a mesma declaração de tipo do Python, o **FastAPI** te dá de forma automática e interativa a documentação (integrada com o Swagger UI).
+## Benefícios baseados em padrões, documentação alternativa { #standards-based-benefits-alternative-documentation }
- Veja que o parâmetro de rota está declarado como sendo um inteiro (int).
+E como o schema gerado é do padrão OpenAPI, existem muitas ferramentas compatíveis.
-## Beneficios baseados em padrões, documentação alternativa
-
-Devido ao schema gerado ser o padrão do OpenAPI, existem muitas ferramentas compatíveis.
-
-Por esse motivo, o próprio **FastAPI** fornece uma API alternativa para documentação (utilizando ReDoc), que você pode acessar em http://127.0.0.1:8000/redoc:
+Por causa disso, o próprio **FastAPI** fornece uma documentação alternativa da API (usando ReDoc), que você pode acessar em http://127.0.0.1:8000/redoc:
Da mesma forma, existem muitas ferramentas compatíveis. Incluindo ferramentas de geração de código para muitas linguagens.
-## Pydantic
+## Pydantic { #pydantic }
-Toda a validação de dados é feita por baixo dos panos pelo Pydantic, então você tem todos os benefícios disso. E assim você sabe que está em boas mãos.
+Toda a validação de dados é realizada nos bastidores pelo Pydantic, então você recebe todos os benefícios disso. E você sabe que está em boas mãos.
-Você pode usar as mesmas declarações de tipo com `str`, `float`, `bool` e muitos outros tipos complexos de dados.
+Você pode usar as mesmas declarações de tipo com `str`, `float`, `bool` e muitos outros tipos de dados complexos.
-Vamos explorar muitos destes tipos nos próximos capítulos do tutorial.
+Vários deles são explorados nos próximos capítulos do tutorial.
-## A ordem importa
+## A ordem importa { #order-matters }
-Quando você cria operações de rota, você pode se deparar com situações onde você pode ter uma rota fixa.
+Ao criar *operações de rota*, você pode encontrar situações em que tem um path fixo.
-Algo como `/users/me` por exemplo, digamos que essa rota seja utilizada para pegar dados sobre o usuário atual.
+Como `/users/me`, digamos que seja para obter dados sobre o usuário atual.
-E então você pode ter também uma rota `/users/{user_id}` para pegar dados sobre um usuário específico associado a um ID de usuário.
+E então você também pode ter um path `/users/{user_id}` para obter dados sobre um usuário específico por algum ID de usuário.
-Porque as operações de rota são avaliadas em ordem, você precisa ter certeza que a rota para `/users/me` está sendo declarado antes da rota `/users/{user_id}`:
+Como as *operações de rota* são avaliadas em ordem, você precisa garantir que o path para `/users/me` seja declarado antes do de `/users/{user_id}`:
-{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
+{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *}
-Caso contrário, a rota para `/users/{user_id}` coincidiria também para `/users/me`, "pensando" que estaria recebendo o parâmetro `user_id` com o valor de `"me"`.
+Caso contrário, o path para `/users/{user_id}` também corresponderia a `/users/me`, "achando" que está recebendo um parâmetro `user_id` com o valor `"me"`.
-## Valores predefinidos
+Da mesma forma, você não pode redefinir uma operação de rota:
-Se você tem uma operação de rota que recebe um parâmetro da rota, mas que você queira que esses valores possíveis do parâmetro da rota sejam predefinidos, você pode usar `Enum` padrão do Python.
+{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *}
-### Criando uma classe `Enum`
+A primeira sempre será usada, já que o path corresponde primeiro.
-Importe `Enum` e crie uma sub-classe que herde de `str` e de `Enum`.
+## Valores predefinidos { #predefined-values }
-Por herdar de `str` a documentação da API vai ser capaz de saber que os valores devem ser do tipo `string` e assim ser capaz de mostrar eles corretamente.
+Se você tem uma *operação de rota* que recebe um *parâmetro de path*, mas quer que os valores válidos possíveis do *parâmetro de path* sejam predefinidos, você pode usar um `Enum` padrão do Python.
-Assim, crie atributos de classe com valores fixos, que serão os valores válidos disponíveis.
+### Crie uma classe `Enum` { #create-an-enum-class }
-{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
+Importe `Enum` e crie uma subclasse que herde de `str` e de `Enum`.
-/// info | informação
+Ao herdar de `str`, a documentação da API saberá que os valores devem ser do tipo `string` e poderá renderizá-los corretamente.
-Enumerations (ou enums) estão disponíveis no Python desde a versão 3.4.
+Em seguida, crie atributos de classe com valores fixos, que serão os valores válidos disponíveis:
-///
+{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *}
/// tip | Dica
-
-
-
+Se você está se perguntando, "AlexNet", "ResNet" e "LeNet" são apenas nomes de modelos de Aprendizado de Máquina.
///
- Se você está se perguntando, "AlexNet", "ResNet", e "LeNet" são apenas nomes de modelos de Machine Learning (aprendizado de máquina).
+### Declare um parâmetro de path { #declare-a-path-parameter }
-### Declare um *parâmetro de rota*
+Em seguida, crie um *parâmetro de path* com anotação de tipo usando a classe enum que você criou (`ModelName`):
-Logo, crie um *parâmetro de rota* com anotações de tipo usando a classe enum que você criou (`ModelName`):
+{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
-{* ../../docs_src/path_params/tutorial005.py hl[16] *}
+### Verifique a documentação { #check-the-docs }
-### Revise a documentação
-
-Visto que os valores disponíveis para o parâmetro da rota estão predefinidos, a documentação interativa pode mostrar esses valores de uma forma bem legal:
+Como os valores disponíveis para o *parâmetro de path* são predefinidos, a documentação interativa pode mostrá-los de forma agradável:
-### Trabalhando com os *enumeration* do Python
+### Trabalhando com *enumerações* do Python { #working-with-python-enumerations }
-O valor do *parâmetro da rota* será um *membro de enumeration*.
+O valor do *parâmetro de path* será um *membro de enumeração*.
-#### Compare *membros de enumeration*
+#### Compare membros de enumeração { #compare-enumeration-members }
-Você pode comparar eles com o *membro de enumeration* no enum `ModelName` que você criou:
+Você pode compará-lo com o *membro de enumeração* no seu enum `ModelName` criado:
-{* ../../docs_src/path_params/tutorial005.py hl[17] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *}
-#### Obtenha o *valor de enumerate*
+#### Obtenha o valor da enumeração { #get-the-enumeration-value }
-Você pode ter o valor exato de enumerate (um `str` nesse caso) usando `model_name.value`, ou em geral, `your_enum_member.value`:
+Você pode obter o valor real (um `str` neste caso) usando `model_name.value`, ou, em geral, `your_enum_member.value`:
-{* ../../docs_src/path_params/tutorial005.py hl[20] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *}
/// tip | Dica
-
-
-
+Você também pode acessar o valor `"lenet"` com `ModelName.lenet.value`.
///
- Você também poderia acessar o valor `"lenet"` com `ModelName.lenet.value`
+#### Retorne membros de enumeração { #return-enumeration-members }
-#### Retorne *membros de enumeration*
+Você pode retornar *membros de enum* da sua *operação de rota*, até mesmo aninhados em um corpo JSON (por exemplo, um `dict`).
-Você pode retornar *membros de enum* da sua *rota de operação*, em um corpo JSON aninhado (por exemplo um `dict`).
+Eles serão convertidos para seus valores correspondentes (strings neste caso) antes de serem retornados ao cliente:
-Eles serão convertidos para o seus valores correspondentes (strings nesse caso) antes de serem retornados ao cliente:
+{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *}
-{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
-
-No seu cliente você vai obter uma resposta JSON como:
+No seu cliente, você receberá uma resposta JSON como:
```JSON
{
@@ -210,56 +187,51 @@ No seu cliente você vai obter uma resposta JSON como:
}
```
-## Parâmetros de rota que contém caminhos
+## Parâmetros de path que contêm paths { #path-parameters-containing-paths }
-Digamos que você tenha uma *operação de rota* com uma rota `/files/{file_path}`.
+Digamos que você tenha uma *operação de rota* com um path `/files/{file_path}`.
-Mas você precisa que o próprio `file_path` contenha uma *rota*, como `home/johndoe/myfile.txt`.
+Mas você precisa que o próprio `file_path` contenha um *path*, como `home/johndoe/myfile.txt`.
-Então, a URL para este arquivo deveria ser algo como: `/files/home/johndoe/myfile.txt`.
+Então, a URL para esse arquivo seria algo como: `/files/home/johndoe/myfile.txt`.
-### Suporte do OpenAPI
+### Suporte do OpenAPI { #openapi-support }
-O OpenAPI não suporta uma maneira de declarar um *parâmetro de rota* que contenha uma *rota* dentro, dado que isso poderia levar a cenários que são difíceis de testar e definir.
+O OpenAPI não oferece suporte a uma maneira de declarar um *parâmetro de path* que contenha um *path* dentro, pois isso poderia levar a cenários difíceis de testar e definir.
-No entanto, você pode fazer isso no **FastAPI**, usando uma das ferramentas internas do Starlette.
+Ainda assim, você pode fazer isso no **FastAPI**, usando uma das ferramentas internas do Starlette.
-A documentação continuaria funcionando, ainda que não adicionaria nenhuma informação dizendo que o parâmetro deveria conter uma rota.
+E a documentação continuará funcionando, embora não adicione nenhuma informação dizendo que o parâmetro deve conter um path.
-### Conversor de rota
+### Conversor de path { #path-convertor }
-Usando uma opção direta do Starlette você pode declarar um *parâmetro de rota* contendo uma *rota* usando uma URL como:
+Usando uma opção diretamente do Starlette você pode declarar um *parâmetro de path* contendo um *path* usando uma URL como:
```
/files/{file_path:path}
```
-Nesse caso, o nome do parâmetro é `file_path`, e a última parte, `:path`, diz que o parâmetro deveria coincidir com qualquer *rota*.
+Nesse caso, o nome do parâmetro é `file_path`, e a última parte, `:path`, diz que o parâmetro deve corresponder a qualquer *path*.
-Então, você poderia usar ele com:
+Então, você pode usá-lo com:
-{* ../../docs_src/path_params/tutorial004.py hl[6] *}
+{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *}
/// tip | Dica
+Você pode precisar que o parâmetro contenha `/home/johndoe/myfile.txt`, com uma barra inicial (`/`).
-
-
+Nesse caso, a URL seria: `/files//home/johndoe/myfile.txt`, com uma barra dupla (`//`) entre `files` e `home`.
///
- Você poderia precisar que o parâmetro contivesse `/home/johndoe/myfile.txt`, com uma barra no inicio (`/`).
+## Recapitulação { #recap }
- Neste caso, a URL deveria ser: `/files//home/johndoe/myfile.txt`, com barra dupla (`//`) entre `files` e `home`.
+Com o **FastAPI**, ao usar declarações de tipo do Python curtas, intuitivas e padrão, você obtém:
+- Suporte no editor: verificações de erro, autocompletar, etc.
+- "Parsing" de dados
+- Validação de dados
+- Anotação da API e documentação automática
-## Recapitulando
+E você só precisa declará-los uma vez.
-Com o **FastAPI**, usando as declarações de tipo do Python, você obtém:
-
-* Suporte no editor: verificação de erros, e opção de autocompletar, etc.
-* "Parsing" de dados
-* Validação de dados
-* Anotação da API e documentação automática
-
-Você apenas tem que declará-los uma vez.
-
-Essa é provavelmente a vantagem mais visível do **FastAPI** se comparado com frameworks alternativos (além do desempenho puro).
+Essa é provavelmente a principal vantagem visível do **FastAPI** em comparação com frameworks alternativos (além do desempenho bruto).
diff --git a/docs/pt/docs/tutorial/query-param-models.md b/docs/pt/docs/tutorial/query-param-models.md
index 01a6e462f1..42d2604cd4 100644
--- a/docs/pt/docs/tutorial/query-param-models.md
+++ b/docs/pt/docs/tutorial/query-param-models.md
@@ -1,4 +1,4 @@
-# Modelos de Parâmetros de Consulta
+# Modelos de Parâmetros de Consulta { #query-parameter-models }
Se você possui um grupo de **parâmetros de consultas** que são relacionados, você pode criar um **modelo Pydantic** para declará-los.
@@ -10,7 +10,7 @@ Isso é suportado desde o FastAPI versão `0.115.0`. 🤓
///
-## Parâmetros de Consulta com um Modelo Pydantic
+## Parâmetros de Consulta com um Modelo Pydantic { #query-parameters-with-a-pydantic-model }
Declare os **parâmetros de consulta** que você precisa em um **modelo Pydantic**, e então declare o parâmetro como `Query`:
@@ -19,7 +19,7 @@ Declare os **parâmetros de consulta** que você precisa em um **modelo Pydantic
O **FastAPI** **extrairá** os dados para **cada campo** dos **parâmetros de consulta** presentes na requisição, e fornecerá o modelo Pydantic que você definiu.
-## Verifique os Documentos
+## Verifique os Documentos { #check-the-docs }
Você pode ver os parâmetros de consulta nos documentos de IU em `/docs`:
@@ -27,7 +27,7 @@ Você pode ver os parâmetros de consulta nos documentos de IU em `/docs`:
-### Lista de parâmetros de consulta / múltiplos valores por padrão
+### Lista de parâmetros de consulta / múltiplos valores com valores padrão { #query-parameter-list-multiple-values-with-defaults }
-E você também pode definir uma lista (`list`) de valores padrão caso nenhum seja informado:
+Você também pode definir uma `list` de valores padrão caso nenhum seja fornecido:
-{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *}
+{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
Se você for até:
@@ -193,7 +315,7 @@ Se você for até:
http://localhost:8000/items/
```
-O valor padrão de `q` será: `["foo", "bar"]` e sua resposta será:
+o valor padrão de `q` será: `["foo", "bar"]` e sua resposta será:
```JSON
{
@@ -204,93 +326,163 @@ O valor padrão de `q` será: `["foo", "bar"]` e sua resposta será:
}
```
-#### Usando `list`
+#### Usando apenas `list` { #using-just-list }
-Você também pode utilizar o tipo `list` diretamente em vez de `List[str]`:
+Você também pode usar `list` diretamente em vez de `list[str]`:
-{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *}
+{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
-/// note | Observação
+/// note | Nota
-Tenha em mente que neste caso, o FastAPI não irá validar os conteúdos da lista.
+Tenha em mente que, neste caso, o FastAPI não verificará o conteúdo da lista.
-Por exemplo, um `List[int]` iria validar (e documentar) que os contéudos da lista são números inteiros. Mas apenas `list` não.
+Por exemplo, `list[int]` verificaria (e documentaria) que os conteúdos da lista são inteiros. Mas `list` sozinho não.
///
-## Declarando mais metadados
+## Declare mais metadados { #declare-more-metadata }
Você pode adicionar mais informações sobre o parâmetro.
-Essa informações serão inclusas no esquema do OpenAPI e utilizado pela documentação interativa e ferramentas externas.
+Essas informações serão incluídas no OpenAPI gerado e usadas pelas interfaces de documentação e por ferramentas externas.
-/// note | Observação
+/// note | Nota
-Tenha em mente que cada ferramenta oferece diferentes níveis de suporte ao OpenAPI.
+Tenha em mente que ferramentas diferentes podem ter níveis diferentes de suporte ao OpenAPI.
-Algumas delas não exibem todas as informações extras que declaramos, ainda que na maioria dos casos, esses recursos estão planejados para desenvolvimento.
+Algumas delas podem ainda não mostrar todas as informações extras declaradas, embora na maioria dos casos o recurso ausente já esteja planejado para desenvolvimento.
///
Você pode adicionar um `title`:
-{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *}
+{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *}
E uma `description`:
-{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *}
+{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
-## Apelidos (alias) de parâmetros
+## Parâmetros com alias { #alias-parameters }
-Imagine que você queira que um parâmetro tenha o nome `item-query`.
+Imagine que você queira que o parâmetro seja `item-query`.
-Desta maneira:
+Assim:
```
http://127.0.0.1:8000/items/?item-query=foobaritems
```
-Mas o nome `item-query` não é um nome de váriavel válido no Python.
+Mas `item-query` não é um nome de variável Python válido.
-O que mais se aproxima é `item_query`.
+O mais próximo seria `item_query`.
-Mas ainda você precisa que o nome seja exatamente `item-query`...
+Mas você ainda precisa que seja exatamente `item-query`...
-Então você pode declarar um `alias`, e esse apelido (alias) que será utilizado para encontrar o valor do parâmetro:
+Então você pode declarar um `alias`, e esse alias será usado para encontrar o valor do parâmetro:
-{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *}
+{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
-## Parâmetros descontinuados
+## Descontinuando parâmetros { #deprecating-parameters }
-Agora vamos dizer que você não queria mais utilizar um parâmetro.
+Agora digamos que você não gosta mais desse parâmetro.
-Você tem que deixá-lo ativo por um tempo, já que existem clientes o utilizando. Mas você quer que a documentação deixe claro que este parâmetro será descontinuado.
+Você tem que deixá-lo por um tempo, pois há clientes usando-o, mas quer que a documentação mostre claramente que ele está descontinuado.
-Então você passa o parâmetro `deprecated=True` para `Query`:
+Então passe o parâmetro `deprecated=True` para `Query`:
-{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *}
+{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *}
-Na documentação aparecerá assim:
+A documentação vai mostrar assim:
-## Recapitulando
+## Excluir parâmetros do OpenAPI { #exclude-parameters-from-openapi }
-Você pode adicionar validações e metadados adicionais aos seus parâmetros.
+Para excluir um parâmetro de consulta do OpenAPI gerado (e portanto, dos sistemas de documentação automáticos), defina o parâmetro `include_in_schema` de `Query` como `False`:
-Validações genéricas e metadados:
+{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *}
+
+## Validação personalizada { #custom-validation }
+
+Podem existir casos em que você precise fazer alguma **validação personalizada** que não pode ser feita com os parâmetros mostrados acima.
+
+Nesses casos, você pode usar uma **função validadora personalizada** que é aplicada após a validação normal (por exemplo, depois de validar que o valor é uma `str`).
+
+Você pode fazer isso usando o `AfterValidator` do Pydantic dentro de `Annotated`.
+
+/// tip | Dica
+
+O Pydantic também tem `BeforeValidator` e outros. 🤓
+
+///
+
+Por exemplo, este validador personalizado verifica se o ID do item começa com `isbn-` para um número de livro ISBN ou com `imdb-` para um ID de URL de filme IMDB:
+
+{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
+
+/// info | Informação
+
+Isso está disponível com a versão 2 do Pydantic ou superior. 😎
+
+///
+
+/// tip | Dica
+
+Se você precisar fazer qualquer tipo de validação que exija comunicação com algum **componente externo**, como um banco de dados ou outra API, você deve usar **Dependências do FastAPI** em vez disso; você aprenderá sobre elas mais adiante.
+
+Esses validadores personalizados são para coisas que podem ser verificadas **apenas** com os **mesmos dados** fornecidos na requisição.
+
+///
+
+### Entenda esse código { #understand-that-code }
+
+O ponto importante é apenas usar **`AfterValidator` com uma função dentro de `Annotated`**. Sinta-se à vontade para pular esta parte. 🤸
+
+---
+
+Mas se você está curioso sobre este exemplo específico e ainda entretido, aqui vão alguns detalhes extras.
+
+#### String com `value.startswith()` { #string-with-value-startswith }
+
+Percebeu? Uma string usando `value.startswith()` pode receber uma tupla, e verificará cada valor na tupla:
+
+{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *}
+
+#### Um item aleatório { #a-random-item }
+
+Com `data.items()` obtemos um objeto iterável com tuplas contendo a chave e o valor de cada item do dicionário.
+
+Convertimos esse objeto iterável em uma `list` adequada com `list(data.items())`.
+
+Em seguida, com `random.choice()` podemos obter um **valor aleatório** da lista, então obtemos uma tupla com `(id, name)`. Será algo como `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`.
+
+Depois **atribuímos esses dois valores** da tupla às variáveis `id` e `name`.
+
+Assim, se o usuário não fornecer um ID de item, ele ainda receberá uma sugestão aleatória.
+
+...fazemos tudo isso em **uma única linha simples**. 🤯 Você não ama Python? 🐍
+
+{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *}
+
+## Recapitulando { #recap }
+
+Você pode declarar validações adicionais e metadados para seus parâmetros.
+
+Validações e metadados genéricos:
* `alias`
* `title`
* `description`
* `deprecated`
-Validações específicas para textos:
+Validações específicas para strings:
* `min_length`
* `max_length`
-* `regex`
+* `pattern`
-Nesses exemplos você viu como declarar validações em valores do tipo `str`.
+Validações personalizadas usando `AfterValidator`.
-Leia os próximos capítulos para ver como declarar validação de outros tipos, como números.
+Nestes exemplos você viu como declarar validações para valores `str`.
+
+Veja os próximos capítulos para aprender a declarar validações para outros tipos, como números.
diff --git a/docs/pt/docs/tutorial/query-params.md b/docs/pt/docs/tutorial/query-params.md
index 8199de5af9..8826602a25 100644
--- a/docs/pt/docs/tutorial/query-params.md
+++ b/docs/pt/docs/tutorial/query-params.md
@@ -1,8 +1,8 @@
-# Parâmetros de Consulta
+# Parâmetros de Consulta { #query-parameters }
Quando você declara outros parâmetros na função que não fazem parte dos parâmetros da rota, esses parâmetros são automaticamente interpretados como parâmetros de "consulta".
-{* ../../docs_src/query_params/tutorial001.py hl[9] *}
+{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *}
A consulta é o conjunto de pares chave-valor que vai depois de `?` na URL, separado pelo caractere `&`.
@@ -28,7 +28,7 @@ Todo o processo que era aplicado para parâmetros de rota também é aplicado pa
* Validação de dados
* Documentação automática
-## Valores padrão
+## Valores padrão { #defaults }
Como os parâmetros de consulta não são uma parte fixa da rota, eles podem ser opcionais e podem ter valores padrão.
@@ -57,7 +57,7 @@ Os valores dos parâmetros na sua função serão:
* `skip=20`: Por que você definiu isso na URL
* `limit=10`: Por que esse era o valor padrão
-## Parâmetros opcionais
+## Parâmetros opcionais { #optional-parameters }
Da mesma forma, você pode declarar parâmetros de consulta opcionais, definindo o valor padrão para `None`:
@@ -65,13 +65,13 @@ Da mesma forma, você pode declarar parâmetros de consulta opcionais, definindo
Nesse caso, o parâmetro da função `q` será opcional, e `None` será o padrão.
-/// check | Verificar
+/// check | Verifique
Você também pode notar que o **FastAPI** é esperto o suficiente para perceber que o parâmetro da rota `item_id` é um parâmetro da rota, e `q` não é, portanto, `q` é o parâmetro de consulta.
///
-## Conversão dos tipos de parâmetros de consulta
+## Conversão dos tipos de parâmetros de consulta { #query-parameter-type-conversion }
Você também pode declarar tipos `bool`, e eles serão convertidos:
@@ -109,7 +109,7 @@ http://127.0.0.1:8000/items/foo?short=yes
ou qualquer outra variação (tudo em maiúscula, primeira letra em maiúscula, etc), a sua função vai ver o parâmetro `short` com um valor `bool` de `True`. Caso contrário `False`.
-## Múltiplos parâmetros de rota e consulta
+## Múltiplos parâmetros de rota e consulta { #multiple-path-and-query-parameters }
Você pode declarar múltiplos parâmetros de rota e parâmetros de consulta ao mesmo tempo, o **FastAPI** vai saber o quê é o quê.
@@ -119,7 +119,7 @@ Eles serão detectados pelo nome:
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
-## Parâmetros de consulta obrigatórios
+## Parâmetros de consulta obrigatórios { #required-query-parameters }
Quando você declara um valor padrão para parâmetros que não são de rota (até agora, nós vimos apenas parâmetros de consulta), então eles não são obrigatórios.
@@ -127,7 +127,7 @@ Caso você não queira adicionar um valor específico mas queira apenas torná-l
Porém, quando você quiser fazer com que o parâmetro de consulta seja obrigatório, você pode simplesmente não declarar nenhum valor como padrão.
-{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
+{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
Aqui o parâmetro de consulta `needy` é um valor obrigatório, do tipo `str`.
@@ -141,16 +141,17 @@ http://127.0.0.1:8000/items/foo-item
```JSON
{
- "detail": [
- {
- "loc": [
- "query",
- "needy"
- ],
- "msg": "field required",
- "type": "value_error.missing"
- }
- ]
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "query",
+ "needy"
+ ],
+ "msg": "Field required",
+ "input": null
+ }
+ ]
}
```
@@ -181,6 +182,6 @@ Nesse caso, existem 3 parâmetros de consulta:
/// tip | Dica
-Você também poderia usar `Enum` da mesma forma que com [Path Parameters](path-params.md#valores-predefinidos){.internal-link target=_blank}.
+Você também poderia usar `Enum` da mesma forma que com [Path Parameters](path-params.md#predefined-values){.internal-link target=_blank}.
///
diff --git a/docs/pt/docs/tutorial/request-files.md b/docs/pt/docs/tutorial/request-files.md
index c22c1c5133..5d0891163a 100644
--- a/docs/pt/docs/tutorial/request-files.md
+++ b/docs/pt/docs/tutorial/request-files.md
@@ -1,4 +1,4 @@
-# Arquivos de Requisição
+# Arquivos de Requisição { #request-files }
Você pode definir arquivos para serem enviados pelo cliente usando `File`.
@@ -16,13 +16,13 @@ Isso é necessário, visto que os arquivos enviados são enviados como "dados de
///
-## Importe `File`
+## Importe `File` { #import-file }
Importe `File` e `UploadFile` de `fastapi`:
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
-## Definir Parâmetros `File`
+## Definir Parâmetros `File` { #define-file-parameters }
Crie parâmetros de arquivo da mesma forma que você faria para `Body` ou `Form`:
@@ -50,7 +50,7 @@ Mantenha em mente que isso significa que todo o conteúdo será armazenado na me
Mas há muitos casos em que você pode se beneficiar do uso de `UploadFile`.
-## Parâmetros de Arquivo com `UploadFile`
+## Parâmetros de Arquivo com `UploadFile` { #file-parameters-with-uploadfile }
Defina um parâmetro de arquivo com um tipo de `UploadFile`:
@@ -66,12 +66,12 @@ Utilizar `UploadFile` tem várias vantagens sobre `bytes`:
* Ele tem uma file-like interface `assíncrona`.
* Ele expõe um objeto python `SpooledTemporaryFile` que você pode passar diretamente para outras bibliotecas que esperam um objeto semelhante a um arquivo("file-like").
-### `UploadFile`
+### `UploadFile` { #uploadfile }
`UploadFile` tem os seguintes atributos:
* `filename`: Uma `str` com o nome do arquivo original que foi enviado (por exemplo, `myimage.jpg`).
-* `content_type`: Uma `str` com o tipo de conteúdo (tipo MIME / tipo de mídia) (por exemplo, `image/jpeg`).
+* `content_type`: Uma `str` com o tipo de conteúdo (MIME type / media type) (por exemplo, `image/jpeg`).
* `file`: Um `SpooledTemporaryFile` (um file-like objeto). Este é o objeto de arquivo Python que você pode passar diretamente para outras funções ou bibliotecas que esperam um objeto semelhante a um arquivo("file-like").
`UploadFile` tem os seguintes métodos `assíncronos`. Todos eles chamam os métodos de arquivo correspondentes por baixo dos panos (usando o `SpooledTemporaryFile` interno).
@@ -105,11 +105,11 @@ Quando você usa os métodos `async`, o **FastAPI** executa os métodos de arqui
/// note | Detalhes Técnicos do Starlette
-O `UploadFile` do ***FastAPI** herda diretamente do `UploadFile` do **Starlette** , mas adiciona algumas partes necessárias para torná-lo compatível com o **Pydantic** e as outras partes do FastAPI.
+O `UploadFile` do **FastAPI** herda diretamente do `UploadFile` do **Starlette**, mas adiciona algumas partes necessárias para torná-lo compatível com o **Pydantic** e as outras partes do FastAPI.
///
-## O que é "Form Data"
+## O que é "Form Data" { #what-is-form-data }
O jeito que os formulários HTML (``) enviam os dados para o servidor normalmente usa uma codificação "especial" para esses dados, a qual é diferente do JSON.
@@ -117,15 +117,15 @@ O jeito que os formulários HTML (``) enviam os dados para o servid
/// note | Detalhes Técnicos
-Dados de formulários normalmente são codificados usando o "media type" (tipo de mídia) `application/x-www-form-urlencoded` quando não incluem arquivos.
+Dados de formulários normalmente são codificados usando o "media type" `application/x-www-form-urlencoded` quando não incluem arquivos.
Mas quando o formulário inclui arquivos, ele é codificado como `multipart/form-data`. Se você usar `File`, o **FastAPI** saberá que tem que pegar os arquivos da parte correta do corpo da requisição.
-Se você quiser ler mais sobre essas codificações e campos de formulário, vá para a MDN web docs para POST.
+Se você quiser ler mais sobre essas codificações e campos de formulário, vá para a MDN web docs para POST.
///
-/// warning | Aviso
+/// warning | Atenção
Você pode declarar múltiplos parâmetros `File` e `Form` em uma *operação de rota*, mas você não pode declarar campos `Body` que você espera receber como JSON, pois a requisição terá o corpo codificado usando `multipart/form-data` ao invés de `application/json`.
@@ -133,19 +133,19 @@ Isso não é uma limitação do **FastAPI**, é parte do protocolo HTTP.
///
-## Upload de Arquivo Opcional
+## Upload de Arquivo Opcional { #optional-file-upload }
Você pode tornar um arquivo opcional usando anotações de tipo padrão e definindo um valor padrão de `None`:
{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
-## `UploadFile` com Metadados Adicionais
+## `UploadFile` com Metadados Adicionais { #uploadfile-with-additional-metadata }
Você também pode usar `File()` com `UploadFile`, por exemplo, para definir metadados adicionais:
{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
-## Uploads de Múltiplos Arquivos
+## Uploads de Múltiplos Arquivos { #multiple-file-uploads }
É possível realizar o upload de vários arquivos ao mesmo tempo.
@@ -165,12 +165,12 @@ Você pode também pode usar `from starlette.responses import HTMLResponse`.
///
-### Uploads de Múltiplos Arquivos com Metadados Adicionais
+### Uploads de Múltiplos Arquivos com Metadados Adicionais { #multiple-file-uploads-with-additional-metadata }
Da mesma forma de antes, você pode usar `File()` para definir parâmetros adicionais, mesmo para `UploadFile`:
{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
-## Recapitulando
+## Recapitulando { #recap }
Utilize `File`, `bytes` e `UploadFile` para declarar arquivos a serem enviados na requisição, enviados como dados de formulário.
diff --git a/docs/pt/docs/tutorial/request-form-models.md b/docs/pt/docs/tutorial/request-form-models.md
index ea0e63d381..8eeffac2ad 100644
--- a/docs/pt/docs/tutorial/request-form-models.md
+++ b/docs/pt/docs/tutorial/request-form-models.md
@@ -1,4 +1,4 @@
-# Modelos de Formulários
+# Modelos de Formulários { #form-models }
Você pode utilizar **Modelos Pydantic** para declarar **campos de formulários** no FastAPI.
@@ -20,7 +20,7 @@ Isto é suportado desde a versão `0.113.0` do FastAPI. 🤓
///
-## Modelos Pydantic para Formulários
+## Modelos Pydantic para Formulários { #pydantic-models-for-forms }
Você precisa apenas declarar um **modelo Pydantic** com os campos que deseja receber como **campos de formulários**, e então declarar o parâmetro como um `Form`:
@@ -28,7 +28,7 @@ Você precisa apenas declarar um **modelo Pydantic** com os campos que deseja re
O **FastAPI** irá **extrair** as informações para **cada campo** dos **dados do formulário** na requisição e dar para você o modelo Pydantic que você definiu.
-## Confira os Documentos
+## Confira os Documentos { #check-the-docs }
Você pode verificar na UI de documentação em `/docs`:
@@ -36,7 +36,7 @@ Você pode verificar na UI de documentação em `/docs`:
POST.
+Se você quiser ler mais sobre essas codificações e campos de formulário, vá para o MDN web docs para POST.
///
-/// warning | Aviso
+/// warning | Atenção
-Você pode declarar vários parâmetros `Form` em uma *operação de caminho*, mas não pode declarar campos `Body` que espera receber como JSON, pois a solicitação terá o corpo codificado usando `application/x-www- form-urlencoded` em vez de `application/json`.
+Você pode declarar vários parâmetros `Form` em uma *operação de rota*, mas não pode declarar campos `Body` que espera receber como JSON, pois a requisição terá o corpo codificado usando `application/x-www-form-urlencoded` em vez de `application/json`.
-Esta não é uma limitação do **FastAPI**, é parte do protocolo HTTP.
+Isso não é uma limitação do **FastAPI**, é parte do protocolo HTTP.
///
-## Recapitulando
+## Recapitulando { #recap }
Use `Form` para declarar os parâmetros de entrada de dados de formulário.
diff --git a/docs/pt/docs/tutorial/request_files.md b/docs/pt/docs/tutorial/request_files.md
deleted file mode 100644
index 15c1ad825a..0000000000
--- a/docs/pt/docs/tutorial/request_files.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# Arquivos de Requisição
-
-Você pode definir arquivos para serem enviados para o cliente utilizando `File`.
-
-/// info
-
-Para receber arquivos compartilhados, primeiro instale `python-multipart`.
-
-E.g. `pip install python-multipart`.
-
-Isso se deve por que arquivos enviados são enviados como "dados de formulário".
-
-///
-
-## Importe `File`
-
-Importe `File` e `UploadFile` do `fastapi`:
-
-{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
-
-## Defina os parâmetros de `File`
-
-Cria os parâmetros do arquivo da mesma forma que você faria para `Body` ou `Form`:
-
-{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *}
-
-/// info | Informação
-
-`File` é uma classe que herda diretamente de `Form`.
-
-Mas lembre-se que quando você importa `Query`,`Path`, `File`, entre outros, do `fastapi`, essas são na verdade funções que retornam classes especiais.
-
-///
-
-/// tip | Dica
-
-Para declarar o corpo de arquivos, você precisa utilizar `File`, do contrário os parâmetros seriam interpretados como parâmetros de consulta ou corpo (JSON) da requisição.
-
-///
-
-Os arquivos serão enviados como "form data".
-
-Se você declarar o tipo do seu parâmetro na sua *função de operação de rota* como `bytes`, o **FastAPI** irá ler o arquivo para você e você receberá o conteúdo como `bytes`.
-
-Lembre-se que isso significa que o conteúdo inteiro será armazenado em memória. Isso funciona bem para arquivos pequenos.
-
-Mas existem vários casos em que você pode se beneficiar ao usar `UploadFile`.
-
-## Parâmetros de arquivo com `UploadFile`
-
-Defina um parâmetro de arquivo com o tipo `UploadFile`
-
-{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *}
-
-Utilizando `UploadFile` tem várias vantagens sobre `bytes`:
-
-* Você não precisa utilizar `File()` como o valor padrão do parâmetro.
-* A classe utiliza um arquivo em "spool":
- * Um arquivo guardado em memória até um tamanho máximo, depois desse limite ele é guardado em disco.
-* Isso significa que a classe funciona bem com arquivos grandes como imagens, vídeos, binários extensos, etc. Sem consumir toda a memória.
-* Você pode obter metadados do arquivo enviado.
-* Ela possui uma interface semelhante a arquivos `async`.
-* Ela expõe um objeto python `SpooledTemporaryFile` que você pode repassar para bibliotecas que esperam um objeto com comportamento de arquivo.
-
-### `UploadFile`
-
-`UploadFile` tem os seguintes atributos:
-
-* `filename`: Uma string (`str`) com o nome original do arquivo enviado (e.g. `myimage.jpg`).
-* `content-type`: Uma `str` com o tipo do conteúdo (tipo MIME / media) (e.g. `image/jpeg`).
-* `file`: Um objeto do tipo `SpooledTemporaryFile` (um objeto file-like). O arquivo propriamente dito que você pode passar diretamente para outras funções ou bibliotecas que esperam um objeto "file-like".
-
-`UploadFile` tem os seguintes métodos `async`. Todos eles chamam os métodos de arquivos por baixo dos panos (usando o objeto `SpooledTemporaryFile` interno).
-
-* `write(data)`: escreve dados (`data`) em `str` ou `bytes` no arquivo.
-* `read(size)`: Lê um número de bytes/caracteres de acordo com a quantidade `size` (`int`).
-* `seek(offset)`: Navega para o byte na posição `offset` (`int`) do arquivo.
- * E.g., `await myfile.seek(0)` navegaria para o ínicio do arquivo.
- * Isso é especialmente útil se você executar `await myfile.read()` uma vez e depois precisar ler os conteúdos do arquivo de novo.
-* `close()`: Fecha o arquivo.
-
-Como todos esses métodos são assíncronos (`async`) você precisa esperar ("await") por eles.
-
-Por exemplo, dentro de uma *função de operação de rota* assíncrona você pode obter os conteúdos com:
-
-```Python
-contents = await myfile.read()
-```
-
-Se você estiver dentro de uma *função de operação de rota* definida normalmente com `def`, você pode acessar `UploadFile.file` diretamente, por exemplo:
-
-```Python
-contents = myfile.file.read()
-```
-
-/// note | Detalhes técnicos do `async`
-
-Quando você utiliza métodos assíncronos, o **FastAPI** executa os métodos do arquivo em uma threadpool e espera por eles.
-
-///
-
-/// note | Detalhes técnicos do Starlette
-
-O `UploadFile` do **FastAPI** herda diretamente do `UploadFile` do **Starlette**, mas adiciona algumas funcionalidades necessárias para ser compatível com o **Pydantic**
-
-///
-
-## O que é "Form Data"
-
-A forma como formulários HTML(``) enviam dados para o servidor normalmente utilizam uma codificação "especial" para esses dados, que é diferente do JSON.
-
-O **FastAPI** garante que os dados serão lidos da forma correta, em vez do JSON.
-
-/// note | Detalhes Técnicos
-
-Dados vindos de formulários geralmente tem a codificação com o "media type" `application/x-www-form-urlencoded` quando estes não incluem arquivos.
-
-Mas quando os dados incluem arquivos, eles são codificados como `multipart/form-data`. Se você utilizar `File`, **FastAPI** saberá que deve receber os arquivos da parte correta do corpo da requisição.
-
-Se você quer ler mais sobre essas codificações e campos de formulário, veja a documentação online da MDN sobre POST .
-
-///
-
-/// warning | Aviso
-
-Você pode declarar múltiplos parâmetros `File` e `Form` em uma *operação de rota*, mas você não pode declarar campos `Body`que seriam recebidos como JSON junto desses parâmetros, por que a codificação do corpo da requisição será `multipart/form-data` em vez de `application/json`.
-
-Isso não é uma limitação do **FastAPI**, é uma parte do protocolo HTTP.
-
-///
-
-## Arquivo de upload opcional
-
-Você pode definir um arquivo como opcional utilizando as anotações de tipo padrão e definindo o valor padrão como `None`:
-
-{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
-
-## `UploadFile` com Metadados Adicionais
-
-Você também pode utilizar `File()` com `UploadFile`, por exemplo, para definir metadados adicionais:
-
-{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
-
-## Envio de Múltiplos Arquivos
-
-É possível enviar múltiplos arquivos ao mesmo tmepo.
-
-Ele ficam associados ao mesmo "campo do formulário" enviado com "form data".
-
-Para usar isso, declare uma lista de `bytes` ou `UploadFile`:
-
-{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *}
-
-Você irá receber, como delcarado uma lista (`list`) de `bytes` ou `UploadFile`s,
-
-/// note | Detalhes Técnicos
-
-Você também poderia utilizar `from starlette.responses import HTMLResponse`.
-
-O **FastAPI** fornece as mesmas `starlette.responses` como `fastapi.responses` apenas como um facilitador para você, desenvolvedor. Mas a maior parte das respostas vem diretamente do Starlette.
-
-///
-
-### Enviando Múltiplos Arquivos com Metadados Adicionais
-
-E da mesma forma que antes, você pode utilizar `File()` para definir parâmetros adicionais, até mesmo para `UploadFile`:
-
-{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
-
-## Recapitulando
-
-Use `File`, `bytes` e `UploadFile` para declarar arquivos que serão enviados na requisição, enviados como dados do formulário.
diff --git a/docs/pt/docs/tutorial/response-model.md b/docs/pt/docs/tutorial/response-model.md
index 6726a20a72..dc66bb46c4 100644
--- a/docs/pt/docs/tutorial/response-model.md
+++ b/docs/pt/docs/tutorial/response-model.md
@@ -1,6 +1,6 @@
-# Modelo de resposta - Tipo de retorno
+# Modelo de resposta - Tipo de retorno { #response-model-return-type }
-Você pode declarar o tipo usado para a resposta anotando o **tipo de retorno** *da função de operação de rota*.
+Você pode declarar o tipo usado para a resposta anotando o **tipo de retorno** da *função de operação de rota*.
Você pode usar **anotações de tipo** da mesma forma que usaria para dados de entrada em **parâmetros** de função, você pode usar modelos Pydantic, listas, dicionários, valores escalares como inteiros, booleanos, etc.
@@ -10,7 +10,7 @@ O FastAPI usará este tipo de retorno para:
* **Validar** os dados retornados.
* Se os dados forem inválidos (por exemplo, se estiver faltando um campo), significa que o código do *seu* aplicativo está quebrado, não retornando o que deveria, e retornará um erro de servidor em vez de retornar dados incorretos. Dessa forma, você e seus clientes podem ter certeza de que receberão os dados e o formato de dados esperados.
-* Adicionar um **Esquema JSON** para a resposta, na *operação de rota* do OpenAPI.
+* Adicionar um **JSON Schema** para a resposta, na *operação de rota* do OpenAPI.
* Isso será usado pela **documentação automática**.
* Também será usado por ferramentas de geração automática de código do cliente.
@@ -19,7 +19,7 @@ Mas o mais importante:
* Ele **limitará e filtrará** os dados de saída para o que está definido no tipo de retorno.
* Isso é particularmente importante para a **segurança**, veremos mais sobre isso abaixo.
-## Parâmetro `response_model`
+## Parâmetro `response_model` { #response-model-parameter }
Existem alguns casos em que você precisa ou deseja retornar alguns dados que não são exatamente o que o tipo declara.
@@ -27,7 +27,7 @@ Por exemplo, você pode querer **retornar um dicionário** ou um objeto de banco
Se você adicionasse a anotação do tipo de retorno, ferramentas e editores reclamariam com um erro (correto) informando que sua função está retornando um tipo (por exemplo, um dict) diferente do que você declarou (por exemplo, um modelo Pydantic).
-Nesses casos, você pode usar o parâmetro `response_model` do *decorador de operação de rota* em vez do tipo de retorno.
+Nesses casos, você pode usar o parâmetro `response_model` do *decorador de operação de rota* em vez do tipo de retorno.
Você pode usar o parâmetro `response_model` em qualquer uma das *operações de rota*:
@@ -45,7 +45,7 @@ Observe que `response_model` é um parâmetro do método "decorator" (`get`, `po
///
-`response_model` recebe o mesmo tipo que você declararia para um campo de modelo Pydantic, então, pode ser um modelo Pydantic, mas também pode ser, por exemplo, uma `lista` de modelos Pydantic, como `List[Item]`.
+`response_model` recebe o mesmo tipo que você declararia para um campo de modelo Pydantic, então, pode ser um modelo Pydantic, mas também pode ser, por exemplo, uma `list` de modelos Pydantic, como `List[Item]`.
O FastAPI usará este `response_model` para fazer toda a documentação de dados, validação, etc. e também para **converter e filtrar os dados de saída** para sua declaração de tipo.
@@ -57,7 +57,7 @@ Dessa forma, você diz ao editor que está retornando qualquer coisa intencional
///
-### Prioridade `response_model`
+### Prioridade `response_model` { #response-model-priority }
Se você declarar tanto um tipo de retorno quanto um `response_model`, o `response_model` terá prioridade e será usado pelo FastAPI.
@@ -65,7 +65,7 @@ Dessa forma, você pode adicionar anotações de tipo corretas às suas funçõe
Você também pode usar `response_model=None` para desabilitar a criação de um modelo de resposta para essa *operação de rota*, você pode precisar fazer isso se estiver adicionando anotações de tipo para coisas que não são campos Pydantic válidos, você verá um exemplo disso em uma das seções abaixo.
-## Retorna os mesmos dados de entrada
+## Retorne os mesmos dados de entrada { #return-the-same-input-data }
Aqui estamos declarando um modelo `UserIn`, ele conterá uma senha em texto simples:
@@ -99,13 +99,13 @@ Neste caso, pode não ser um problema, porque é o mesmo usuário enviando a sen
Mas se usarmos o mesmo modelo para outra *operação de rota*, poderíamos estar enviando as senhas dos nossos usuários para todos os clientes.
-/// danger | Perigo
+/// danger | Cuidado
Nunca armazene a senha simples de um usuário ou envie-a em uma resposta como esta, a menos que você saiba todas as ressalvas e saiba o que está fazendo.
///
-## Adicionar um modelo de saída
+## Adicione um modelo de saída { #add-an-output-model }
Podemos, em vez disso, criar um modelo de entrada com a senha em texto simples e um modelo de saída sem ela:
@@ -121,7 +121,7 @@ Aqui, embora nossa *função de operação de rota* esteja retornando o mesmo us
Então, **FastAPI** cuidará de filtrar todos os dados que não são declarados no modelo de saída (usando Pydantic).
-### `response_model` ou Tipo de Retorno
+### `response_model` ou Tipo de Retorno { #response-model-or-return-type }
Neste caso, como os dois modelos são diferentes, se anotássemos o tipo de retorno da função como `UserOut`, o editor e as ferramentas reclamariam que estamos retornando um tipo inválido, pois são classes diferentes.
@@ -129,7 +129,7 @@ Neste caso, como os dois modelos são diferentes, se anotássemos o tipo de reto
...mas continue lendo abaixo para ver como superar isso.
-## Tipo de Retorno e Filtragem de Dados
+## Tipo de Retorno e Filtragem de Dados { #return-type-and-data-filtering }
Vamos continuar do exemplo anterior. Queríamos **anotar a função com um tipo**, mas queríamos poder retornar da função algo que realmente incluísse **mais dados**.
@@ -147,7 +147,7 @@ Com isso, temos suporte de ferramentas, de editores e mypy, pois este código es
Como isso funciona? Vamos verificar. 🤓
-### Anotações de tipo e ferramentas
+### Anotações de tipo e ferramentas { #type-annotations-and-tooling }
Primeiro, vamos ver como editores, mypy e outras ferramentas veriam isso.
@@ -157,7 +157,7 @@ Anotamos o tipo de retorno da função como `BaseUser`, mas na verdade estamos r
O editor, mypy e outras ferramentas não reclamarão disso porque, em termos de digitação, `UserIn` é uma subclasse de `BaseUser`, o que significa que é um tipo *válido* quando o que é esperado é qualquer coisa que seja um `BaseUser`.
-### Filtragem de dados FastAPI
+### Filtragem de dados FastAPI { #fastapi-data-filtering }
Agora, para FastAPI, ele verá o tipo de retorno e garantirá que o que você retornar inclua **apenas** os campos que são declarados no tipo.
@@ -165,7 +165,7 @@ O FastAPI faz várias coisas internamente com o Pydantic para garantir que essas
Dessa forma, você pode obter o melhor dos dois mundos: anotações de tipo com **suporte a ferramentas** e **filtragem de dados**.
-## Veja na documentação
+## Veja na documentação { #see-it-in-the-docs }
Quando você vê a documentação automática, pode verificar se o modelo de entrada e o modelo de saída terão seus próprios esquemas JSON:
@@ -175,29 +175,29 @@ E ambos os modelos serão usados para a documentação interativa da API:
-## Outras anotações de tipo de retorno
+## Outras anotações de tipo de retorno { #other-return-type-annotations }
Pode haver casos em que você retorna algo que não é um campo Pydantic válido e anota na função, apenas para obter o suporte fornecido pelas ferramentas (o editor, mypy, etc).
-### Retornar uma resposta diretamente
+### Retorne uma Response diretamente { #return-a-response-directly }
-O caso mais comum seria [retornar uma resposta diretamente, conforme explicado posteriormente na documentação avançada](../advanced/response-directly.md){.internal-link target=_blank}.
+O caso mais comum seria [retornar uma Response diretamente, conforme explicado posteriormente na documentação avançada](../advanced/response-directly.md){.internal-link target=_blank}.
-{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
+{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
Este caso simples é tratado automaticamente pelo FastAPI porque a anotação do tipo de retorno é a classe (ou uma subclasse de) `Response`.
E as ferramentas também ficarão felizes porque `RedirectResponse` e `JSONResponse` são subclasses de `Response`, então a anotação de tipo está correta.
-### Anotar uma subclasse de resposta
+### Anote uma subclasse de Response { #annotate-a-response-subclass }
Você também pode usar uma subclasse de `Response` na anotação de tipo:
-{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
+{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
Isso também funcionará porque `RedirectResponse` é uma subclasse de `Response`, e o FastAPI tratará automaticamente este caso simples.
-### Anotações de Tipo de Retorno Inválido
+### Anotações de Tipo de Retorno Inválido { #invalid-return-type-annotations }
Mas quando você retorna algum outro objeto arbitrário que não é um tipo Pydantic válido (por exemplo, um objeto de banco de dados) e você o anota dessa forma na função, o FastAPI tentará criar um modelo de resposta Pydantic a partir dessa anotação de tipo e falhará.
@@ -205,9 +205,9 @@ O mesmo aconteceria se você tivesse algo como uma
/// note | Nota
-
+Não importa o que você digite no formulário, ainda não vai funcionar. Mas nós vamos chegar lá.
///
- Não importa o que você digita no formulário, não vai funcionar ainda. Mas nós vamos chegar lá.
+Claro que este não é o frontend para os usuários finais, mas é uma ótima ferramenta automática para documentar interativamente toda a sua API.
-Claro que este não é o frontend para os usuários finais, mas é uma ótima ferramenta automática para documentar interativamente toda sua API.
+Pode ser usada pelo time de frontend (que pode ser você mesmo).
-Pode ser usado pelo time de frontend (que pode ser você no caso).
+Pode ser usada por aplicações e sistemas de terceiros.
-Pode ser usado por aplicações e sistemas third party (de terceiros).
+E também pode ser usada por você mesmo, para depurar, verificar e testar a mesma aplicação.
-E também pode ser usada por você mesmo, para debugar, checar e testar a mesma aplicação.
-
-## O Fluxo da `senha`
+## O fluxo de `password` { #the-password-flow }
Agora vamos voltar um pouco e entender o que é isso tudo.
-O "fluxo" da `senha` é um dos caminhos ("fluxos") definidos no OAuth2, para lidar com a segurança e autenticação.
+O "fluxo" `password` é uma das formas ("fluxos") definidas no OAuth2 para lidar com segurança e autenticação.
-OAuth2 foi projetado para que o backend ou a API pudesse ser independente do servidor que autentica o usuário.
+O OAuth2 foi projetado para que o backend ou a API pudesse ser independente do servidor que autentica o usuário.
-Mas nesse caso, a mesma aplicação **FastAPI** irá lidar com a API e a autenticação.
+Mas, neste caso, a mesma aplicação **FastAPI** irá lidar com a API e com a autenticação.
Então, vamos rever de um ponto de vista simplificado:
-* O usuário digita o `username` e a `senha` no frontend e aperta `Enter`.
-* O frontend (rodando no browser do usuário) manda o `username` e a `senha` para uma URL específica na sua API (declarada com `tokenUrl="token"`).
-* A API checa aquele `username` e `senha`, e responde com um "token" (nós não implementamos nada disso ainda).
- * Um "token" é apenas uma string com algum conteúdo que nós podemos utilizar mais tarde para verificar o usuário.
- * Normalmente, um token é definido para expirar depois de um tempo.
- * Então, o usuário terá que se logar de novo depois de um tempo.
- * E se o token for roubado, o risco é menor. Não é como se fosse uma chave permanente que vai funcionar para sempre (na maioria dos casos).
- * O frontend armazena aquele token temporariamente em algum lugar.
- * O usuário clica no frontend para ir à outra seção daquele frontend do aplicativo web.
- * O frontend precisa buscar mais dados daquela API.
- * Mas precisa de autenticação para aquele endpoint em específico.
- * Então, para autenticar com nossa API, ele manda um header de `Autorização` com o valor `Bearer` mais o token.
- * Se o token contém `foobar`, o conteúdo do header de `Autorização` será: `Bearer foobar`.
+* O usuário digita o `username` e o `password` no frontend e pressiona `Enter`.
+* O frontend (rodando no navegador do usuário) envia esse `username` e `password` para uma URL específica na nossa API (declarada com `tokenUrl="token"`).
+* A API verifica esse `username` e `password`, e responde com um "token" (ainda não implementamos nada disso).
+ * Um "token" é apenas uma string com algum conteúdo que podemos usar depois para verificar esse usuário.
+ * Normalmente, um token é definido para expirar depois de algum tempo.
+ * Então, o usuário terá que fazer login novamente em algum momento.
+ * E se o token for roubado, o risco é menor. Não é como uma chave permanente que funcionará para sempre (na maioria dos casos).
+* O frontend armazena esse token temporariamente em algum lugar.
+* O usuário clica no frontend para ir para outra seção do aplicativo web.
+* O frontend precisa buscar mais dados da API.
+ * Mas precisa de autenticação para aquele endpoint específico.
+ * Então, para autenticar com nossa API, ele envia um header `Authorization` com o valor `Bearer ` mais o token.
+ * Se o token contém `foobar`, o conteúdo do header `Authorization` seria: `Bearer foobar`.
-## **FastAPI**'s `OAuth2PasswordBearer`
+## O `OAuth2PasswordBearer` do **FastAPI** { #fastapis-oauth2passwordbearer }
-**FastAPI** fornece várias ferramentas, em diferentes níveis de abstração, para implementar esses recursos de segurança.
+O **FastAPI** fornece várias ferramentas, em diferentes níveis de abstração, para implementar essas funcionalidades de segurança.
-Neste exemplo, nós vamos usar o **OAuth2** com o fluxo de **Senha**, usando um token **Bearer**. Fazemos isso usando a classe `OAuth2PasswordBearer`.
+Neste exemplo, vamos usar **OAuth2**, com o fluxo **Password**, usando um token **Bearer**. Fazemos isso usando a classe `OAuth2PasswordBearer`.
-/// info | informação
+/// info | Informação
+Um token "bearer" não é a única opção.
+Mas é a melhor para o nosso caso de uso.
+
+E pode ser a melhor para a maioria dos casos de uso, a menos que você seja um especialista em OAuth2 e saiba exatamente por que existe outra opção que se adapta melhor às suas necessidades.
+
+Nesse caso, o **FastAPI** também fornece as ferramentas para construí-la.
///
- Um token "bearer" não é a única opção.
+Quando criamos uma instância da classe `OAuth2PasswordBearer`, passamos o parâmetro `tokenUrl`. Esse parâmetro contém a URL que o client (o frontend rodando no navegador do usuário) usará para enviar o `username` e o `password` para obter um token.
- Mas é a melhor no nosso caso.
-
- E talvez seja a melhor para a maioria dos casos, a não ser que você seja um especialista em OAuth2 e saiba exatamente o porquê de existir outras opções que se adequam melhor às suas necessidades.
-
- Nesse caso, **FastAPI** também fornece as ferramentas para construir.
-
-Quando nós criamos uma instância da classe `OAuth2PasswordBearer`, nós passamos pelo parâmetro `tokenUrl` Esse parâmetro contém a URL que o client (o frontend rodando no browser do usuário) vai usar para mandar o `username` e `senha` para obter um token.
-
-{* ../../docs_src/security/tutorial001.py hl[6] *}
+{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *}
/// tip | Dica
+Aqui `tokenUrl="token"` refere-se a uma URL relativa `token` que ainda não criamos. Como é uma URL relativa, é equivalente a `./token`.
+Como estamos usando uma URL relativa, se sua API estivesse localizada em `https://example.com/`, então se referiria a `https://example.com/token`. Mas se sua API estivesse localizada em `https://example.com/api/v1/`, então se referiria a `https://example.com/api/v1/token`.
+
+Usar uma URL relativa é importante para garantir que sua aplicação continue funcionando mesmo em um caso de uso avançado como [Atrás de um Proxy](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
///
- Esse `tokenUrl="token"` se refere a uma URL relativa que nós não criamos ainda. Como é uma URL relativa, é equivalente a `./token`.
+Esse parâmetro não cria aquele endpoint/operação de rota, mas declara que a URL `/token` será aquela que o client deve usar para obter o token. Essa informação é usada no OpenAPI e depois nos sistemas de documentação interativa da API.
- Porque estamos usando uma URL relativa, se sua API estava localizada em `https://example.com/`, então irá referir-se à `https://example.com/token`. Mas se sua API estava localizada em `https://example.com/api/v1/`, então irá referir-se à `https://example.com/api/v1/token`.
+Em breve também criaremos a operação de rota real.
- Usar uma URL relativa é importante para garantir que sua aplicação continue funcionando, mesmo em um uso avançado tipo [Atrás de um Proxy](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
-
-Esse parâmetro não cria um endpoint / *path operation*, mas declara que a URL `/token` vai ser aquela que o client deve usar para obter o token. Essa informação é usada no OpenAPI, e depois na API Interativa de documentação de sistemas.
-
-Em breve também criaremos o atual path operation.
-
-/// info | informação
+/// info | Informação
+Se você é um "Pythonista" muito rigoroso, pode não gostar do estilo do nome do parâmetro `tokenUrl` em vez de `token_url`.
+Isso ocorre porque ele usa o mesmo nome da especificação do OpenAPI. Assim, se você precisar investigar mais sobre qualquer um desses esquemas de segurança, pode simplesmente copiar e colar para encontrar mais informações sobre isso.
///
- Se você é um "Pythonista" muito rigoroso, você pode não gostar do estilo do nome do parâmetro `tokenUrl` em vez de `token_url`.
+A variável `oauth2_scheme` é uma instância de `OAuth2PasswordBearer`, mas também é "chamável" (callable).
- Isso ocorre porque está utilizando o mesmo nome que está nas especificações do OpenAPI. Então, se você precisa investigar mais sobre qualquer um desses esquemas de segurança, você pode simplesmente copiar e colar para encontrar mais informações sobre isso.
-
-A variável `oauth2_scheme` é um instância de `OAuth2PasswordBearer`, mas também é um "callable".
-
-Pode ser chamada de:
+Ela pode ser chamada como:
```Python
oauth2_scheme(some, parameters)
```
-Então, pode ser usado com `Depends`.
+Então, pode ser usada com `Depends`.
-## Use-o
+### Use-o { #use-it }
-Agora você pode passar aquele `oauth2_scheme` em uma dependência com `Depends`.
+Agora você pode passar esse `oauth2_scheme` em uma dependência com `Depends`.
-{* ../../docs_src/security/tutorial001.py hl[10] *}
+{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
-Esse dependência vai fornecer uma `str` que é atribuído ao parâmetro `token da *função do path operation*
+Essa dependência fornecerá uma `str` que é atribuída ao parâmetro `token` da função de operação de rota.
-A **FastAPI** saberá que pode usar essa dependência para definir um "esquema de segurança" no esquema da OpenAPI (e na documentação da API automática).
+O **FastAPI** saberá que pode usar essa dependência para definir um "esquema de segurança" no esquema OpenAPI (e na documentação automática da API).
-/// info | Detalhes técnicos
+/// info | Detalhes Técnicos
+O **FastAPI** saberá que pode usar a classe `OAuth2PasswordBearer` (declarada em uma dependência) para definir o esquema de segurança no OpenAPI porque ela herda de `fastapi.security.oauth2.OAuth2`, que por sua vez herda de `fastapi.security.base.SecurityBase`.
+Todos os utilitários de segurança que se integram com o OpenAPI (e com a documentação automática da API) herdam de `SecurityBase`, é assim que o **FastAPI** sabe como integrá-los ao OpenAPI.
///
- **FastAPI** saberá que pode usar a classe `OAuth2PasswordBearer` (declarada na dependência) para definir o esquema de segurança na OpenAPI porque herda de `fastapi.security.oauth2.OAuth2`, que por sua vez herda de `fastapi.security.base.Securitybase`.
+## O que ele faz { #what-it-does }
- Todos os utilitários de segurança que se integram com OpenAPI (e na documentação da API automática) herdam de `SecurityBase`, é assim que **FastAPI** pode saber como integrá-los no OpenAPI.
+Ele irá procurar na requisição pelo header `Authorization`, verificar se o valor é `Bearer ` mais algum token e retornará o token como uma `str`.
-## O que ele faz
+Se não houver um header `Authorization`, ou se o valor não tiver um token `Bearer `, ele responderá diretamente com um erro de status 401 (`UNAUTHORIZED`).
-Ele irá e olhará na requisição para aquele header de `Autorização`, verificará se o valor é `Bearer` mais algum token, e vai retornar o token como uma `str`
-
-Se ele não ver o header de `Autorização` ou o valor não tem um token `Bearer`, vai responder com um código de erro 401 (`UNAUTHORIZED`) diretamente.
-
-Você nem precisa verificar se o token existe para retornar um erro. Você pode ter certeza de que se a sua função for executada, ela terá um `str` nesse token.
+Você nem precisa verificar se o token existe para retornar um erro. Você pode ter certeza de que, se sua função for executada, ela terá uma `str` nesse token.
Você já pode experimentar na documentação interativa:
-Não estamos verificando a validade do token ainda, mas isso já é um começo
+Ainda não estamos verificando a validade do token, mas isso já é um começo.
-## Recapitulando
+## Recapitulando { #recap }
-Então, em apenas 3 ou 4 linhas extras, você já tem alguma forma primitiva de segurança.
+Então, com apenas 3 ou 4 linhas extras, você já tem alguma forma primitiva de segurança.
diff --git a/docs/pt/docs/tutorial/security/get-current-user.md b/docs/pt/docs/tutorial/security/get-current-user.md
index 1a2badb83b..2135ae236d 100644
--- a/docs/pt/docs/tutorial/security/get-current-user.md
+++ b/docs/pt/docs/tutorial/security/get-current-user.md
@@ -1,105 +1,105 @@
-# Obter Usuário Atual
-
-No capítulo anterior, o sistema de segurança (que é baseado no sistema de injeção de dependências) estava fornecendo à *função de operação de rota* um `token` como uma `str`:
-
-{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
-
-Mas isso ainda não é tão útil.
-
-Vamos fazer com que ele nos forneça o usuário atual.
-
-## Criar um modelo de usuário
-
-Primeiro, vamos criar um modelo de usuário com Pydantic.
-
-Da mesma forma que usamos o Pydantic para declarar corpos, podemos usá-lo em qualquer outro lugar:
-
-{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *}
-
-## Criar uma dependência `get_current_user`
-
-Vamos criar uma dependência chamada `get_current_user`.
-
-Lembra que as dependências podem ter subdependências?
-
-`get_current_user` terá uma dependência com o mesmo `oauth2_scheme` que criamos antes.
-
-Da mesma forma que estávamos fazendo antes diretamente na *operação de rota*, a nossa nova dependência `get_current_user` receberá um `token` como uma `str` da subdependência `oauth2_scheme`:
-
-{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *}
-
-## Obter o usuário
-
-`get_current_user` usará uma função utilitária (falsa) que criamos, que recebe um token como uma `str` e retorna nosso modelo Pydantic `User`:
-
-{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *}
-
-## Injetar o usuário atual
-
-Então agora nós podemos usar o mesmo `Depends` com nosso `get_current_user` na *operação de rota*:
-
-{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *}
-
-Observe que nós declaramos o tipo de `current_user` como o modelo Pydantic `User`.
-
-Isso nos ajudará dentro da função com todo o preenchimento automático e verificações de tipo.
-
-/// tip | Dica
-
-Você pode se lembrar que corpos de requisição também são declarados com modelos Pydantic.
-
-Aqui, o **FastAPI** não ficará confuso porque você está usando `Depends`.
-
-///
-
-/// check | Verifique
-
-A forma como esse sistema de dependências foi projetado nos permite ter diferentes dependências (diferentes "dependables") que retornam um modelo `User`.
-
-Não estamos restritos a ter apenas uma dependência que possa retornar esse tipo de dado.
-
-///
-
-## Outros modelos
-
-Agora você pode obter o usuário atual diretamente nas *funções de operação de rota* e lidar com os mecanismos de segurança no nível da **Injeção de Dependências**, usando `Depends`.
-
-E você pode usar qualquer modelo ou dado para os requisitos de segurança (neste caso, um modelo Pydantic `User`).
-
-Mas você não está restrito a usar um modelo de dados, classe ou tipo específico.
-
-Você quer ter apenas um `id` e `email`, sem incluir nenhum `username` no modelo? Claro. Você pode usar essas mesmas ferramentas.
-
-Você quer ter apenas uma `str`? Ou apenas um `dict`? Ou uma instância de modelo de classe de banco de dados diretamente? Tudo funciona da mesma forma.
-
-Na verdade, você não tem usuários que fazem login no seu aplicativo, mas sim robôs, bots ou outros sistemas, que possuem apenas um token de acesso? Novamente, tudo funciona da mesma forma.
-
-Apenas use qualquer tipo de modelo, qualquer tipo de classe, qualquer tipo de banco de dados que você precise para a sua aplicação. O **FastAPI** cobre tudo com o sistema de injeção de dependências.
-
-## Tamanho do código
-
-Este exemplo pode parecer verboso. Lembre-se de que estamos misturando segurança, modelos de dados, funções utilitárias e *operações de rota* no mesmo arquivo.
-
-Mas aqui está o ponto principal.
-
-O código relacionado à segurança e à injeção de dependências é escrito apenas uma vez.
-
-E você pode torná-lo tão complexo quanto quiser. E ainda assim, tê-lo escrito apenas uma vez, em um único lugar. Com toda a flexibilidade.
-
-Mas você pode ter milhares de endpoints (*operações de rota*) usando o mesmo sistema de segurança.
-
-E todos eles (ou qualquer parte deles que você desejar) podem aproveitar o reuso dessas dependências ou de quaisquer outras dependências que você criar.
-
-E todos esses milhares de *operações de rota* podem ter apenas 3 linhas:
-
-{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
-
-## Recapitulação
-
-Agora você pode obter o usuário atual diretamente na sua *função de operação de rota*.
-
-Já estamos na metade do caminho.
-
-Só precisamos adicionar uma *operação de rota* para que o usuário/cliente realmente envie o `username` e `password`.
-
+# Obter Usuário Atual { #get-current-user }
+
+No capítulo anterior, o sistema de segurança (que é baseado no sistema de injeção de dependências) estava fornecendo à *função de operação de rota* um `token` como uma `str`:
+
+{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
+
+Mas isso ainda não é tão útil.
+
+Vamos fazer com que ele nos forneça o usuário atual.
+
+## Criar um modelo de usuário { #create-a-user-model }
+
+Primeiro, vamos criar um modelo de usuário com Pydantic.
+
+Da mesma forma que usamos o Pydantic para declarar corpos, podemos usá-lo em qualquer outro lugar:
+
+{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *}
+
+## Criar uma dependência `get_current_user` { #create-a-get-current-user-dependency }
+
+Vamos criar uma dependência chamada `get_current_user`.
+
+Lembra que as dependências podem ter subdependências?
+
+`get_current_user` terá uma dependência com o mesmo `oauth2_scheme` que criamos antes.
+
+Da mesma forma que estávamos fazendo antes diretamente na *operação de rota*, a nossa nova dependência `get_current_user` receberá um `token` como uma `str` da subdependência `oauth2_scheme`:
+
+{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *}
+
+## Obter o usuário { #get-the-user }
+
+`get_current_user` usará uma função utilitária (falsa) que criamos, que recebe um token como uma `str` e retorna nosso modelo Pydantic `User`:
+
+{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *}
+
+## Injetar o usuário atual { #inject-the-current-user }
+
+Então agora nós podemos usar o mesmo `Depends` com nosso `get_current_user` na *operação de rota*:
+
+{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *}
+
+Observe que nós declaramos o tipo de `current_user` como o modelo Pydantic `User`.
+
+Isso nos ajudará dentro da função com todo o preenchimento automático e verificações de tipo.
+
+/// tip | Dica
+
+Você pode se lembrar que corpos de requisição também são declarados com modelos Pydantic.
+
+Aqui, o **FastAPI** não ficará confuso porque você está usando `Depends`.
+
+///
+
+/// check | Verifique
+
+A forma como esse sistema de dependências foi projetado nos permite ter diferentes dependências (diferentes "dependables") que retornam um modelo `User`.
+
+Não estamos restritos a ter apenas uma dependência que possa retornar esse tipo de dado.
+
+///
+
+## Outros modelos { #other-models }
+
+Agora você pode obter o usuário atual diretamente nas *funções de operação de rota* e lidar com os mecanismos de segurança no nível da **Injeção de Dependências**, usando `Depends`.
+
+E você pode usar qualquer modelo ou dado para os requisitos de segurança (neste caso, um modelo Pydantic `User`).
+
+Mas você não está restrito a usar um modelo de dados, classe ou tipo específico.
+
+Você quer ter apenas um `id` e `email`, sem incluir nenhum `username` no modelo? Claro. Você pode usar essas mesmas ferramentas.
+
+Você quer ter apenas uma `str`? Ou apenas um `dict`? Ou uma instância de modelo de classe de banco de dados diretamente? Tudo funciona da mesma forma.
+
+Na verdade, você não tem usuários que fazem login no seu aplicativo, mas sim robôs, bots ou outros sistemas, que possuem apenas um token de acesso? Novamente, tudo funciona da mesma forma.
+
+Apenas use qualquer tipo de modelo, qualquer tipo de classe, qualquer tipo de banco de dados que você precise para a sua aplicação. O **FastAPI** cobre tudo com o sistema de injeção de dependências.
+
+## Tamanho do código { #code-size }
+
+Este exemplo pode parecer verboso. Lembre-se de que estamos misturando segurança, modelos de dados, funções utilitárias e *operações de rota* no mesmo arquivo.
+
+Mas aqui está o ponto principal.
+
+O código relacionado à segurança e à injeção de dependências é escrito apenas uma vez.
+
+E você pode torná-lo tão complexo quanto quiser. E ainda assim, tê-lo escrito apenas uma vez, em um único lugar. Com toda a flexibilidade.
+
+Mas você pode ter milhares de endpoints (*operações de rota*) usando o mesmo sistema de segurança.
+
+E todos eles (ou qualquer parte deles que você desejar) podem aproveitar o reuso dessas dependências ou de quaisquer outras dependências que você criar.
+
+E todos esses milhares de *operações de rota* podem ter apenas 3 linhas:
+
+{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
+
+## Recapitulação { #recap }
+
+Agora você pode obter o usuário atual diretamente na sua *função de operação de rota*.
+
+Já estamos na metade do caminho.
+
+Só precisamos adicionar uma *operação de rota* para que o usuário/cliente realmente envie o `username` e `password`.
+
Isso vem a seguir.
diff --git a/docs/pt/docs/tutorial/security/index.md b/docs/pt/docs/tutorial/security/index.md
index 2ebb87fcd8..d3de3e0509 100644
--- a/docs/pt/docs/tutorial/security/index.md
+++ b/docs/pt/docs/tutorial/security/index.md
@@ -1,4 +1,4 @@
-# Segurança
+# Segurança { #security }
Há várias formas de lidar segurança, autenticação e autorização.
@@ -10,11 +10,11 @@ Em muitos frameworks e sistemas, apenas lidar com segurança e autenticação ex
Mas primeiro, vamos verificar alguns pequenos conceitos.
-## Está com pressa?
+## Está com pressa? { #in-a-hurry }
Se você não se importa com qualquer um desses termos e só precisa adicionar segurança com autenticação baseada em usuário e senha _agora_, pule para os próximos capítulos.
-## OAuth2
+## OAuth2 { #oauth2 }
OAuth2 é uma especificação que define várias formas para lidar com autenticação e autorização.
@@ -24,7 +24,7 @@ Ela inclui uma forma para autenticação usando “third party”/aplicações d
Isso é o que todos os sistemas com “Login with Facebook, Google, X (Twitter), GitHub” usam por baixo.
-### OAuth 1
+### OAuth 1 { #oauth-1 }
Havia um OAuth 1, que é bem diferente do OAuth2, e mais complexo, isso incluía diretamente as especificações de como criptografar a comunicação.
@@ -38,7 +38,7 @@ Na seção sobre **deployment** você irá ver como configurar HTTPS de modo gra
///
-## OpenID Connect
+## OpenID Connect { #openid-connect }
OpenID Connect é outra especificação, baseada em **OAuth2**.
@@ -48,7 +48,7 @@ Por exemplo, o login do Google usa OpenID Connect (que por baixo dos panos usa O
Mas o login do Facebook não tem suporte para OpenID Connect. Ele tem a própria implementação do OAuth2.
-### OpenID (não "OpenID Connect")
+### OpenID (não "OpenID Connect") { #openid-not-openid-connect }
Houve também uma especificação “OpenID”. Ela tentou resolver a mesma coisa que a **OpenID Connect**, mas não baseada em OAuth2.
@@ -56,7 +56,7 @@ Então, ela foi um sistema adicional completo.
Ela não é muito popular ou usada nos dias de hoje.
-## OpenAPI
+## OpenAPI { #openapi }
OpenAPI (anteriormente conhecido como Swagger) é a especificação aberta para a criação de APIs (agora parte da Linux Foundation).
@@ -97,7 +97,7 @@ O problema mais complexo é criar um provedor de autenticação/autorização co
///
-## **FastAPI** utilitários
+## **FastAPI** utilitários { #fastapi-utilities }
**FastAPI** fornece várias ferramentas para cada um desses esquemas de segurança no módulo `fastapi.security` que simplesmente usa esses mecanismos de segurança.
diff --git a/docs/pt/docs/tutorial/security/oauth2-jwt.md b/docs/pt/docs/tutorial/security/oauth2-jwt.md
index 7d80d12fab..f68b8c39ea 100644
--- a/docs/pt/docs/tutorial/security/oauth2-jwt.md
+++ b/docs/pt/docs/tutorial/security/oauth2-jwt.md
@@ -1,4 +1,4 @@
-# OAuth2 com Senha (e hashing), Bearer com tokens JWT
+# OAuth2 com Senha (e hashing), Bearer com tokens JWT { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens }
Agora que temos todo o fluxo de segurança, vamos tornar a aplicação realmente segura, usando tokens JWT e hashing de senhas seguras.
@@ -6,7 +6,7 @@ Este código é algo que você pode realmente usar na sua aplicação, salvar os
Vamos começar de onde paramos no capítulo anterior e incrementá-lo.
-## Sobre o JWT
+## Sobre o JWT { #about-jwt }
JWT significa "JSON Web Tokens".
@@ -26,7 +26,7 @@ Depois de uma semana, o token expirará e o usuário não estará autorizado, pr
Se você quiser brincar com tokens JWT e ver como eles funcionam, visite https://jwt.io.
-## Instalar `PyJWT`
+## Instalar `PyJWT` { #install-pyjwt }
Nós precisamos instalar o `PyJWT` para criar e verificar os tokens JWT em Python.
@@ -50,7 +50,7 @@ Você pode ler mais sobre isso na
```console
-$ pip install "passlib[bcrypt]"
+$ pip install "pwdlib[argon2]"
---> 100%
```
@@ -86,7 +86,7 @@ $ pip install "passlib[bcrypt]"
/// tip | Dica
-Com o `passlib`, você poderia até configurá-lo para ser capaz de ler senhas criadas pelo **Django**, um plug-in de segurança do **Flask** ou muitos outros.
+Com o `pwdlib`, você poderia até configurá-lo para ser capaz de ler senhas criadas pelo **Django**, um plug-in de segurança do **Flask** ou muitos outros.
Assim, você poderia, por exemplo, compartilhar os mesmos dados de um aplicativo Django em um banco de dados com um aplicativo FastAPI. Ou migrar gradualmente uma aplicação Django usando o mesmo banco de dados.
@@ -94,17 +94,17 @@ E seus usuários poderiam fazer login tanto pela sua aplicação Django quanto p
///
-## Criar o hash e verificar as senhas
+## Criar o hash e verificar as senhas { #hash-and-verify-the-passwords }
-Importe as ferramentas que nós precisamos de `passlib`.
+Importe as ferramentas que nós precisamos de `pwdlib`.
-Crie um "contexto" do PassLib. Este será usado para criar o hash e verificar as senhas.
+Crie uma instância de PasswordHash com as configurações recomendadas – ela será usada para criar o hash e verificar as senhas.
/// tip | Dica
-O contexto do PassLib também possui funcionalidades para usar diferentes algoritmos de hashing, incluindo algoritmos antigos que estão obsoletos, apenas para permitir verificá-los, etc.
+pwdlib também oferece suporte ao algoritmo de hashing bcrypt, mas não inclui algoritmos legados – para trabalhar com hashes antigos, é recomendado usar a biblioteca passlib.
-Por exemplo, você poderia usá-lo para ler e verificar senhas geradas por outro sistema (como Django), mas criar o hash de novas senhas com um algoritmo diferente, como o Bcrypt.
+Por exemplo, você poderia usá-lo para ler e verificar senhas geradas por outro sistema (como Django), mas criar o hash de novas senhas com um algoritmo diferente, como o Argon2 ou o Bcrypt.
E ser compatível com todos eles ao mesmo tempo.
@@ -120,11 +120,11 @@ E outra para autenticar e retornar um usuário.
/// note | Nota
-Se você verificar o novo banco de dados (falso) `fake_users_db`, você verá como o hash da senha se parece agora: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`.
+Se você verificar o novo banco de dados (falso) `fake_users_db`, você verá como o hash da senha se parece agora: `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`.
///
-## Manipular tokens JWT
+## Manipular tokens JWT { #handle-jwt-tokens }
Importe os módulos instalados.
@@ -154,7 +154,7 @@ Crie uma função utilitária para gerar um novo token de acesso.
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
-## Atualize as dependências
+## Atualize as dependências { #update-the-dependencies }
Atualize `get_current_user` para receber o mesmo token de antes, mas desta vez, usando tokens JWT.
@@ -164,7 +164,7 @@ Se o token for inválido, retorne um erro HTTP imediatamente.
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *}
-## Atualize a *operação de rota* `/token`
+## Atualize a *operação de rota* `/token` { #update-the-token-path-operation }
Crie um `timedelta` com o tempo de expiração do token.
@@ -172,7 +172,7 @@ Crie um token de acesso JWT real e o retorne.
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
-### Detalhes técnicos sobre o "sujeito" `sub` do JWT
+### Detalhes técnicos sobre o "sujeito" `sub` do JWT { #technical-details-about-the-jwt-subject-sub }
A especificação JWT diz que existe uma chave `sub`, com o sujeito do token.
@@ -194,7 +194,7 @@ Então, para evitar colisões de ID, ao criar o token JWT para o usuário, você
O importante a se lembrar é que a chave `sub` deve ter um identificador único em toda a aplicação e deve ser uma string.
-## Testando
+## Verifique { #check-it }
Execute o servidor e vá para a documentação: http://127.0.0.1:8000/docs.
@@ -240,7 +240,7 @@ Perceba que o cabeçalho `Authorization`, com o valor que começa com `Bearer `.
///
-## Uso avançado com `scopes`
+## Uso avançado com `scopes` { #advanced-usage-with-scopes }
O OAuth2 tem a noção de "scopes" (escopos).
@@ -250,8 +250,7 @@ Então, você pode dar este token diretamente a um usuário ou a uma terceira pa
Você pode aprender como usá-los e como eles são integrados ao **FastAPI** mais adiante no **Guia Avançado do Usuário**.
-
-## Recapitulação
+## Recapitulação { #recap }
Com o que você viu até agora, você pode configurar uma aplicação **FastAPI** segura usando padrões como OAuth2 e JWT.
@@ -265,7 +264,7 @@ O **FastAPI** não faz nenhuma concessão com nenhum banco de dados, modelo de d
Ele oferece toda a flexibilidade para você escolher as opções que melhor se ajustam ao seu projeto.
-E você pode usar diretamente muitos pacotes bem mantidos e amplamente utilizados, como `passlib` e `PyJWT`, porque o **FastAPI** não exige mecanismos complexos para integrar pacotes externos.
+E você pode usar diretamente muitos pacotes bem mantidos e amplamente utilizados, como `pwdlib` e `PyJWT`, porque o **FastAPI** não exige mecanismos complexos para integrar pacotes externos.
Mas ele fornece as ferramentas para simplificar o processo o máximo possível, sem comprometer a flexibilidade, robustez ou segurança.
diff --git a/docs/pt/docs/tutorial/security/simple-oauth2.md b/docs/pt/docs/tutorial/security/simple-oauth2.md
index 1cf05785ee..902ae2d225 100644
--- a/docs/pt/docs/tutorial/security/simple-oauth2.md
+++ b/docs/pt/docs/tutorial/security/simple-oauth2.md
@@ -1,8 +1,8 @@
-# Simples OAuth2 com senha e Bearer
+# Simples OAuth2 com senha e Bearer { #simple-oauth2-with-password-and-bearer }
Agora vamos construir a partir do capítulo anterior e adicionar as partes que faltam para ter um fluxo de segurança completo.
-## Pegue o `username` (nome de usuário) e `password` (senha)
+## Obtenha o `username` e a `password` { #get-the-username-and-password }
É utilizado o utils de segurança da **FastAPI** para obter o `username` e a `password`.
@@ -18,9 +18,9 @@ Mas para a *operação de rota* de login, precisamos usar esses nomes para serem
A especificação também afirma que o `username` e a `password` devem ser enviados como dados de formulário (portanto, não há JSON aqui).
-### `scope`
+### `scope` { #scope }
-A especificação também diz que o cliente pode enviar outro campo de formulário "`scope`" (Escopo).
+A especificação também diz que o cliente pode enviar outro campo de formulário "`scope`".
O nome do campo do formulário é `scope` (no singular), mas na verdade é uma longa string com "escopos" separados por espaços.
@@ -44,11 +44,11 @@ Para OAuth2 são apenas strings.
///
-## Código para conseguir o `username` e a `password`
+## Código para conseguir o `username` e a `password` { #code-to-get-the-username-and-password }
Agora vamos usar os utilitários fornecidos pelo **FastAPI** para lidar com isso.
-### `OAuth2PasswordRequestForm`
+### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform }
Primeiro, importe `OAuth2PasswordRequestForm` e use-o como uma dependência com `Depends` na *operação de rota* para `/token`:
@@ -59,7 +59,7 @@ Primeiro, importe `OAuth2PasswordRequestForm` e use-o como uma dependência com
* O `username`.
* A `password`.
* Um campo `scope` opcional como uma string grande, composta de strings separadas por espaços.
-* Um `grant_type` (tipo de concessão) opcional.
+* Um `grant_type` opcional.
/// tip | Dica
@@ -84,7 +84,7 @@ Mas como é um caso de uso comum, ele é fornecido diretamente pelo **FastAPI**,
///
-### Use os dados do formulário
+### Use os dados do formulário { #use-the-form-data }
/// tip | Dica
@@ -96,13 +96,13 @@ Não estamos usando `scopes` neste exemplo, mas a funcionalidade está disponív
Agora, obtenha os dados do usuário do banco de dados (falso), usando o `username` do campo do formulário.
-Se não existir tal usuário, retornaremos um erro dizendo "Incorrect username or password" (Nome de usuário ou senha incorretos).
+Se não existir tal usuário, retornaremos um erro dizendo "Incorrect username or password".
Para o erro, usamos a exceção `HTTPException`:
{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *}
-### Confira a password (senha)
+### Confira a senha { #check-the-password }
Neste ponto temos os dados do usuário do nosso banco de dados, mas não verificamos a senha.
@@ -112,7 +112,7 @@ Você nunca deve salvar senhas em texto simples, portanto, usaremos o sistema de
Se as senhas não corresponderem, retornaremos o mesmo erro.
-#### Hashing de senha
+#### Hashing de senha { #password-hashing }
"Hashing" significa: converter algum conteúdo (uma senha neste caso) em uma sequência de bytes (apenas uma string) que parece algo sem sentido.
@@ -120,7 +120,7 @@ Sempre que você passa exatamente o mesmo conteúdo (exatamente a mesma senha),
Mas você não pode converter a sequência aleatória de caracteres de volta para a senha.
-##### Porque usar hashing de senha
+##### Porque usar hashing de senha { #why-use-password-hashing }
Se o seu banco de dados for roubado, o ladrão não terá as senhas em texto simples dos seus usuários, apenas os hashes.
@@ -128,11 +128,11 @@ Assim, o ladrão não poderá tentar usar essas mesmas senhas em outro sistema (
{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *}
-#### Sobre `**user_dict`
+#### Sobre `**user_dict` { #about-user-dict }
`UserInDB(**user_dict)` significa:
-*Passe as keys (chaves) e values (valores) de `user_dict` diretamente como argumentos de valor-chave, equivalente a:*
+*Passe as chaves e valores de `user_dict` diretamente como argumentos de valor-chave, equivalente a:*
```Python
UserInDB(
@@ -146,11 +146,11 @@ UserInDB(
/// info | Informação
-Para uma explicação mais completa de `**user_dict`, verifique [a documentação para **Extra Models**](../extra-models.md#about-user_indict){.internal-link target=_blank}.
+Para uma explicação mais completa de `**user_dict`, verifique [a documentação para **Extra Models**](../extra-models.md#about-user-in-dict){.internal-link target=_blank}.
///
-## Retorne o token
+## Retorne o token { #return-the-token }
A resposta do endpoint `token` deve ser um objeto JSON.
@@ -182,11 +182,11 @@ De resto, **FastAPI** cuida disso para você.
///
-## Atualize as dependências
+## Atualize as dependências { #update-the-dependencies }
Agora vamos atualizar nossas dependências.
-Queremos obter o `user_user` *somente* se este usuário estiver ativo.
+Queremos obter o `current_user` *somente* se este usuário estiver ativo.
Portanto, criamos uma dependência adicional `get_current_active_user` que por sua vez usa `get_current_user` como dependência.
@@ -214,11 +214,11 @@ Esse é o benefício dos padrões...
///
-## Veja em ação
+## Veja em ação { #see-it-in-action }
Abra o docs interativo: http://127.0.0.1:8000/docs.
-### Autenticação
+### Autentique-se { #authenticate }
Clique no botão "Authorize".
@@ -234,7 +234,7 @@ Após autenticar no sistema, você verá assim:
-### Obtenha seus próprios dados de usuário
+### Obtenha seus próprios dados de usuário { #get-your-own-user-data }
Agora use a operação `GET` com o caminho `/users/me`.
@@ -260,7 +260,7 @@ Se você clicar no ícone de cadeado, sair e tentar a mesma operação novamente
}
```
-### Usuário inativo
+### Usuário inativo { #inactive-user }
Agora tente com um usuário inativo, autentique-se com:
@@ -278,7 +278,7 @@ Você receberá um erro "Usuário inativo", como:
}
```
-## Recaptulando
+## Recapitulando { #recap }
Agora você tem as ferramentas para implementar um sistema de segurança completo baseado em `username` e `password` para sua API.
diff --git a/docs/pt/docs/tutorial/sql-databases.md b/docs/pt/docs/tutorial/sql-databases.md
index 3d76a532cc..543e164e9f 100644
--- a/docs/pt/docs/tutorial/sql-databases.md
+++ b/docs/pt/docs/tutorial/sql-databases.md
@@ -1,4 +1,4 @@
-# Bancos de Dados SQL (Relacionais)
+# Bancos de Dados SQL (Relacionais) { #sql-relational-databases }
**FastAPI** não exige que você use um banco de dados SQL (relacional). Mas você pode usar **qualquer banco de dados** que quiser.
@@ -8,7 +8,7 @@ Aqui veremos um exemplo usando "ORMs"), o FastAPI não obriga você a usar nada. 😎
+Você pode usar qualquer outra biblioteca de banco de dados SQL ou NoSQL que quiser (em alguns casos chamadas de "ORMs"), o FastAPI não obriga você a usar nada. 😎
///
@@ -32,7 +32,7 @@ Existe um gerador de projetos oficial com **FastAPI** e **PostgreSQL** incluindo
Este é um tutorial muito simples e curto, se você quiser aprender sobre bancos de dados em geral, sobre SQL ou recursos mais avançados, acesse a documentação do SQLModel.
-## Instalar o `SQLModel`
+## Instalar o `SQLModel` { #install-sqlmodel }
Primeiro, certifique-se de criar seu [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e, em seguida, instalar o `sqlmodel`:
@@ -45,13 +45,13 @@ $ pip install sqlmodel
@@ -225,21 +193,7 @@ John Doe
Аналогично вы бы объявили `tuple` и `set`:
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial007_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial007.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
Это означает:
@@ -254,21 +208,7 @@ John Doe
Второй параметр типа — для значений `dict`:
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
Это означает:
@@ -292,10 +232,10 @@ John Doe
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008b.py!}
+{!> ../../docs_src/python_types/tutorial008b_py39.py!}
```
////
@@ -309,7 +249,7 @@ John Doe
В Python 3.6 и выше (включая Python 3.10) это можно объявить, импортировав и используя `Optional` из модуля `typing`.
```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
+{!../../docs_src/python_types/tutorial009_py39.py!}
```
Использование `Optional[str]` вместо просто `str` позволит редактору кода помочь вам обнаружить ошибки, когда вы предполагаете, что значение всегда `str`, хотя на самом деле оно может быть и `None`.
@@ -326,18 +266,18 @@ John Doe
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009.py!}
+{!> ../../docs_src/python_types/tutorial009_py39.py!}
```
////
-//// tab | Python 3.8+ альтернативный вариант
+//// tab | Python 3.9+ альтернативный вариант
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009b.py!}
+{!> ../../docs_src/python_types/tutorial009b_py39.py!}
```
////
@@ -357,7 +297,7 @@ John Doe
В качестве примера возьмём эту функцию:
-{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *}
+{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
Параметр `name` определён как `Optional[str]`, но он **не необязательный** — вы не можете вызвать функцию без этого параметра:
@@ -390,10 +330,10 @@ say_hi(name=None) # Это работает, None допустим 🎉
* `set`
* `dict`
-И, как и в Python 3.8, из модуля `typing`:
+И, как и в предыдущих версиях Python, из модуля `typing`:
* `Union`
-* `Optional` (так же, как в Python 3.8)
+* `Optional`
* ...и другие.
В Python 3.10, как альтернативу generics `Union` и `Optional`, можно использовать вертикальную черту (`|`) для объявления объединений типов — это гораздо лучше и проще.
@@ -409,7 +349,7 @@ say_hi(name=None) # Это работает, None допустим 🎉
* `set`
* `dict`
-И, как и в Python 3.8, из модуля `typing`:
+И generics из модуля `typing`:
* `Union`
* `Optional`
@@ -417,29 +357,17 @@ say_hi(name=None) # Это работает, None допустим 🎉
////
-//// tab | Python 3.8+
-
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Union`
-* `Optional`
-* ...и другие.
-
-////
-
### Классы как типы { #classes-as-types }
Вы также можете объявлять класс как тип переменной.
Допустим, у вас есть класс `Person` с именем:
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
+{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
Тогда вы можете объявить переменную типа `Person`:
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
+{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
И снова вы получите полную поддержку редактора кода:
@@ -463,29 +391,7 @@ say_hi(name=None) # Это работает, None допустим 🎉
Пример из официальной документации Pydantic:
-//// tab | Python 3.10+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py310.py!}
-```
-
-////
-
-//// tab | Python 3.9+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial011_py310.py *}
/// info | Информация
@@ -507,27 +413,9 @@ say_hi(name=None) # Это работает, None допустим 🎉
В Python также есть возможность добавлять **дополнительные метаданные** к подсказкам типов с помощью `Annotated`.
-//// tab | Python 3.9+
+Начиная с Python 3.9, `Annotated` входит в стандартную библиотеку, поэтому вы можете импортировать его из `typing`.
-В Python 3.9 `Annotated` входит в стандартную библиотеку, поэтому вы можете импортировать его из `typing`.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-В версиях ниже Python 3.9 импортируйте `Annotated` из `typing_extensions`.
-
-Он уже будет установлен вместе с **FastAPI**.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
Сам Python ничего не делает с `Annotated`. А для редакторов кода и других инструментов тип по-прежнему `str`.
@@ -558,7 +446,7 @@ say_hi(name=None) # Это работает, None допустим 🎉
...и **FastAPI** использует эти же объявления для:
-* **Определения требований**: из path-параметров, query-параметров, HTTP-заголовков, тел запросов, зависимостей и т.д.
+* **Определения требований**: из path-параметров пути запроса, query-параметров, HTTP-заголовков, тел запросов, зависимостей и т.д.
* **Преобразования данных**: из HTTP-запроса к требуемому типу.
* **Валидации данных**: приходящих с каждого HTTP-запроса:
* Генерации **автоматических ошибок**, возвращаемых клиенту, когда данные некорректны.
diff --git a/docs/ru/docs/resources/index.md b/docs/ru/docs/resources/index.md
index 54be4e5fd3..faf80f7f49 100644
--- a/docs/ru/docs/resources/index.md
+++ b/docs/ru/docs/resources/index.md
@@ -1,3 +1,3 @@
# Ресурсы { #resources }
-Дополнительные ресурсы, внешние ссылки, статьи и многое другое. ✈️
+Дополнительные ресурсы, внешние ссылки и многое другое. ✈️
diff --git a/docs/ru/docs/tutorial/background-tasks.md b/docs/ru/docs/tutorial/background-tasks.md
index 1ed8522d69..8d7b7442f9 100644
--- a/docs/ru/docs/tutorial/background-tasks.md
+++ b/docs/ru/docs/tutorial/background-tasks.md
@@ -15,7 +15,7 @@
Сначала импортируйте `BackgroundTasks` и объявите параметр в вашей функции‑обработчике пути с типом `BackgroundTasks`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
**FastAPI** создаст объект типа `BackgroundTasks` для вас и передаст его через этот параметр.
@@ -31,13 +31,13 @@
Так как операция записи не использует `async` и `await`, мы определим функцию как обычную `def`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
## Добавление фоновой задачи { #add-the-background-task }
Внутри вашей функции‑обработчика пути передайте функцию задачи объекту фоновых задач методом `.add_task()`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
`.add_task()` принимает следующие аргументы:
diff --git a/docs/ru/docs/tutorial/bigger-applications.md b/docs/ru/docs/tutorial/bigger-applications.md
index b832383cc9..5e5d6ada94 100644
--- a/docs/ru/docs/tutorial/bigger-applications.md
+++ b/docs/ru/docs/tutorial/bigger-applications.md
@@ -85,17 +85,13 @@ from app.routers import items
Точно также, как и в случае с классом `FastAPI`, вам нужно импортировать и создать объект класса `APIRouter`.
-```Python hl_lines="1 3" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
### Создание *эндпоинтов* с помощью `APIRouter` { #path-operations-with-apirouter }
В дальнейшем используйте `APIRouter` для объявления *эндпоинтов*, точно также, как вы используете класс `FastAPI`:
-```Python hl_lines="6 11 16" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
Вы можете думать об `APIRouter` как об "уменьшенной версии" класса FastAPI`.
@@ -119,35 +115,7 @@ from app.routers import items
Теперь мы воспользуемся простой зависимостью, чтобы прочитать кастомизированный `X-Token` из заголовка:
-//// tab | Python 3.9+
-
-```Python hl_lines="3 6-8" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 5-7" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app_an/dependencies.py!}
-```
-
-////
-
-//// tab | Python 3.8+ non-Annotated
-
-/// tip | Подсказка
-
-Мы рекомендуем использовать версию `Annotated`, когда это возможно.
-
-///
-
-```Python hl_lines="1 4-6" title="app/dependencies.py"
-{!> ../../docs_src/bigger_applications/app/dependencies.py!}
-```
-
-////
+{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
/// tip | Подсказка
@@ -180,9 +148,7 @@ from app.routers import items
Таким образом, вместо того чтобы добавлять все эти свойства в функцию каждого отдельного *эндпоинта*,
мы добавим их в `APIRouter`.
-```Python hl_lines="5-10 16 21" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
Так как каждый *эндпоинт* начинается с символа `/`:
@@ -241,9 +207,7 @@ async def read_item(item_id: str):
Мы используем операцию относительного импорта `..` для импорта зависимости:
-```Python hl_lines="3" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
#### Как работает относительный импорт? { #how-relative-imports-work }
@@ -313,9 +277,7 @@ from ...dependencies import get_token_header
Но помимо этого мы можем добавить новые теги для каждого отдельного *эндпоинта*, а также некоторые дополнительные ответы (`responses`), характерные для данного *эндпоинта*:
-```Python hl_lines="30-31" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *}
/// tip | Подсказка
@@ -341,17 +303,13 @@ from ...dependencies import get_token_header
Мы даже можем объявить [глобальные зависимости](dependencies/global-dependencies.md){.internal-link target=_blank}, которые будут объединены с зависимостями для каждого отдельного маршрутизатора:
-```Python hl_lines="1 3 7" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
### Импорт `APIRouter` { #import-the-apirouter }
Теперь мы импортируем другие суб-модули, содержащие `APIRouter`:
-```Python hl_lines="4-5" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
Так как файлы `app/routers/users.py` и `app/routers/items.py` являются суб-модулями одного и того же Python-пакета `app`, то мы сможем их импортировать, воспользовавшись операцией относительного импорта `.`.
@@ -414,17 +372,13 @@ from .routers.users import router
Поэтому, для того чтобы использовать обе эти переменные в одном файле, мы импортировали соответствующие суб-модули:
-```Python hl_lines="5" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *}
### Подключение маршрутизаторов (`APIRouter`) для `users` и для `items` { #include-the-apirouters-for-users-and-items }
Давайте подключим маршрутизаторы (`router`) из суб-модулей `users` и `items`:
-```Python hl_lines="10-11" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *}
/// info | Примечание
@@ -465,17 +419,13 @@ from .routers.users import router
В данном примере это сделать очень просто. Но давайте предположим, что поскольку файл используется для нескольких проектов,
то мы не можем модифицировать его, добавляя префиксы (`prefix`), зависимости (`dependencies`), теги (`tags`), и т.д. непосредственно в `APIRouter`:
-```Python hl_lines="3" title="app/internal/admin.py"
-{!../../docs_src/bigger_applications/app/internal/admin.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
Но, несмотря на это, мы хотим использовать кастомный префикс (`prefix`) для подключенного маршрутизатора (`APIRouter`), в результате чего, каждая *операция пути* будет начинаться с `/admin`. Также мы хотим защитить наш маршрутизатор с помощью зависимостей, созданных для нашего проекта. И ещё мы хотим включить теги (`tags`) и ответы (`responses`).
Мы можем применить все вышеперечисленные настройки, не изменяя начальный `APIRouter`. Нам всего лишь нужно передать нужные параметры в `app.include_router()`.
-```Python hl_lines="14-17" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *}
Таким образом, оригинальный `APIRouter` не будет модифицирован, и мы сможем использовать файл `app/internal/admin.py` сразу в нескольких проектах организации.
@@ -496,9 +446,7 @@ from .routers.users import router
Здесь мы это делаем ... просто, чтобы показать, что это возможно 🤷:
-```Python hl_lines="21-23" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
+{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *}
и это будет работать корректно вместе с другими *эндпоинтами*, добавленными с помощью `app.include_router()`.
diff --git a/docs/ru/docs/tutorial/body-nested-models.md b/docs/ru/docs/tutorial/body-nested-models.md
index 5bb5abbe63..4c914b97f0 100644
--- a/docs/ru/docs/tutorial/body-nested-models.md
+++ b/docs/ru/docs/tutorial/body-nested-models.md
@@ -14,35 +14,14 @@
В Python есть специальный способ объявлять списки с внутренними типами, или «параметрами типа»:
-### Импортируйте `List` из модуля typing { #import-typings-list }
-
-В Python 3.9 и выше вы можете использовать стандартный тип `list` для объявления аннотаций типов, как мы увидим ниже. 💡
-
-Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing`:
-
-{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
-
### Объявите `list` с параметром типа { #declare-a-list-with-a-type-parameter }
-Для объявления типов, у которых есть параметры типа (внутренние типы), таких как `list`, `dict`, `tuple`:
-
-* Если у вас Python версии ниже 3.9, импортируйте их аналоги из модуля `typing`
-* Передайте внутренний(ие) тип(ы) как «параметры типа», используя квадратные скобки: `[` и `]`
-
-В Python 3.9 это будет:
+Для объявления типов, у которых есть параметры типа (внутренние типы), таких как `list`, `dict`, `tuple`, передайте внутренний(ие) тип(ы) как «параметры типа», используя квадратные скобки: `[` и `]`
```Python
my_list: list[str]
```
-В версиях Python до 3.9 это будет:
-
-```Python
-from typing import List
-
-my_list: List[str]
-```
-
Это всё стандартный синтаксис Python для объявления типов.
Используйте этот же стандартный синтаксис для атрибутов модели с внутренними типами.
@@ -107,7 +86,7 @@ my_list: List[str]
Ещё раз: сделав такое объявление, с помощью **FastAPI** вы получите:
-* Поддержку редактора кода (автозавершение и т. д.), даже для вложенных моделей
+* Поддержку редактора кода (автозавершение и т.д.), даже для вложенных моделей
* Преобразование данных
* Валидацию данных
* Автоматическую документацию
@@ -178,12 +157,6 @@ my_list: List[str]
Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python — `list`), вы можете объявить тип в параметре функции, так же как в моделях Pydantic:
-```Python
-images: List[Image]
-```
-
-или в Python 3.9 и выше:
-
```Python
images: list[Image]
```
diff --git a/docs/ru/docs/tutorial/body.md b/docs/ru/docs/tutorial/body.md
index 16ff6466c1..b61f3e7a09 100644
--- a/docs/ru/docs/tutorial/body.md
+++ b/docs/ru/docs/tutorial/body.md
@@ -161,7 +161,7 @@ JSON Schema ваших моделей будет частью сгенериро
FastAPI понимает, что значение `q` не является обязательным из-за значения по умолчанию `= None`.
-Аннотации типов `str | None` (Python 3.10+) или `Union[str, None]` (Python 3.8+) не используются FastAPI для определения обязательности; он узнает, что параметр не обязателен, потому что у него есть значение по умолчанию `= None`.
+Аннотации типов `str | None` (Python 3.10+) или `Union[str, None]` (Python 3.9+) не используются FastAPI для определения обязательности; он узнает, что параметр не обязателен, потому что у него есть значение по умолчанию `= None`.
Но добавление аннотаций типов позволит вашему редактору кода лучше вас поддерживать и обнаруживать ошибки.
diff --git a/docs/ru/docs/tutorial/cookie-param-models.md b/docs/ru/docs/tutorial/cookie-param-models.md
index daac764e3f..182813afdf 100644
--- a/docs/ru/docs/tutorial/cookie-param-models.md
+++ b/docs/ru/docs/tutorial/cookie-param-models.md
@@ -50,7 +50,7 @@
Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) любые дополнительные (`extra`) поля:
-{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
+{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *}
Если клиент попробует отправить **дополнительные cookies**, то в ответ он получит **ошибку**.
diff --git a/docs/ru/docs/tutorial/cors.md b/docs/ru/docs/tutorial/cors.md
index b0704351a9..d09a31e2c3 100644
--- a/docs/ru/docs/tutorial/cors.md
+++ b/docs/ru/docs/tutorial/cors.md
@@ -46,7 +46,7 @@
* Отдельных HTTP-методов (`POST`, `PUT`) или всех вместе, используя `"*"`.
* Отдельных HTTP-заголовков или всех вместе, используя `"*"`.
-{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
+{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *}
`CORSMiddleware` использует "запрещающие" значения по умолчанию, поэтому вам нужно явным образом разрешить использование отдельных источников, методов или заголовков, чтобы браузеры могли использовать их в кросс-доменном контексте.
diff --git a/docs/ru/docs/tutorial/debugging.md b/docs/ru/docs/tutorial/debugging.md
index a5340af089..51955835e6 100644
--- a/docs/ru/docs/tutorial/debugging.md
+++ b/docs/ru/docs/tutorial/debugging.md
@@ -6,7 +6,7 @@
В вашем FastAPI приложении, импортируйте и вызовите `uvicorn` напрямую:
-{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
+{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *}
### Описание `__name__ == "__main__"` { #about-name-main }
diff --git a/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md
index ec7770d960..a38e885d43 100644
--- a/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md
+++ b/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md
@@ -101,7 +101,7 @@ fluffy = Cat(name="Mr Fluffy")
Обратите внимание, что в приведенном выше коде мы два раза пишем `CommonQueryParams`:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ non-Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Подсказка
@@ -137,7 +137,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
В этом случае первый `CommonQueryParams`, в:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, ...
@@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ...
////
-//// tab | Python 3.8+ non-Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Подсказка
@@ -163,7 +163,7 @@ commons: CommonQueryParams ...
На самом деле можно написать просто:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[Any, Depends(CommonQueryParams)]
@@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ non-Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Подсказка
@@ -197,7 +197,7 @@ commons = Depends(CommonQueryParams)
Но вы видите, что здесь мы имеем некоторое повторение кода, дважды написав `CommonQueryParams`:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ non-Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Подсказка
@@ -225,7 +225,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
Вместо того чтобы писать:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
-//// tab | Python 3.8+ non-Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Подсказка
@@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
...следует написать:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends()]
@@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()]
////
-//// tab | Python 3.8 non-Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Подсказка
diff --git a/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md
index 267faa4068..dc202db616 100644
--- a/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md
+++ b/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md
@@ -29,15 +29,15 @@ FastAPI поддерживает зависимости, которые выпо
Перед созданием ответа будет выполнен только код до и включая оператор `yield`:
-{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *}
Значение, полученное из `yield`, внедряется в *операции пути* и другие зависимости:
-{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *}
Код, следующий за оператором `yield`, выполняется после ответа:
-{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *}
/// tip | Подсказка
@@ -57,7 +57,7 @@ FastAPI поддерживает зависимости, которые выпо
Точно так же можно использовать `finally`, чтобы убедиться, что обязательные шаги при выходе выполнены независимо от того, было ли исключение или нет.
-{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *}
## Подзависимости с `yield` { #sub-dependencies-with-yield }
@@ -184,6 +184,51 @@ participant tasks as Background tasks
///
+## Ранний выход и `scope` { #early-exit-and-scope }
+
+Обычно «код выхода» зависимостей с `yield` выполняется **после того, как ответ** отправлен клиенту.
+
+Но если вы знаете, что не будете использовать зависимость после возврата из *функции-обработчика пути*, вы можете использовать `Depends(scope="function")`, чтобы сообщить FastAPI, что он должен закрыть зависимость после возврата из *функции-обработчика пути*, но **до того**, как **ответ будет отправлен**.
+
+{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *}
+
+`Depends()` принимает параметр `scope`, который может быть:
+
+* `"function"`: начать зависимость до *функции-обработчика пути*, которая обрабатывает запрос, завершить зависимость после окончания *функции-обработчика пути*, но **до того**, как ответ будет отправлен обратно клиенту. То есть функция зависимости будет выполнена **вокруг** *функции-обработчика пути*.
+* `"request"`: начать зависимость до *функции-обработчика пути*, которая обрабатывает запрос (как и при использовании `"function"`), но завершить **после** того, как ответ будет отправлен обратно клиенту. То есть функция зависимости будет выполнена **вокруг** цикла запроса (**request**) и ответа.
+
+Если не указано и в зависимости есть `yield`, по умолчанию будет `scope` со значением `"request"`.
+
+### `scope` для подзависимостей { #scope-for-sub-dependencies }
+
+Когда вы объявляете зависимость с `scope="request"` (значение по умолчанию), любая подзависимость также должна иметь `scope` равный `"request"`.
+
+Но зависимость со `scope` равным `"function"` может иметь зависимости со `scope` `"function"` и со `scope` `"request"`.
+
+Это потому, что любая зависимость должна иметь возможность выполнить свой код выхода раньше подзависимостей, так как ей может понадобиться использовать их во время своего кода выхода.
+
+```mermaid
+sequenceDiagram
+
+participant client as Client
+participant dep_req as Зависимость scope="request"
+participant dep_func as Зависимость scope="function"
+participant operation as Функция-обработчик пути
+
+ client ->> dep_req: Запрос
+ Note over dep_req: Выполнить код до yield
+ dep_req ->> dep_func: Передать значение
+ Note over dep_func: Выполнить код до yield
+ dep_func ->> operation: Выполнить функцию-обработчик пути
+ operation ->> dep_func: Выход из функции-обработчика пути
+ Note over dep_func: Выполнить код после yield
+ Note over dep_func: ✅ Зависимость закрыта
+ dep_func ->> client: Отправить ответ клиенту
+ Note over client: Ответ отправлен
+ Note over dep_req: Выполнить код после yield
+ Note over dep_req: ✅ Зависимость закрыта
+```
+
## Зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами { #dependencies-with-yield-httpexception-except-and-background-tasks }
Зависимости с `yield` со временем эволюционировали, чтобы покрыть разные сценарии и исправить некоторые проблемы.
@@ -224,7 +269,7 @@ with open("./somefile.txt") as f:
Их также можно использовать внутри зависимостей **FastAPI** с `yield`, применяя операторы
`with` или `async with` внутри функции зависимости:
-{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *}
+{* ../../docs_src/dependencies/tutorial010_py39.py hl[1:9,13] *}
/// tip | Подсказка
diff --git a/docs/ru/docs/tutorial/dependencies/global-dependencies.md b/docs/ru/docs/tutorial/dependencies/global-dependencies.md
index 075d6b0baa..2347c6dd83 100644
--- a/docs/ru/docs/tutorial/dependencies/global-dependencies.md
+++ b/docs/ru/docs/tutorial/dependencies/global-dependencies.md
@@ -6,7 +6,7 @@
В этом случае они будут применяться ко всем *операциям пути* в приложении:
-{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[16] *}
+{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[17] *}
Все способы [добавления `dependencies` (зависимостей) в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} по-прежнему применимы, но в данном случае зависимости применяются ко всем *операциям пути* приложения.
diff --git a/docs/ru/docs/tutorial/dependencies/sub-dependencies.md b/docs/ru/docs/tutorial/dependencies/sub-dependencies.md
index efe8d98c31..da31a6682f 100644
--- a/docs/ru/docs/tutorial/dependencies/sub-dependencies.md
+++ b/docs/ru/docs/tutorial/dependencies/sub-dependencies.md
@@ -62,7 +62,7 @@ query_extractor --> query_or_cookie_extractor --> read_query
В расширенном сценарии, когда вы знаете, что вам нужно, чтобы зависимость вызывалась на каждом шаге (возможно, несколько раз) в одном и том же запросе, вместо использования "кэшированного" значения, вы можете установить параметр `use_cache=False` при использовании `Depends`:
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python hl_lines="1"
async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]):
@@ -71,7 +71,7 @@ async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_ca
////
-//// tab | Python 3.8+ без Annotated
+//// tab | Python 3.9+ без Annotated
/// tip | Подсказка
diff --git a/docs/ru/docs/tutorial/first-steps.md b/docs/ru/docs/tutorial/first-steps.md
index c82118cbe5..798c03d517 100644
--- a/docs/ru/docs/tutorial/first-steps.md
+++ b/docs/ru/docs/tutorial/first-steps.md
@@ -2,7 +2,7 @@
Самый простой файл FastAPI может выглядеть так:
-{* ../../docs_src/first_steps/tutorial001.py *}
+{* ../../docs_src/first_steps/tutorial001_py39.py *}
Скопируйте это в файл `main.py`.
@@ -143,11 +143,47 @@ OpenAPI определяет схему API для вашего API. И эта
Вы также можете использовать её для автоматической генерации кода для клиентов, которые взаимодействуют с вашим API. Например, для фронтенд-, мобильных или IoT-приложений.
+### Разверните приложение (необязательно) { #deploy-your-app-optional }
+
+При желании вы можете развернуть своё приложение FastAPI в FastAPI Cloud, перейдите и присоединитесь к списку ожидания, если ещё не сделали этого. 🚀
+
+Если у вас уже есть аккаунт **FastAPI Cloud** (мы пригласили вас из списка ожидания 😉), вы можете развернуть приложение одной командой.
+
+Перед развертыванием убедитесь, что вы вошли в систему:
+
+kwargs, даже если у них нет значений по умолчанию.
-{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
### Лучше с `Annotated` { #better-with-annotated }
diff --git a/docs/ru/docs/tutorial/path-params.md b/docs/ru/docs/tutorial/path-params.md
index f7d138afbe..83a7ed3ffe 100644
--- a/docs/ru/docs/tutorial/path-params.md
+++ b/docs/ru/docs/tutorial/path-params.md
@@ -2,7 +2,7 @@
Вы можете определить "параметры" или "переменные" пути, используя синтаксис форматированных строк Python:
-{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
+{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
Значение параметра пути `item_id` будет передано в функцию в качестве аргумента `item_id`.
@@ -16,7 +16,7 @@
Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python:
-{* ../../docs_src/path_params/tutorial002.py hl[7] *}
+{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *}
Здесь, `item_id` объявлен типом `int`.
@@ -118,13 +118,13 @@
Поскольку *операции пути* выполняются в порядке их объявления, необходимо, чтобы путь для `/users/me` был объявлен раньше, чем путь для `/users/{user_id}`:
-{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
+{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *}
Иначе путь для `/users/{user_id}` также будет соответствовать `/users/me`, "подразумевая", что он получает параметр `user_id` со значением `"me"`.
Аналогично, вы не можете переопределить операцию с путем:
-{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *}
+{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *}
Первый будет выполняться всегда, так как путь совпадает первым.
@@ -140,13 +140,7 @@
Затем создайте атрибуты класса с фиксированными допустимыми значениями:
-{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
-
-/// info | Дополнительная информация
-
-Перечисления (enum) доступны в Python начиная с версии 3.4.
-
-///
+{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *}
/// tip | Подсказка
@@ -158,7 +152,7 @@
Определите *параметр пути*, используя в аннотации типа класс перечисления (`ModelName`), созданный ранее:
-{* ../../docs_src/path_params/tutorial005.py hl[16] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
### Проверьте документацию { #check-the-docs }
@@ -174,13 +168,13 @@
Вы можете сравнить это значение с *элементом перечисления* класса `ModelName`:
-{* ../../docs_src/path_params/tutorial005.py hl[17] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *}
#### Получение *значения перечисления* { #get-the-enumeration-value }
Можно получить фактическое значение (в данном случае - `str`) с помощью `model_name.value` или в общем случае `your_enum_member.value`:
-{* ../../docs_src/path_params/tutorial005.py hl[20] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *}
/// tip | Подсказка
@@ -194,7 +188,7 @@
Они будут преобразованы в соответствующие значения (в данном случае - строки) перед их возвратом клиенту:
-{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *}
Вы отправите клиенту такой JSON-ответ:
```JSON
@@ -232,7 +226,7 @@ OpenAPI не поддерживает способов объявления *п
Можете использовать так:
-{* ../../docs_src/path_params/tutorial004.py hl[6] *}
+{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *}
/// tip | Подсказка
diff --git a/docs/ru/docs/tutorial/query-params-str-validations.md b/docs/ru/docs/tutorial/query-params-str-validations.md
index 302901d4ec..3a4ecc37dc 100644
--- a/docs/ru/docs/tutorial/query-params-str-validations.md
+++ b/docs/ru/docs/tutorial/query-params-str-validations.md
@@ -55,7 +55,7 @@ q: str | None = None
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
q: Union[str, None] = None
@@ -73,7 +73,7 @@ q: Annotated[str | None] = None
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
q: Annotated[Union[str, None]] = None
diff --git a/docs/ru/docs/tutorial/query-params.md b/docs/ru/docs/tutorial/query-params.md
index 5a84f9768c..be1c0e46e1 100644
--- a/docs/ru/docs/tutorial/query-params.md
+++ b/docs/ru/docs/tutorial/query-params.md
@@ -2,7 +2,7 @@
Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры.
-{* ../../docs_src/query_params/tutorial001.py hl[9] *}
+{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *}
Query-параметры представляют из себя набор пар ключ-значение, которые идут после знака `?` в URL-адресе, разделенные символами `&`.
@@ -127,7 +127,7 @@ http://127.0.0.1:8000/items/foo?short=yes
Но если вы хотите сделать query-параметр обязательным, вы можете просто не указывать значение по умолчанию:
-{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
+{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
Здесь параметр запроса `needy` является обязательным параметром с типом данных `str`.
diff --git a/docs/ru/docs/tutorial/response-model.md b/docs/ru/docs/tutorial/response-model.md
index c045f9ed78..07308c1db2 100644
--- a/docs/ru/docs/tutorial/response-model.md
+++ b/docs/ru/docs/tutorial/response-model.md
@@ -183,7 +183,7 @@ FastAPI делает несколько вещей внутри вместе с
Самый распространённый случай — [возвращать Response напрямую, как описано далее в разделах для продвинутых](../advanced/response-directly.md){.internal-link target=_blank}.
-{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
+{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
Этот простой случай обрабатывается FastAPI автоматически, потому что аннотация возвращаемого типа — это класс (или подкласс) `Response`.
@@ -193,7 +193,7 @@ FastAPI делает несколько вещей внутри вместе с
Вы также можете использовать подкласс `Response` в аннотации типа:
-{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
+{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
Это тоже сработает, так как `RedirectResponse` — подкласс `Response`, и FastAPI автоматически обработает этот случай.
diff --git a/docs/ru/docs/tutorial/response-status-code.md b/docs/ru/docs/tutorial/response-status-code.md
index f5b1ff6ad7..30f642b643 100644
--- a/docs/ru/docs/tutorial/response-status-code.md
+++ b/docs/ru/docs/tutorial/response-status-code.md
@@ -8,7 +8,7 @@
* `@app.delete()`
* и других.
-{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
+{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
/// note | Примечание
@@ -74,7 +74,7 @@ FastAPI знает об этом и создаст документацию Open
Рассмотрим предыдущий пример еще раз:
-{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
+{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
`201` – это код статуса "Создано".
@@ -82,7 +82,7 @@ FastAPI знает об этом и создаст документацию Open
Для удобства вы можете использовать переменные из `fastapi.status`.
-{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
+{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *}
Они содержат те же числовые значения, но позволяют использовать автозавершение редактора кода для выбора кода статуса:
diff --git a/docs/ru/docs/tutorial/security/index.md b/docs/ru/docs/tutorial/security/index.md
index 8fb4bf24f2..ebac013b6c 100644
--- a/docs/ru/docs/tutorial/security/index.md
+++ b/docs/ru/docs/tutorial/security/index.md
@@ -1,4 +1,4 @@
-# Настройка авторизации
+# Настройка авторизации { #security }
Существует множество способов обеспечения безопасности, аутентификации и авторизации.
@@ -10,11 +10,11 @@
Но сначала давайте рассмотрим некоторые небольшие концепции.
-## Куда-то торопишься?
+## Куда-то торопишься? { #in-a-hurry }
Если вам не нужна информация о каких-либо из следующих терминов и вам просто нужно добавить защиту с аутентификацией на основе логина и пароля *прямо сейчас*, переходите к следующим главам.
-## OAuth2
+## OAuth2 { #oauth2 }
OAuth2 - это протокол, который определяет несколько способов обработки аутентификации и авторизации.
@@ -24,7 +24,7 @@ OAuth2 включает в себя способы аутентификации
Это то, что используют под собой все кнопки "вход с помощью Facebook, Google, X (Twitter), GitHub" на страницах авторизации.
-### OAuth 1
+### OAuth 1 { #oauth-1 }
Ранее использовался протокол OAuth 1, который сильно отличается от OAuth2 и является более сложным, поскольку он включал прямые описания того, как шифровать сообщение.
@@ -34,11 +34,11 @@ OAuth2 не указывает, как шифровать сообщение, о
/// tip | Подсказка
-В разделе **Развертывание** вы увидите [как настроить протокол HTTPS бесплатно, используя Traefik и Let's Encrypt.](https://fastapi.tiangolo.com/ru/deployment/https/)
+В разделе **Развертывание** вы увидите как настроить протокол HTTPS бесплатно, используя Traefik и Let's Encrypt.
///
-## OpenID Connect
+## OpenID Connect { #openid-connect }
OpenID Connect - это еще один протокол, основанный на **OAuth2**.
@@ -48,7 +48,7 @@ OpenID Connect - это еще один протокол, основанный
Но вход в Facebook не поддерживает OpenID Connect. У него есть собственная вариация OAuth2.
-### OpenID (не "OpenID Connect")
+### OpenID (не "OpenID Connect") { #openid-not-openid-connect }
Также ранее использовался стандарт "OpenID", который пытался решить ту же проблему, что и **OpenID Connect**, но не был основан на OAuth2.
@@ -56,7 +56,7 @@ OpenID Connect - это еще один протокол, основанный
В настоящее время не очень популярен и не используется.
-## OpenAPI
+## OpenAPI { #openapi }
OpenAPI (ранее известный как Swagger) - это открытая спецификация для создания API (в настоящее время является частью Linux Foundation).
@@ -97,7 +97,7 @@ OpenAPI может использовать следующие схемы авт
///
-## Преимущества **FastAPI**
+## Преимущества **FastAPI** { #fastapi-utilities }
Fast API предоставляет несколько инструментов для каждой из этих схем безопасности в модуле `fastapi.security`, которые упрощают использование этих механизмов безопасности.
diff --git a/docs/ru/docs/tutorial/sql-databases.md b/docs/ru/docs/tutorial/sql-databases.md
index c44f37b9ae..1d03465337 100644
--- a/docs/ru/docs/tutorial/sql-databases.md
+++ b/docs/ru/docs/tutorial/sql-databases.md
@@ -63,9 +63,9 @@ $ pip install sqlmodel
* `table=True` сообщает SQLModel, что это *модель-таблица*, она должна представлять **таблицу** в SQL базе данных, это не просто *модель данных* (как обычный класс Pydantic).
-* `Field(primary_key=True)` сообщает SQLModel, что `id` — это **первичный ключ** в SQL базе данных (подробнее о первичных ключах можно узнать в документации SQLModel).
+* `Field(primary_key=True)` сообщает SQLModel, что `id` — это **первичный ключ** в SQL базе данных (подробнее о первичных ключах SQL можно узнать в документации SQLModel).
- Благодаря типу `int | None`, SQLModel будет знать, что этот столбец должен быть `INTEGER` в SQL базе данных и должен допускать значение `NULL`.
+ **Примечание:** Мы используем `int | None` для поля первичного ключа, чтобы в Python-коде можно было *создать объект без `id`* (`id=None`), предполагая, что база данных *сгенерирует его при сохранении*. SQLModel понимает, что база данных предоставит `id`, и *определяет столбец как `INTEGER` (не `NULL`)* в схеме базы данных. См. документацию SQLModel о первичных ключах для подробностей.
* `Field(index=True)` сообщает SQLModel, что нужно создать **SQL индекс** для этого столбца, что позволит быстрее выполнять выборки при чтении данных, отфильтрованных по этому столбцу.
@@ -107,7 +107,7 @@ $ pip install sqlmodel
Здесь мы создаём таблицы в обработчике события запуска приложения.
-Для продакшна вы, вероятно, будете использовать скрипт миграций, который выполняется до запуска приложения. 🤓
+Для продакшн вы, вероятно, будете использовать скрипт миграций, который выполняется до запуска приложения. 🤓
/// tip | Подсказка
diff --git a/docs/ru/docs/tutorial/static-files.md b/docs/ru/docs/tutorial/static-files.md
index 8455aea0a2..f40cfe9b04 100644
--- a/docs/ru/docs/tutorial/static-files.md
+++ b/docs/ru/docs/tutorial/static-files.md
@@ -7,7 +7,7 @@
* Импортируйте `StaticFiles`.
* "Примонтируйте" экземпляр `StaticFiles()` к определённому пути.
-{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
+{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *}
/// note | Технические детали
diff --git a/docs/ru/docs/tutorial/testing.md b/docs/ru/docs/tutorial/testing.md
index 0224798b1a..ab58429c51 100644
--- a/docs/ru/docs/tutorial/testing.md
+++ b/docs/ru/docs/tutorial/testing.md
@@ -30,7 +30,7 @@ $ pip install httpx
Напишите простое утверждение с `assert` дабы проверить истинность Python-выражения (это тоже стандарт `pytest`).
-{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
+{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *}
/// tip | Подсказка
@@ -76,7 +76,7 @@ $ pip install httpx
В файле `main.py` находится Ваше приложение **FastAPI**:
-{* ../../docs_src/app_testing/main.py *}
+{* ../../docs_src/app_testing/app_a_py39/main.py *}
### Файл тестов { #testing-file }
@@ -92,7 +92,7 @@ $ pip install httpx
Так как оба файла находятся в одной директории, для импорта объекта приложения из файла `main` в файл `test_main` Вы можете использовать относительный импорт:
-{* ../../docs_src/app_testing/test_main.py hl[3] *}
+{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *}
...и писать дальше тесты, как и раньше.
@@ -121,63 +121,13 @@ $ pip install httpx
Обе *операции пути* требуют наличия в запросе заголовка `X-Token`.
-//// tab | Python 3.10+
-
-```Python
-{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
-```
-
-////
-
-//// tab | Python 3.9+
-
-```Python
-{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python
-{!> ../../docs_src/app_testing/app_b_an/main.py!}
-```
-
-////
-
-//// tab | Python 3.10+ без Annotated
-
-/// tip | Подсказка
-
-По возможности используйте версию с `Annotated`.
-
-///
-
-```Python
-{!> ../../docs_src/app_testing/app_b_py310/main.py!}
-```
-
-////
-
-//// tab | Python 3.8+ без Annotated
-
-/// tip | Подсказка
-
-По возможности используйте версию с `Annotated`.
-
-///
-
-```Python
-{!> ../../docs_src/app_testing/app_b/main.py!}
-```
-
-////
+{* ../../docs_src/app_testing/app_b_an_py310/main.py *}
### Расширенный файл тестов { #extended-testing-file }
Теперь обновим файл `test_main.py`, добавив в него тестов:
-{* ../../docs_src/app_testing/app_b/test_main.py *}
+{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *}
Если Вы не знаете, как передать информацию в запросе, можете воспользоваться поисковиком (погуглить) и задать вопрос: "Как передать информацию в запросе с помощью `httpx`", можно даже спросить: "Как передать информацию в запросе с помощью `requests`", поскольку дизайн HTTPX основан на дизайне Requests.
diff --git a/docs/ru/docs/virtual-environments.md b/docs/ru/docs/virtual-environments.md
index 5153cd4864..43136298a3 100644
--- a/docs/ru/docs/virtual-environments.md
+++ b/docs/ru/docs/virtual-environments.md
@@ -242,6 +242,26 @@ $ python -m pip install --upgrade pip
+/// tip | Подсказка
+
+Иногда при попытке обновить pip вы можете получить ошибку **`No module named pip`**.
+
+Если это произошло, установите и обновите pip с помощью команды ниже:
+
+- Готовий до продакшину, високопродуктивний, простий у вивченні та швидкий для написання коду фреймворк + Фреймворк FastAPI: висока продуктивність, легко вивчати, швидко писати код, готовий до продакшину
@@ -21,46 +27,51 @@
---
-**Документація**: https://fastapi.tiangolo.com
+**Документація**: https://fastapi.tiangolo.com
-**Програмний код**: https://github.com/fastapi/fastapi
+**Вихідний код**: https://github.com/fastapi/fastapi
---
-FastAPI - це сучасний, швидкий (високопродуктивний), вебфреймворк для створення API за допомогою Python,в основі якого лежить стандартна анотація типів Python.
+FastAPI — це сучасний, швидкий (високопродуктивний) вебфреймворк для створення API за допомогою Python, що базується на стандартних підказках типів Python.
Ключові особливості:
-* **Швидкий**: Дуже висока продуктивність, на рівні з **NodeJS** та **Go** (завдяки Starlette та Pydantic). [Один із найшвидших фреймворків](#performance).
+* **Швидкий**: дуже висока продуктивність, на рівні з **NodeJS** та **Go** (завдяки Starlette та Pydantic). [Один із найшвидших Python-фреймворків](#performance).
+* **Швидке написання коду**: пришвидшує розробку функціоналу приблизно на 200%–300%. *
+* **Менше помилок**: зменшує приблизно на 40% кількість помилок, спричинених людиною (розробником). *
+* **Інтуїтивний**: чудова підтримка редакторами коду. Автодоповнення всюди. Менше часу на налагодження.
+* **Простий**: спроєктований так, щоб бути простим у використанні та вивченні. Менше часу на читання документації.
+* **Короткий**: мінімізує дублювання коду. Кілька можливостей з кожного оголошення параметра. Менше помилок.
+* **Надійний**: ви отримуєте код, готовий до продакшину. З автоматичною інтерактивною документацією.
+* **Заснований на стандартах**: базується на (і повністю сумісний з) відкритими стандартами для API: OpenAPI (раніше відомий як Swagger) та JSON Schema.
-* **Швидке написання коду**: Пришвидшує розробку функціоналу приблизно на 200%-300%. *
-* **Менше помилок**: Зменшить кількість помилок спричинених людиною (розробником) на 40%. *
-* **Інтуїтивний**: Чудова підтримка редакторами коду. Доповнення всюди. Зменште час на налагодження.
-* **Простий**: Спроектований, для легкого використання та навчання. Знадобиться менше часу на читання документації.
-* **Короткий**: Зведе до мінімуму дублювання коду. Кожен оголошений параметр може виконувати кілька функцій.
-* **Надійний**: Ви матимете стабільний код готовий до продакшину з автоматичною інтерактивною документацією.
-* **Стандартизований**: Оснований та повністю сумісний з відкритими стандартами для API: OpenAPI (попередньо відомий як Swagger) та JSON Schema.
+* оцінка на основі тестів, проведених внутрішньою командою розробників, що створює продакшн-застосунки.
-* оцінка на основі тестів внутрішньої команди розробників, створення продуктових застосунків.
-
-## Спонсори
+## Спонсори { #sponsors }
-{% if sponsors %}
+### Ключовий спонсор { #keystone-sponsor }
+
+{% for sponsor in sponsors.keystone -%}
+
+{% endfor -%}
+
+### Золоті та срібні спонсори { #gold-and-silver-sponsors }
+
{% for sponsor in sponsors.gold -%}
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
{% endfor %}
-{% endif %}
-Other sponsors
+Інші спонсори
-## Враження
+## Враження { #opinions }
"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._"
@@ -88,7 +99,7 @@ FastAPI - це сучасний, швидкий (високопродуктив
"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._"
-
+
+## **Typer**, FastAPI для CLI { #typer-the-fastapi-of-clis }
uvicorn main:app --reload...fastapi dev main.py...email-validator - для валідації електронної пошти.
-* pydantic-settings - для управління налаштуваннями.
+
+Використовується Starlette:
+
+* httpx - потрібно, якщо ви хочете використовувати `TestClient`.
+* jinja2 - потрібно, якщо ви хочете використовувати конфігурацію шаблонів за замовчуванням.
+* python-multipart - потрібно, якщо ви хочете підтримувати «parsing» форм за допомогою `request.form()`.
+
+Використовується FastAPI:
+
+* uvicorn - для сервера, який завантажує та обслуговує ваш застосунок. Це включає `uvicorn[standard]`, до якого входять деякі залежності (наприклад, `uvloop`), потрібні для високопродуктивної роботи сервера.
+* `fastapi-cli[standard]` - щоб надати команду `fastapi`.
+ * Це включає `fastapi-cloud-cli`, який дозволяє розгортати ваш застосунок FastAPI у FastAPI Cloud.
+
+### Без залежностей `standard` { #without-standard-dependencies }
+
+Якщо ви не хочете включати необовʼязкові залежності `standard`, ви можете встановити через `pip install fastapi` замість `pip install "fastapi[standard]"`.
+
+### Без `fastapi-cloud-cli` { #without-fastapi-cloud-cli }
+
+Якщо ви хочете встановити FastAPI зі стандартними залежностями, але без `fastapi-cloud-cli`, ви можете встановити через `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
+
+### Додаткові необовʼязкові залежності { #additional-optional-dependencies }
+
+Є ще деякі додаткові залежності, які ви можете захотіти встановити.
+
+Додаткові необовʼязкові залежності Pydantic:
+
+* pydantic-settings - для керування налаштуваннями.
* pydantic-extra-types - для додаткових типів, що можуть бути використані з Pydantic.
+Додаткові необовʼязкові залежності FastAPI:
-Starlette використовує:
+* orjson - потрібно, якщо ви хочете використовувати `ORJSONResponse`.
+* ujson - потрібно, якщо ви хочете використовувати `UJSONResponse`.
-* httpx - Необхідно, якщо Ви хочете використовувати `TestClient`.
-* jinja2 - Необхідно, якщо Ви хочете використовувати шаблони як конфігурацію за замовчуванням.
-* python-multipart - Необхідно, якщо Ви хочете підтримувати "розбір" форми за допомогою `request.form()`.
-* itsdangerous - Необхідно для підтримки `SessionMiddleware`.
-* pyyaml - Необхідно для підтримки Starlette `SchemaGenerator` (ймовірно, вам це не потрібно з FastAPI).
-
-FastAPI / Starlette використовують:
-
-* uvicorn - для сервера, який завантажує та обслуговує вашу програму.
-* orjson - Необхідно, якщо Ви хочете використовувати `ORJSONResponse`.
-* ujson - Необхідно, якщо Ви хочете використовувати `UJSONResponse`.
-
-Ви можете встановити все це за допомогою `pip install fastapi[all]`.
-
-## Ліцензія
+## Ліцензія { #license }
Цей проєкт ліцензовано згідно з умовами ліцензії MIT.
diff --git a/docs/uk/docs/learn/index.md b/docs/uk/docs/learn/index.md
index 7f9f21e572..6e28d414ad 100644
--- a/docs/uk/docs/learn/index.md
+++ b/docs/uk/docs/learn/index.md
@@ -1,5 +1,5 @@
-# Навчання
+# Навчання { #learn }
-У цьому розділі надані вступні та навчальні матеріали для вивчення FastAPI.
+У цьому розділі надані вступні розділи та навчальні матеріали для вивчення **FastAPI**.
Це можна розглядати як **книгу**, **курс**, або **офіційний** та рекомендований спосіб освоїти FastAPI. 😎
diff --git a/docs/uk/docs/python-types.md b/docs/uk/docs/python-types.md
index 676bafb15c..a82d13a285 100644
--- a/docs/uk/docs/python-types.md
+++ b/docs/uk/docs/python-types.md
@@ -1,29 +1,28 @@
-# Вступ до типів Python
+# Вступ до типів Python { #python-types-intro }
-Python підтримує додаткові "підказки типу" ("type hints") (також звані "анотаціями типу" ("type annotations")).
+Python підтримує додаткові «підказки типів» (також звані «анотаціями типів»).
-Ці **"type hints"** є спеціальним синтаксисом, що дозволяє оголошувати тип змінної.
+Ці **«підказки типів»** або анотації — це спеціальний синтаксис, що дозволяє оголошувати тип змінної.
-За допомогою оголошення типів для ваших змінних, редактори та інструменти можуть надати вам кращу підтримку.
+За допомогою оголошення типів для ваших змінних редактори та інструменти можуть надати вам кращу підтримку.
-Це просто **швидкий посібник / нагадування** про анотації типів у Python. Він покриває лише мінімум, необхідний щоб використовувати їх з **FastAPI**... що насправді дуже мало.
+Це лише **швидкий туторіал / нагадування** про підказки типів у Python. Він покриває лише мінімум, необхідний щоб використовувати їх з **FastAPI**... що насправді дуже мало.
-**FastAPI** повністю базується на цих анотаціях типів, вони дають йому багато переваг.
+**FastAPI** повністю базується на цих підказках типів, вони дають йому багато переваг і користі.
Але навіть якщо ви ніколи не використаєте **FastAPI**, вам буде корисно дізнатись трохи про них.
-/// note
+/// note | Примітка
-Якщо ви експерт у Python і ви вже знаєте усе про анотації типів - перейдіть до наступного розділу.
+Якщо ви експерт у Python і ви вже знаєте все про підказки типів, перейдіть до наступного розділу.
///
-## Мотивація
+## Мотивація { #motivation }
Давайте почнемо з простого прикладу:
-{* ../../docs_src/python_types/tutorial001.py *}
-
+{* ../../docs_src/python_types/tutorial001_py39.py *}
Виклик цієї програми виводить:
@@ -34,13 +33,12 @@ John Doe
Функція виконує наступне:
* Бере `first_name` та `last_name`.
-* Конвертує кожну літеру кожного слова у верхній регістр за допомогою `title()`.
-* Конкатенує їх разом із пробілом по середині.
+* Перетворює першу літеру кожного з них у верхній регістр за допомогою `title()`.
+* Конкатенує їх разом із пробілом по середині.
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
+{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
-
-### Редагуйте це
+### Редагуйте це { #edit-it }
Це дуже проста програма.
@@ -48,11 +46,11 @@ John Doe
У певний момент ви розпочали б визначення функції, у вас були б готові параметри...
-Але тоді вам потрібно викликати "той метод, який переводить першу літеру у верхній регістр".
+Але тоді вам потрібно викликати «той метод, який перетворює першу літеру у верхній регістр».
Це буде `upper`? Чи `uppercase`? `first_uppercase`? `capitalize`?
-Тоді ви спробуєте давнього друга програміста - автозаповнення редактора коду.
+Тоді ви спробуєте давнього друга програміста — автозаповнення редактора коду.
Ви надрукуєте перший параметр функції, `first_name`, тоді крапку (`.`), а тоді натиснете `Ctrl+Space`, щоб запустити автозаповнення.
@@ -60,7 +58,7 @@ John Doe
-### Додайте типи
+### Додайте типи { #add-types }
Давайте змінимо один рядок з попередньої версії.
@@ -78,10 +76,9 @@ John Doe
Ось і все.
-Це "type hints":
-
-{* ../../docs_src/python_types/tutorial002.py hl[1] *}
+Це «підказки типів»:
+{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
Це не те саме, що оголошення значень за замовчуванням, як це було б з:
@@ -91,43 +88,41 @@ John Doe
Це зовсім інше.
-Ми використовуємо двокрапку (`:`), не дорівнює (`=`).
+Ми використовуємо двокрапку (`:`), не знак дорівнює (`=`).
-І додавання анотації типу зазвичай не змінює того, що сталось би без них.
+І додавання підказок типів зазвичай не змінює того, що відбувається, порівняно з тим, що відбувалося б без них.
-Але тепер, уявіть що ви посеред процесу створення функції, але з анотаціями типів.
+Але тепер уявіть, що ви знову посеред процесу створення функції, але з підказками типів.
-В цей же момент, ви спробуєте викликати автозаповнення з допомогою `Ctrl+Space` і побачите:
+У той самий момент ви спробуєте викликати автозаповнення за допомогою `Ctrl+Space` і побачите:
-Разом з цим, ви можете прокручувати, переглядати опції, допоки ви не знайдете одну, що звучить схоже:
+Разом з цим ви можете прокручувати, переглядаючи опції, допоки не знайдете ту, що «щось вам підказує»:
-## Більше мотивації
+## Більше мотивації { #more-motivation }
-Перевірте цю функцію, вона вже має анотацію типу:
-
-{* ../../docs_src/python_types/tutorial003.py hl[1] *}
+Перевірте цю функцію, вона вже має підказки типів:
+{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}
Оскільки редактор знає типи змінних, ви не тільки отримаєте автозаповнення, ви також отримаєте перевірку помилок:
-Тепер ви знаєте, щоб виправити це, вам потрібно перетворити `age` у строку з допомогою `str(age)`:
+Тепер ви знаєте, щоб виправити це, вам потрібно перетворити `age` у рядок за допомогою `str(age)`:
-{* ../../docs_src/python_types/tutorial004.py hl[2] *}
+{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
+## Оголошення типів { #declaring-types }
-## Оголошення типів
-
-Щойно ви побачили основне місце для оголошення анотацій типу. Як параметри функції.
+Щойно ви побачили основне місце для оголошення підказок типів. Як параметри функції.
Це також основне місце, де ви б їх використовували у **FastAPI**.
-### Прості типи
+### Прості типи { #simple-types }
Ви можете оголошувати усі стандартні типи у Python, не тільки `str`.
@@ -138,78 +133,51 @@ John Doe
* `bool`
* `bytes`
-{* ../../docs_src/python_types/tutorial005.py hl[1] *}
+{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
-
-### Generic-типи з параметрами типів
+### Generic-типи з параметрами типів { #generic-types-with-type-parameters }
Існують деякі структури даних, які можуть містити інші значення, наприклад `dict`, `list`, `set` та `tuple`. І внутрішні значення також можуть мати свій тип.
-Ці типи, які мають внутрішні типи, називаються "**generic**" типами. І оголосити їх можна навіть із внутрішніми типами.
+Ці типи, які мають внутрішні типи, називаються «**generic**» типами. І оголосити їх можна навіть із внутрішніми типами.
-Щоб оголосити ці типи та внутрішні типи, ви можете використовувати стандартний модуль Python `typing`. Він існує спеціально для підтримки анотацій типів.
+Щоб оголосити ці типи та внутрішні типи, ви можете використовувати стандартний модуль Python `typing`. Він існує спеціально для підтримки цих підказок типів.
-#### Новіші версії Python
+#### Новіші версії Python { #newer-versions-of-python }
Синтаксис із використанням `typing` **сумісний** з усіма версіями, від Python 3.6 до останніх, включаючи Python 3.9, Python 3.10 тощо.
-У міру розвитку Python **новіші версії** мають покращену підтримку анотацій типів і в багатьох випадках вам навіть не потрібно буде імпортувати та використовувати модуль `typing` для оголошення анотацій типу.
+У міру розвитку Python **новіші версії** мають покращену підтримку цих анотацій типів і в багатьох випадках вам навіть не потрібно буде імпортувати та використовувати модуль `typing` для оголошення анотацій типів.
-Якщо ви можете вибрати новішу версію Python для свого проекту, ви зможете скористатися цією додатковою простотою. Дивіться кілька прикладів нижче.
+Якщо ви можете вибрати новішу версію Python для свого проекту, ви зможете скористатися цією додатковою простотою.
-#### List (список)
+У всій документації є приклади, сумісні з кожною версією Python (коли є різниця).
+
+Наприклад, «**Python 3.6+**» означає, що це сумісно з Python 3.6 або вище (включно з 3.7, 3.8, 3.9, 3.10 тощо). А «**Python 3.9+**» означає, що це сумісно з Python 3.9 або вище (включаючи 3.10 тощо).
+
+Якщо ви можете використовувати **останні версії Python**, використовуйте приклади для останньої версії — вони матимуть **найкращий і найпростіший синтаксис**, наприклад, «**Python 3.10+**».
+
+#### List { #list }
Наприклад, давайте визначимо змінну, яка буде `list` із `str`.
-//// tab | Python 3.8 і вище
+Оголосіть змінну з тим самим синтаксисом двокрапки (`:`).
-З модуля `typing`, імпортуємо `List` (з великої літери `L`):
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-Оголосимо змінну з тим самим синтаксисом двокрапки (`:`).
-
-Як тип вкажемо `List`, який ви імпортували з `typing`.
+Як тип вкажіть `list`.
Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки:
-```Python hl_lines="4"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
+{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
-////
+/// info | Інформація
-//// tab | Python 3.9 і вище
+Ці внутрішні типи в квадратних дужках називаються «параметрами типу».
-Оголосимо змінну з тим самим синтаксисом двокрапки (`:`).
-
-Як тип вкажемо `list`.
-
-Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки:
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006_py39.py!}
-```
-
-////
-
-/// info
-
-Ці внутрішні типи в квадратних дужках називаються "параметрами типу".
-
-У цьому випадку, `str` це параметр типу переданий у `List` (або `list` у Python 3.9 і вище).
+У цьому випадку `str` — це параметр типу, переданий у `list`.
///
-Це означає: "змінна `items` це `list`, і кожен з елементів у цьому списку - `str`".
-
-/// tip
-
-Якщо ви використовуєте Python 3.9 і вище, вам не потрібно імпортувати `List` з `typing`, ви можете використовувати натомість тип `list`.
-
-///
+Це означає: «змінна `items` — це `list`, і кожен з елементів у цьому списку — `str`».
Зробивши це, ваш редактор може надати підтримку навіть під час обробки елементів зі списку:
@@ -221,78 +189,42 @@ John Doe
І все ж редактор знає, що це `str`, і надає підтримку для цього.
-#### Tuple and Set (кортеж та набір)
+#### Tuple and Set { #tuple-and-set }
Ви повинні зробити те ж саме, щоб оголосити `tuple` і `set`:
-//// tab | Python 3.8 і вище
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial007.py!}
-```
-
-////
-
-//// tab | Python 3.9 і вище
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial007_py39.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
Це означає:
-* Змінна `items_t` це `tuple` з 3 елементами, `int`, ще `int`, та `str`.
-* Змінна `items_s` це `set`, і кожен його елемент типу `bytes`.
+* Змінна `items_t` — це `tuple` з 3 елементами: `int`, ще `int`, та `str`.
+* Змінна `items_s` — це `set`, і кожен його елемент має тип `bytes`.
-#### Dict (словник)
+#### Dict { #dict }
Щоб оголосити `dict`, вам потрібно передати 2 параметри типу, розділені комами.
-Перший параметр типу для ключа у `dict`.
+Перший параметр типу для ключів у `dict`.
-Другий параметр типу для значення у `dict`:
+Другий параметр типу для значень у `dict`:
-//// tab | Python 3.8 і вище
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008.py!}
-```
-
-////
-
-//// tab | Python 3.9 і вище
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008_py39.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
Це означає:
-* Змінна `prices` це `dict`:
- * Ключі цього `dict` типу `str` (наприклад, назва кожного елементу).
- * Значення цього `dict` типу `float` (наприклад, ціна кожного елементу).
+* Змінна `prices` — це `dict`:
+ * Ключі цього `dict` мають тип `str` (скажімо, назва кожного елементу).
+ * Значення цього `dict` мають тип `float` (скажімо, ціна кожного елементу).
-#### Union (об'єднання)
+#### Union { #union }
-Ви можете оголосити, що змінна може бути будь-яким із **кількох типів**, наприклад, `int` або `str`.
+Ви можете оголосити, що змінна може бути будь-яким із **кількох типів**, наприклад `int` або `str`.
У Python 3.6 і вище (включаючи Python 3.10) ви можете використовувати тип `Union` з `typing` і вставляти в квадратні дужки можливі типи, які можна прийняти.
-У Python 3.10 також є **альтернативний синтаксис**, у якому ви можете розділити можливі типи за допомогою вертикальної смуги (`|`).
+У Python 3.10 також є **новий синтаксис**, у якому ви можете розділити можливі типи за допомогою вертикальної смуги (`|`).
-//// tab | Python 3.8 і вище
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008b.py!}
-```
-
-////
-
-//// tab | Python 3.10 і вище
+//// tab | Python 3.10+
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial008b_py310.py!}
@@ -300,16 +232,24 @@ John Doe
////
+//// tab | Python 3.9+
+
+```Python hl_lines="1 4"
+{!> ../../docs_src/python_types/tutorial008b_py39.py!}
+```
+
+////
+
В обох випадках це означає, що `item` може бути `int` або `str`.
-#### Possibly `None` (Optional)
+#### Можливо `None` { #possibly-none }
Ви можете оголосити, що значення може мати тип, наприклад `str`, але також може бути `None`.
У Python 3.6 і вище (включаючи Python 3.10) ви можете оголосити його, імпортувавши та використовуючи `Optional` з модуля `typing`.
```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
+{!../../docs_src/python_types/tutorial009_py39.py!}
```
Використання `Optional[str]` замість просто `str` дозволить редактору допомогти вам виявити помилки, коли ви могли б вважати, що значенням завжди є `str`, хоча насправді воно також може бути `None`.
@@ -318,23 +258,7 @@ John Doe
Це також означає, що в Python 3.10 ви можете використовувати `Something | None`:
-//// tab | Python 3.8 і вище
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009.py!}
-```
-
-////
-
-//// tab | Python 3.8 і вище - альтернатива
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009b.py!}
-```
-
-////
-
-//// tab | Python 3.10 і вище
+//// tab | Python 3.10+
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial009_py310.py!}
@@ -342,32 +266,90 @@ John Doe
////
-#### Generic типи
+//// tab | Python 3.9+
+
+```Python hl_lines="1 4"
+{!> ../../docs_src/python_types/tutorial009_py39.py!}
+```
+
+////
+
+//// tab | Python 3.9+ alternative
+
+```Python hl_lines="1 4"
+{!> ../../docs_src/python_types/tutorial009b_py39.py!}
+```
+
+////
+
+#### Використання `Union` або `Optional` { #using-union-or-optional }
+
+Якщо ви використовуєте версію Python нижче 3.10, ось порада з моєї дуже **суб’єктивної** точки зору:
+
+* 🚨 Уникайте використання `Optional[SomeType]`
+* Натомість ✨ **використовуйте `Union[SomeType, None]`** ✨.
+
+Обидва варіанти еквівалентні й «під капотом» це одне й те саме, але я рекомендую `Union` замість `Optional`, тому що слово «**optional**» може створювати враження, ніби значення є необов’язковим, хоча насправді це означає «воно може бути `None`», навіть якщо воно не є необов’язковим і все одно є обов’язковим.
+
+Я вважаю, що `Union[SomeType, None]` більш явно показує, що саме мається на увазі.
+
+Це лише про слова й назви. Але ці слова можуть впливати на те, як ви та ваша команда думаєте про код.
+
+Як приклад, розгляньмо цю функцію:
+
+{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
+
+Параметр `name` визначено як `Optional[str]`, але він **не є необов’язковим**, ви не можете викликати функцію без параметра:
+
+```Python
+say_hi() # Ой, ні, це викликає помилку! 😱
+```
+
+Параметр `name` **все ще є обов’язковим** (не *optional*), тому що він не має значення за замовчуванням. Водночас `name` приймає `None` як значення:
+
+```Python
+say_hi(name=None) # Це працює, None є валідним 🎉
+```
+
+Добра новина: щойно ви перейдете на Python 3.10, вам не доведеться про це хвилюватися, адже ви зможете просто використовувати `|` для визначення об’єднань типів:
+
+{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
+
+І тоді вам не доведеться хвилюватися про назви на кшталт `Optional` і `Union`. 😎
+
+#### Generic типи { #generic-types }
Ці типи, які приймають параметри типу у квадратних дужках, називаються **Generic types** or **Generics**, наприклад:
-//// tab | Python 3.8 і вище
+//// tab | Python 3.10+
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Union`
-* `Optional`
-* ...та інші.
-
-////
-
-//// tab | Python 3.9 і вище
-
-Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині):
+Ви можете використовувати ті самі вбудовані типи як generic (з квадратними дужками та типами всередині):
* `list`
* `tuple`
* `set`
* `dict`
-І те саме, що й у Python 3.8, із модуля `typing`:
+І так само, як і в попередніх версіях Python, з модуля `typing`:
+
+* `Union`
+* `Optional`
+* ...та інші.
+
+У Python 3.10 як альтернативу використанню generic `Union` та `Optional` ви можете використовувати вертикальну смугу (`|`) для оголошення об’єднань типів — це значно краще й простіше.
+
+////
+
+//// tab | Python 3.9+
+
+Ви можете використовувати ті самі вбудовані типи як generic (з квадратними дужками та типами всередині):
+
+* `list`
+* `tuple`
+* `set`
+* `dict`
+
+І generic з модуля `typing`:
* `Union`
* `Optional`
@@ -375,46 +357,29 @@ John Doe
////
-//// tab | Python 3.10 і вище
-
-Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині):
-
-* `list`
-* `tuple`
-* `set`
-* `dict`
-
-І те саме, що й у Python 3.8, із модуля `typing`:
-
-* `Union`
-* `Optional` (так само як у Python 3.8)
-* ...та інші.
-
-У Python 3.10, як альтернатива використанню `Union` та `Optional`, ви можете використовувати вертикальну смугу (`|`) щоб оголосити об'єднання типів.
-
-////
-
-### Класи як типи
+### Класи як типи { #classes-as-types }
Ви також можете оголосити клас як тип змінної.
Скажімо, у вас є клас `Person` з імʼям:
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
-
+{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
Потім ви можете оголосити змінну типу `Person`:
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
-
+{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
І знову ж таки, ви отримуєте всю підтримку редактора:
-## Pydantic моделі
+Зверніть увагу, що це означає: «`one_person` — це **екземпляр** класу `Person`».
-Pydantic це бібліотека Python для валідації даних.
+Це не означає: «`one_person` — це **клас** з назвою `Person`».
+
+## Pydantic моделі { #pydantic-models }
+
+Pydantic — це бібліотека Python для валідації даних.
Ви оголошуєте «форму» даних як класи з атрибутами.
@@ -424,33 +389,11 @@ John Doe
І ви отримуєте всю підтримку редактора з цим отриманим об’єктом.
-Приклад з документації Pydantic:
+Приклад з офіційної документації Pydantic:
-//// tab | Python 3.8 і вище
+{* ../../docs_src/python_types/tutorial011_py310.py *}
-```Python
-{!> ../../docs_src/python_types/tutorial011.py!}
-```
-
-////
-
-//// tab | Python 3.9 і вище
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py39.py!}
-```
-
-////
-
-//// tab | Python 3.10 і вище
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py310.py!}
-```
-
-////
-
-/// info
+/// info | Інформація
Щоб дізнатись більше про Pydantic, перегляньте його документацію.
@@ -460,11 +403,43 @@ John Doe
Ви побачите набагато більше цього всього на практиці в [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
-## Анотації типів у **FastAPI**
+/// tip | Порада
-**FastAPI** використовує ці підказки для виконання кількох речей.
+Pydantic має спеціальну поведінку, коли ви використовуєте `Optional` або `Union[Something, None]` без значення за замовчуванням; детальніше про це можна прочитати в документації Pydantic про Required Optional fields.
-З **FastAPI** ви оголошуєте параметри з підказками типу, і отримуєте:
+///
+
+## Підказки типів з анотаціями метаданих { #type-hints-with-metadata-annotations }
+
+У Python також є можливість додавати **додаткові метадані** до цих підказок типів за допомогою `Annotated`.
+
+Починаючи з Python 3.9, `Annotated` є частиною стандартної бібліотеки, тож ви можете імпортувати його з `typing`.
+
+{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
+
+Сам Python нічого не робить із цим `Annotated`. А для редакторів та інших інструментів тип усе ще є `str`.
+
+Але ви можете використати це місце в `Annotated`, щоб надати **FastAPI** додаткові метадані про те, як ви хочете, щоб ваш застосунок поводився.
+
+Важливо пам’ятати, що **перший *параметр типу***, який ви передаєте в `Annotated`, — це **фактичний тип**. Решта — це лише метадані для інших інструментів.
+
+Наразі вам просто потрібно знати, що `Annotated` існує і що це стандартний Python. 😎
+
+Пізніше ви побачите, наскільки **потужним** це може бути.
+
+/// tip | Порада
+
+Той факт, що це **стандартний Python**, означає, що ви й надалі отримуватимете **найкращий можливий досвід розробки** у вашому редакторі, з інструментами, які ви використовуєте для аналізу та рефакторингу коду тощо. ✨
+
+А також те, що ваш код буде дуже сумісним із багатьма іншими інструментами та бібліотеками Python. 🚀
+
+///
+
+## Анотації типів у **FastAPI** { #type-hints-in-fastapi }
+
+**FastAPI** використовує ці підказки типів для виконання кількох речей.
+
+З **FastAPI** ви оголошуєте параметри з підказками типів, і отримуєте:
* **Підтримку редактора**.
* **Перевірку типів**.
@@ -473,17 +448,17 @@ John Doe
* **Визначення вимог**: з параметрів шляху запиту, параметрів запиту, заголовків, тіл, залежностей тощо.
* **Перетворення даних**: із запиту в необхідний тип.
-* **Перевірка даних**: що надходять від кожного запиту:
+* **Перевірки даних**: що надходять від кожного запиту:
* Генерування **автоматичних помилок**, що повертаються клієнту, коли дані недійсні.
* **Документування** API за допомогою OpenAPI:
* який потім використовується для автоматичної інтерактивної документації користувальницьких інтерфейсів.
-Все це може здатися абстрактним. Не хвилюйтеся. Ви побачите все це в дії в [Туторіал - Посібник користувача](tutorial/index.md){.internal-link target=_blank}.
+Все це може здатися абстрактним. Не хвилюйтеся. Ви побачите все це в дії в [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
Важливо те, що за допомогою стандартних типів Python в одному місці (замість того, щоб додавати більше класів, декораторів тощо), **FastAPI** зробить багато роботи за вас.
-/// info
+/// info | Інформація
-Якщо ви вже пройшли весь навчальний посібник і повернулися, щоб дізнатися більше про типи, ось хороший ресурс "шпаргалка" від `mypy`.
+Якщо ви вже пройшли весь туторіал і повернулися, щоб дізнатися більше про типи, ось хороший ресурс: «шпаргалка» від `mypy`.
///
diff --git a/docs/uk/docs/tutorial/background-tasks.md b/docs/uk/docs/tutorial/background-tasks.md
index 0a9349650d..6d7804195e 100644
--- a/docs/uk/docs/tutorial/background-tasks.md
+++ b/docs/uk/docs/tutorial/background-tasks.md
@@ -1,28 +1,27 @@
-# Фонові задачі
+# Фонові задачі { #background-tasks }
Ви можете створювати фонові задачі, які будуть виконуватися *після* повернення відповіді.
Це корисно для операцій, які потрібно виконати після обробки запиту, але клієнту не обов’язково чекати завершення цієї операції перед отриманням відповіді.
-Приклади використання:
+Це включає, наприклад:
* Надсилання email-сповіщень після виконання певної дії:
- * Підключення до поштового сервера та надсилання листа може займати кілька секунд. Ви можете відразу повернути відповідь, а email відправити у фоні.
+ * Підключення до поштового сервера та надсилання листа може займати кілька секунд. Ви можете відразу повернути відповідь, а email-сповіщення надіслати у фоні.
* Обробка даних:
- * Наприклад, якщо отримано файл, який потрібно обробити довготривалим процесом, можна повернути відповідь "Accepted" ("Прийнято", HTTP 202) і виконати обробку файлу у фоні.
+ * Наприклад, якщо ви отримали файл, який потрібно обробити довготривалим процесом, можна повернути відповідь «Accepted» (HTTP 202) і виконати обробку файлу у фоні.
-## Використання `BackgroundTasks`
+## Використання `BackgroundTasks` { #using-backgroundtasks }
-Спочатку імпортуйте `BackgroundTasks` і додайте його як параметр у Вашу *функцію операції шляху* (path operation function) до `BackgroundTasks`:
+Спочатку імпортуйте `BackgroundTasks` і оголосіть параметр у вашій *функції операції шляху* з анотацією типу `BackgroundTasks`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
-**FastAPI** автоматично створить об'єкт `BackgroundTasks` і передасть його у цей параметр.
+**FastAPI** створить для вас об’єкт типу `BackgroundTasks` і передасть його як цей параметр.
+## Створення функції задачі { #create-a-task-function }
-## Створення функції задачі
-
-Створіть функцію, яка буде виконувати фонову задачу.
+Створіть функцію, яка буде виконуватися як фонова задача.
Це звичайна функція, яка може отримувати параметри.
@@ -32,54 +31,54 @@
І оскільки операція запису не використовує `async` та `await`, ми визначаємо функцію як звичайну `def`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
-## Додавання фонової задачі
+## Додавання фонової задачі { #add-the-background-task }
-Усередині Вашої *функції обробки шляху*, передайте функцію задачі в об'єкт *background tasks*, використовуючи метод `.add_task()`:
+Усередині вашої *функції операції шляху*, передайте функцію задачі в об'єкт *background tasks*, використовуючи метод `.add_task()`:
-{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
+{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
`.add_task()` приймає аргументи:
-* Функція задача, яка буде виконуватися у фоновому режимі (`write_notification`). Зверніть увагу, що передається обʼєкт без дужок.
-* Будь-яка послідовність аргументів, які потрібно передати у функцію завдання у відповідному порядку (`email`).
-* Будь-які іменовані аргументи, які потрібно передати у функцію задачу (`message="some notification"`).
+* Функцію задачі, яка буде виконуватися у фоновому режимі (`write_notification`).
+* Будь-яку послідовність аргументів, які потрібно передати у функцію задачі у відповідному порядку (`email`).
+* Будь-які іменовані аргументи, які потрібно передати у функцію задачі (`message="some notification"`).
-## Впровадження залежностей
+## Впровадження залежностей { #dependency-injection }
-Використання `BackgroundTasks` також працює з системою впровадження залежностей. Ви можете оголосити параметр типу `BackgroundTasks` на різних рівнях: у *функції операції шляху*, у залежності (dependable), у під залежності тощо.
+Використання `BackgroundTasks` також працює з системою впровадження залежностей. Ви можете оголосити параметр типу `BackgroundTasks` на різних рівнях: у *функції операції шляху*, у залежності (dependable), у підзалежності тощо.
-**FastAPI** знає, як діяти в кожному випадку і як повторно використовувати один і той самий об'єкт, щоб усі фонові задачі були об’єднані та виконувалися у фоновому режимі після завершення основного запиту.
+**FastAPI** знає, як діяти в кожному випадку і як повторно використовувати один і той самий об'єкт, щоб усі фонові задачі були об’єднані та виконувалися у фоновому режимі після завершення основного запиту:
{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *}
У цьому прикладі повідомлення будуть записані у файл `log.txt` *після* того, як відповідь буде надіслана.
-Якщо у запиті був переданий query-параметр, він буде записаний у лог у фоновій задачі.
+Якщо у запиті був переданий query, він буде записаний у лог у фоновій задачі.
-А потім інша фонова задача, яка створюється у *функції операції шляху*, запише повідомлення з використанням path параметра `email`.
+А потім інша фонова задача, згенерована у *функції операції шляху*, запише повідомлення з використанням path параметра `email`.
-## Технічні деталі
+## Технічні деталі { #technical-details }
Клас `BackgroundTasks` походить безпосередньо з `starlette.background`.
-Він імпортується безпосередньо у FastAPI, щоб Ви могли використовувати його з `fastapi` і випадково не імпортували `BackgroundTask` (без s в кінці) з `starlette.background`.
+Він імпортується/включається безпосередньо у FastAPI, щоб ви могли імпортувати його з `fastapi` і випадково не імпортували альтернативний `BackgroundTask` (без `s` в кінці) з `starlette.background`.
Якщо використовувати лише `BackgroundTasks` (а не `BackgroundTask`), то його можна передавати як параметр у *функції операції шляху*, і **FastAPI** подбає про все інше, так само як і про використання об'єкта `Request`.
-Також можна використовувати `BackgroundTask` окремо в FastAPI, але для цього Вам доведеться створити об'єкт у коді та повернути Starlette `Response`, включаючи його.
+Також можна використовувати `BackgroundTask` окремо в FastAPI, але для цього вам доведеться створити об'єкт у коді та повернути Starlette `Response`, включаючи його.
-Детальніше можна почитати в офіційній документації Starlette про фонові задачі .
+Детальніше можна почитати в офіційній документації Starlette про Background Tasks.
-## Застереження
+## Застереження { #caveat }
-Якщо Вам потрібно виконувати складні фонові обчислення, і при цьому нема потреби запускати їх у тому ж процесі (наприклад, не потрібно спільного доступу до пам’яті чи змінних), можливо, варто скористатися більш потужними інструментами, такими як Celery.
+Якщо вам потрібно виконувати складні фонові обчислення, і при цьому нема потреби запускати їх у тому ж процесі (наприклад, не потрібно спільного доступу до пам’яті чи змінних), можливо, варто скористатися більш потужними інструментами, такими як Celery.
-Такі інструменти зазвичай потребують складнішої конфігурації та менеджера черги повідомлень/завдань, наприклад, RabbitMQ або Redis. Однак вони дозволяють виконувати фонові задачі в кількох процесах і навіть на кількох серверах.
+Такі інструменти зазвичай потребують складнішої конфігурації та менеджера черги повідомлень/завдань, наприклад, RabbitMQ або Redis. Однак вони дозволяють виконувати фонові задачі в кількох процесах і особливо — на кількох серверах.
-Якщо ж Вам потрібно отримати доступ до змінних і об’єктів із тієї ж **FastAPI** - програми або виконувати невеликі фонові завдання (наприклад, надсилати сповіщення електронною поштою), достатньо просто використовувати `BackgroundTasks`.
+Якщо ж вам потрібно отримати доступ до змінних і об’єктів із тієї ж **FastAPI**-програми або виконувати невеликі фонові завдання (наприклад, надсилати email-сповіщення), достатньо просто використовувати `BackgroundTasks`.
-## Підсумок
+## Підсумок { #recap }
-Імпортуйте та використовуйте `BackgroundTasks` як параметр у *функціях операції шляху* та залежностях, щоб додавати фонові задачі.
+Імпортуйте та використовуйте `BackgroundTasks` як параметри у *функціях операції шляху* та залежностях, щоб додавати фонові задачі.
diff --git a/docs/uk/docs/tutorial/body-fields.md b/docs/uk/docs/tutorial/body-fields.md
index 7ddd9d104b..70d94f3d6e 100644
--- a/docs/uk/docs/tutorial/body-fields.md
+++ b/docs/uk/docs/tutorial/body-fields.md
@@ -1,60 +1,61 @@
-# Тіло - Поля
+# Тіло — Поля { #body-fields }
-Так само як ви можете визначати додаткову валідацію та метадані у параметрах *функції обробки шляху* за допомогою `Query`, `Path` та `Body`, ви можете визначати валідацію та метадані всередині моделей Pydantic за допомогою `Field` від Pydantic.
+Так само як ви можете оголошувати додаткову валідацію та метадані в параметрах *функції операції шляху* за допомогою `Query`, `Path` та `Body`, ви можете оголошувати валідацію та метадані всередині моделей Pydantic, використовуючи `Field` від Pydantic.
-## Імпорт `Field`
+## Імпорт `Field` { #import-field }
Спочатку вам потрібно імпортувати це:
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *}
-/// warning
-Зверніть увагу, що `Field` імпортується прямо з `pydantic`, а не з `fastapi`, як всі інші (`Query`, `Path`, `Body` тощо).
+/// warning | Попередження
+
+Зверніть увагу, що `Field` імпортується безпосередньо з `pydantic`, а не з `fastapi`, як усе інше (`Query`, `Path`, `Body` тощо).
///
-## Оголошення атрибутів моделі
+## Оголошення атрибутів моделі { #declare-model-attributes }
-Ви можете використовувати `Field` з атрибутами моделі:
+Потім ви можете використовувати `Field` з атрибутами моделі:
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *}
-`Field` працює так само, як `Query`, `Path` і `Body`, у нього такі самі параметри тощо.
+`Field` працює так само, як `Query`, `Path` і `Body`, має ті самі параметри тощо.
/// note | Технічні деталі
-Насправді, `Query`, `Path` та інші, що ви побачите далі, створюють об'єкти підкласів загального класу `Param`, котрий сам є підкласом класу `FieldInfo` з Pydantic.
+Насправді `Query`, `Path` та інші, які ви побачите далі, створюють об'єкти підкласів спільного класу `Param`, який сам є підкласом класу `FieldInfo` з Pydantic.
І `Field` від Pydantic також повертає екземпляр `FieldInfo`.
-`Body` також безпосередньо повертає об'єкти підкласу `FieldInfo`. І є інші підкласи, які ви побачите пізніше, що є підкласами класу Body.
+`Body` також безпосередньо повертає об'єкти підкласу `FieldInfo`. І є інші, які ви побачите пізніше, що є підкласами класу `Body`.
-Пам'ятайте, що коли ви імпортуєте 'Query', 'Path' та інше з 'fastapi', вони фактично є функціями, які повертають спеціальні класи.
+Пам'ятайте, що коли ви імпортуєте `Query`, `Path` та інші з `fastapi`, це фактично функції, які повертають спеціальні класи.
///
-/// tip
+/// tip | Порада
-Зверніть увагу, що кожен атрибут моделі із типом, значенням за замовчуванням та `Field` має ту саму структуру, що й параметр *функції обробки шляху*, з `Field` замість `Path`, `Query` і `Body`.
+Зверніть увагу, що кожен атрибут моделі з типом, значенням за замовчуванням і `Field` має ту саму структуру, що й параметр *функції операції шляху*, з `Field` замість `Path`, `Query` і `Body`.
///
-## Додавання додаткової інформації
+## Додавання додаткової інформації { #add-extra-information }
-Ви можете визначити додаткову інформацію у `Field`, `Query`, `Body` тощо. І вона буде включена у згенеровану JSON схему.
+Ви можете оголошувати додаткову інформацію в `Field`, `Query`, `Body` тощо. І вона буде включена до згенерованої JSON Schema.
-Ви дізнаєтеся більше про додавання додаткової інформації пізніше у документації, коли вивчатимете визначення прикладів.
+Ви дізнаєтеся більше про додавання додаткової інформації пізніше в документації, коли вивчатимете, як оголошувати приклади.
-/// warning
+/// warning | Попередження
-Додаткові ключі, передані в `Field`, також будуть присутні у згенерованій схемі OpenAPI для вашого додатка.
-Оскільки ці ключі не обов'язково можуть бути частиною специфікації OpenAPI, деякі інструменти OpenAPI, наприклад, [OpenAPI валідатор](https://validator.swagger.io/), можуть не працювати з вашою згенерованою схемою.
+Додаткові ключі, передані в `Field`, також будуть присутні в отриманій схемі OpenAPI для вашого застосунку.
+Оскільки ці ключі не обов'язково є частиною специфікації OpenAPI, деякі інструменти OpenAPI, наприклад [валідатор OpenAPI](https://validator.swagger.io/), можуть не працювати з вашою згенерованою схемою.
///
-## Підсумок
+## Підсумок { #recap }
-Ви можете використовувати `Field` з Pydantic для визначення додаткових перевірок та метаданих для атрибутів моделі.
+Ви можете використовувати `Field` від Pydantic, щоб оголошувати додаткову валідацію та метадані для атрибутів моделі.
-Ви також можете використовувати додаткові іменовані аргументи для передачі додаткових метаданих JSON схеми.
+Ви також можете використовувати додаткові keyword arguments, щоб передавати додаткові метадані JSON Schema.
diff --git a/docs/uk/docs/tutorial/body-multiple-params.md b/docs/uk/docs/tutorial/body-multiple-params.md
index e2acf8a703..dc9a768c35 100644
--- a/docs/uk/docs/tutorial/body-multiple-params.md
+++ b/docs/uk/docs/tutorial/body-multiple-params.md
@@ -1,24 +1,24 @@
-# Тіло запиту - Декілька параметрів
+# Тіло - Декілька параметрів { #body-multiple-parameters }
-Тепер, коли ми розглянули використання `Path` та `Query`, розгляньмо більш просунуті способи оголошення тіла запиту в **FastAPI**.
+Тепер, коли ми побачили, як використовувати `Path` і `Query`, розгляньмо більш просунуті варіанти оголошення тіла запиту.
-## Змішування `Path`, `Query` та параметрів тіла запиту
+## Змішування `Path`, `Query` та параметрів тіла { #mix-path-query-and-body-parameters }
-По-перше, звісно, Ви можете вільно змішувати оголошення параметрів `Path`, `Query` та тіла запиту, і **FastAPI** правильно їх обробить.
+По-перше, звісно, ви можете вільно змішувати оголошення параметрів `Path`, `Query` та тіла запиту, і **FastAPI** знатиме, що робити.
-Також Ви можете оголосити параметри тіла як необов’язкові, встановивши для них значення за замовчуванням `None`:
+Також ви можете оголошувати параметри тіла як необов’язкові, встановивши для них значення за замовчуванням `None`:
{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *}
/// note | Примітка
-Зверніть увагу, що в цьому випадку параметр `item`, який береться з тіла запиту, є необов'язковим, оскільки має значення за замовчуванням `None`.
+Зверніть увагу, що в цьому випадку параметр `item`, який береться з тіла, є необов'язковим. Оскільки має значення за замовчуванням `None`.
///
-## Декілька параметрів тіла запиту
+## Декілька параметрів тіла { #multiple-body-parameters }
-У попередньому прикладі *операція шляху* очікувала JSON з атрибутами `Item`, наприклад:
+У попередньому прикладі *операції шляху* очікували б JSON-тіло з атрибутами `Item`, наприклад:
```JSON
{
@@ -28,13 +28,15 @@
"tax": 3.2
}
```
-Але Ви також можете оголосити декілька параметрів тіла, наприклад `item` та `user`:
+
+Але ви також можете оголосити декілька параметрів тіла, наприклад `item` та `user`:
{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *}
-У цьому випадку **FastAPI** розпізнає, що є кілька параметрів тіла (два параметри є моделями Pydantic).
-Тому він використає назви параметрів як ключі (назви полів) у тілі запиту, очікуючи:
+У цьому випадку **FastAPI** помітить, що у функції є більше ніж один параметр тіла (є два параметри, які є моделями Pydantic).
+
+Тож він використає назви параметрів як ключі (назви полів) у тілі та очікуватиме тіло такого вигляду:
```JSON
{
@@ -53,27 +55,28 @@
/// note | Примітка
-Зверніть увагу, що хоча `item` оголошено, так само як і раніше, тепер він очікується в тілі під ключем `item`.
+Зверніть увагу, що хоча `item` оголошено так само, як і раніше, тепер він очікується всередині тіла з ключем `item`.
///
-**FastAPI** автоматично конвертує дані із запиту таким чином, щоб параметр `item` отримав свій вміст, і те ж саме стосується `user`.
+**FastAPI** виконає автоматичне перетворення із запиту, щоб параметр `item` отримав свій конкретний вміст, і те ж саме для `user`.
-Він виконає валідацію складених даних і задокументує їх відповідним чином у схемі OpenAPI та в автоматичній документації.
+Він виконає валідацію складених даних і задокументує це таким чином у схемі OpenAPI та в автоматичній документації.
-## Одиничні значення в тілі запиту
+## Одиничні значення в тілі { #singular-values-in-body }
Так само як є `Query` і `Path` для визначення додаткових даних для параметрів запиту та шляху, **FastAPI** надає еквівалентний `Body`.
-Наприклад, розширюючи попередню модель, Ви можете вирішити додати ще один ключ `importance` в те ж саме тіло запиту разом із `item` і `user`.
+Наприклад, розширивши попередню модель, ви можете вирішити додати ще один ключ `importance` у те саме тіло, окрім `item` і `user`.
-Якщо Ви оголосите його як є, то, оскільки це одиничне значення, **FastAPI** припускатиме, що це параметр запиту (query parameter).
+Якщо оголосити його як є, оскільки це одиничне значення, **FastAPI** припустить, що це параметр запиту.
-Але Ви можете вказати **FastAPI** обробляти його як інший ключ тіла (body key), використовуючи `Body`:
+Але ви можете вказати **FastAPI** обробляти його як інший ключ тіла, використовуючи `Body`:
{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *}
-У цьому випадку **FastAPI** очікуватиме тіло запиту у такому вигляді:
+
+У цьому випадку **FastAPI** очікуватиме тіло такого вигляду:
```JSON
{
@@ -90,19 +93,20 @@
"importance": 5
}
```
-Знову ж таки, **FastAPI** конвертуватиме типи даних, перевірятиме їх, створюватиме документацію тощо.
-## Декілька body та query параметрів
+Знову ж таки, він перетворюватиме типи даних, перевірятиме, документуватиме тощо.
-Звісно, Ви можете оголошувати додаткові query параметри запиту, коли це необхідно, на додаток до будь-яких параметрів тіла запиту.
+## Декілька параметрів тіла та query { #multiple-body-params-and-query }
-Оскільки за замовчуванням окремі значення інтерпретуються як параметри запиту, Вам не потрібно явно додавати `Query`, можна просто використати:
+Звісно, ви також можете оголошувати додаткові query параметри щоразу, коли це потрібно, додатково до будь-яких параметрів тіла.
+
+Оскільки за замовчуванням одиничні значення інтерпретуються як параметри запиту, вам не потрібно явно додавати `Query`, ви можете просто зробити:
```Python
q: Union[str, None] = None
```
-Або в Python 3.10 та вище:
+Або в Python 3.10 і вище:
```Python
q: str | None = None
@@ -115,17 +119,17 @@ q: str | None = None
/// info | Інформація
-`Body` також має ті самі додаткові параметри валідації та метаданих, що й `Query`, `Path` та інші, які Ви побачите пізніше.
+`Body` також має всі ті самі додаткові параметри валідації та метаданих, що й `Query`, `Path` та інші, які ви побачите пізніше.
///
-## Вкладений поодинокий параметр тіла запиту
+## Вбудувати один параметр тіла { #embed-a-single-body-parameter }
-Припустимо, у вас є лише один параметр тіла запиту `item` з моделі Pydantic `Item`.
+Скажімо, у вас є лише один параметр тіла `item` з моделі Pydantic `Item`.
-За замовчуванням **FastAPI** очікуватиме, що тіло запиту міститиме вміст безпосередньо.
+За замовчуванням **FastAPI** очікуватиме його тіло безпосередньо.
-Але якщо Ви хочете, щоб він очікував JSON з ключем `item`, а всередині — вміст моделі (так, як це відбувається при оголошенні додаткових параметрів тіла), Ви можете використати спеціальний параметр `Body` — `embed`:
+Але якщо ви хочете, щоб він очікував JSON з ключем `item`, а всередині нього — вміст моделі, як це відбувається, коли ви оголошуєте додаткові параметри тіла, ви можете використати спеціальний параметр `Body` — `embed`:
```Python
item: Item = Body(embed=True)
@@ -135,7 +139,8 @@ item: Item = Body(embed=True)
{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *}
-У цьому випадку **FastAPI** очікуватиме тіло запиту такого вигляду:
+
+У цьому випадку **FastAPI** очікуватиме тіло такого вигляду:
```JSON hl_lines="2"
{
@@ -159,12 +164,12 @@ item: Item = Body(embed=True)
}
```
-## Підсумок
+## Підсумок { #recap }
-Ви можете додавати кілька параметрів тіла до Вашої *функції операції шляху* (*path operation function*), навіть якщо запит може мати лише одне тіло.
+Ви можете додавати кілька параметрів тіла до вашої *функції операції шляху*, навіть якщо запит може мати лише одне тіло.
-Але **FastAPI** обробить це, надасть Вам потрібні дані у функції, перевірить їх та задокументує коректну схему в *операції шляху*.
+Але **FastAPI** обробить це, надасть вам правильні дані у функції та перевірить і задокументує правильну схему в *операції шляху*.
-Також Ви можете оголошувати окремі значення, які будуть отримані як частина тіла запиту.
+Також ви можете оголошувати одиничні значення, щоб отримувати їх як частину тіла.
-Крім того, Ви можете вказати **FastAPI** вбудовувати тіло в ключ, навіть якщо оголошено лише один параметр.
+І ви можете вказати **FastAPI** вбудовувати тіло в ключ, навіть коли оголошено лише один параметр.
diff --git a/docs/uk/docs/tutorial/body-nested-models.md b/docs/uk/docs/tutorial/body-nested-models.md
index abc33f2eb3..6d0669358a 100644
--- a/docs/uk/docs/tutorial/body-nested-models.md
+++ b/docs/uk/docs/tutorial/body-nested-models.md
@@ -1,8 +1,8 @@
-# Тіло запиту - Вкладені моделі
+# Тіло - Вкладені моделі { #body-nested-models }
-З **FastAPI** Ви можете визначати, перевіряти, документувати та використовувати моделі, які можуть бути вкладені на будь-яку глибину (завдяки Pydantic).
+З **FastAPI** ви можете визначати, перевіряти, документувати та використовувати моделі, які можуть бути вкладені на будь-яку глибину (завдяки Pydantic).
-## Поля списку
+## Поля списку { #list-fields }
Ви можете визначити атрибут як підтип. Наприклад, Python-список (`list`):
@@ -10,47 +10,28 @@
Це зробить `tags` списком, хоча не визначається тип елементів списку.
-## Поля списку з параметром типу
+## Поля списку з параметром типу { #list-fields-with-type-parameter }
-Але Python має специфічний спосіб оголошення списків з внутрішніми типами або "параметрами типу":
-### Імпортуємо `List` з модуля typing
+Але Python має специфічний спосіб оголошення списків з внутрішніми типами або «параметрами типу»:
-У Python 3.9 і вище можна використовувати стандартний `list` для оголошення таких типів, як ми побачимо нижче. 💡
+### Оголошення `list` з параметром типу { #declare-a-list-with-a-type-parameter }
-Але в Python версії до 3.9 (від 3.6 і вище) спочатку потрібно імпортувати `List` з модуля стандартної бібліотеки Python `typing`:
-
-{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
-
-### Оголошення `list` з параметром типу
-
-Щоб оголосити типи з параметрами типу (внутрішніми типами), такими як `list`, `dict`, `tuple`:
-
-* Якщо Ви використовуєте версію Python до 3.9, імпортуйте їх відповідну версію з модуля `typing`.
-* Передайте внутрішні типи як "параметри типу", використовуючи квадратні дужки: `[` and `]`.
-
-У Python 3.9 це буде виглядати так:
+Щоб оголосити типи з параметрами типу (внутрішніми типами), такими як `list`, `dict`, `tuple`,
+передайте внутрішні тип(и) як «параметри типу», використовуючи квадратні дужки: `[` and `]`
```Python
my_list: list[str]
```
-У версіях Python до 3.9 це виглядає так:
-
-```Python
-from typing import List
-
-my_list: List[str]
-```
-
Це стандартний синтаксис Python для оголошення типів.
Використовуйте той самий стандартний синтаксис для атрибутів моделей з внутрішніми типами.
-Отже, у нашому прикладі, ми можемо зробити `tags` саме "списком рядків":
+Отже, у нашому прикладі, ми можемо зробити `tags` саме «списком рядків»:
{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}
-## Типи множин
+## Типи множин { #set-types }
Але потім ми подумали, що теги не повинні повторюватися, вони, ймовірно, повинні бути унікальними рядками.
@@ -60,29 +41,29 @@ my_list: List[str]
{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *}
-Навіть якщо Ви отримаєте запит з дубльованими даними, він буде перетворений у множину унікальних елементів.
+Навіть якщо ви отримаєте запит з дубльованими даними, він буде перетворений у множину унікальних елементів.
-І коли Ви будете виводити ці дані, навіть якщо джерело містить дублікати, вони будуть виведені як множина унікальних елементів.
+І коли ви будете виводити ці дані, навіть якщо джерело містить дублікати, вони будуть виведені як множина унікальних елементів.
І це буде анотовано/документовано відповідно.
-## Вкладені моделі
+## Вкладені моделі { #nested-models }
Кожен атрибут моделі Pydantic має тип.
Але цей тип сам може бути іншою моделлю Pydantic.
-Отже, Ви можете оголосити глибоко вкладені JSON "об'єкти" з конкретними іменами атрибутів, типами та перевірками.
+Отже, ви можете оголосити глибоко вкладені JSON «об'єкти» з конкретними іменами атрибутів, типами та перевірками.
Усе це, вкладене без обмежень.
-### Визначення підмоделі
+### Визначення підмоделі { #define-a-submodel }
Наприклад, ми можемо визначити модель `Image`:
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *}
-### Використання підмоделі як типу
+### Використання підмоделі як типу { #use-the-submodel-as-a-type }
А потім ми можемо використовувати її як тип атрибута:
@@ -104,16 +85,16 @@ my_list: List[str]
}
```
-Завдяки такій декларації у **FastAPI** Ви отримуєте:
+Завдяки такій декларації у **FastAPI** ви отримуєте:
* Підтримку в редакторі (автозавершення тощо), навіть для вкладених моделей
* Конвертацію даних
* Валідацію даних
* Автоматичну документацію
-## Спеціальні типи та валідація
+## Спеціальні типи та валідація { #special-types-and-validation }
-Окрім звичайних типів, таких як `str`, `int`, `float`, та ін. Ви можете використовувати складніші типи, які наслідують `str`.
+Окрім звичайних типів, таких як `str`, `int`, `float`, та ін. ви можете використовувати складніші типи, які наслідують `str`.
Щоб побачити всі доступні варіанти, ознайомтеся з оглядом типів у Pydantic. Деякі приклади будуть у наступних розділах.
@@ -123,9 +104,9 @@ my_list: List[str]
Рядок буде перевірено як дійсну URL-адресу і задокументовано в JSON Schema / OpenAPI як URL.
-## Атрибути зі списками підмоделей
+## Атрибути зі списками підмоделей { #attributes-with-lists-of-submodels }
-У Pydantic Ви можете використовувати моделі як підтипи для `list`, `set` тощо:
+У Pydantic ви можете використовувати моделі як підтипи для `list`, `set` тощо:
{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *}
@@ -161,7 +142,7 @@ my_list: List[str]
///
-## Глибоко вкладені моделі
+## Глибоко вкладені моделі { #deeply-nested-models }
Ви можете визначати вкладені моделі довільної глибини:
@@ -173,14 +154,9 @@ my_list: List[str]
///
-## Тіла запитів, що складаються зі списків
+## Тіла запитів, що складаються зі списків { #bodies-of-pure-lists }
-Якщо верхній рівень JSON тіла, яке Ви очікуєте, є JSON `масивом` (у Python — `list`), Ви можете оголосити тип у параметрі функції, як і в моделях Pydantic:
-
-```Python
-images: List[Image]
-```
-або в Python 3.9 і вище:
+Якщо верхній рівень JSON тіла, яке ви очікуєте, є JSON `масивом` (у Python — `list`), ви можете оголосити тип у параметрі функції, як і в моделях Pydantic:
```Python
images: list[Image]
@@ -190,7 +166,7 @@ images: list[Image]
{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}
-## Підтримка в редакторі всюди
+## Підтримка в редакторі всюди { #editor-support-everywhere }
Ви отримаєте підтримку в редакторі всюди.
@@ -200,23 +176,23 @@ images: list[Image]
Ви не змогли б отримати таку підтримку в редакторі, якби працювали напряму зі `dict`, а не з моделями Pydantic.
-Але Вам не потрібно турбуватися про це: вхідні dict'и автоматично конвертуються, а вихідні дані автоматично перетворюються в JSON.
+Але вам не потрібно турбуватися про це: вхідні dict'и автоматично конвертуються, а вихідні дані автоматично перетворюються в JSON.
-## Тіла з довільними `dict`
+## Тіла з довільними `dict` { #bodies-of-arbitrary-dicts }
Ви також можете оголосити тіло як `dict` з ключами одного типу та значеннями іншого типу.
-Це корисно, якщо Ви не знаєте наперед, які імена полів будуть дійсними (як у випадку з моделями Pydantic).
+Це корисно, якщо ви не знаєте наперед, які імена полів будуть дійсними (як у випадку з моделями Pydantic).
-Це буде корисно, якщо Ви хочете приймати ключі, які заздалегідь невідомі.
+Це буде корисно, якщо ви хочете приймати ключі, які заздалегідь невідомі.
---
-Це також зручно, якщо Ви хочете мати ключі іншого типу (наприклад, `int`).
+Це також зручно, якщо ви хочете мати ключі іншого типу (наприклад, `int`).
Ось що ми розглянемо далі.
-У цьому випадку Ви можете приймати будь-який `dict`, якщо його ключі — це `int`, а значення — `float`:
+У цьому випадку ви можете приймати будь-який `dict`, якщо його ключі — це `int`, а значення — `float`:
{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}
@@ -228,18 +204,18 @@ images: list[Image]
Це означає, що навіть якщо клієнти вашого API надсилатимуть ключі у вигляді рядків, якщо вони містять цілі числа, Pydantic конвертує їх і проведе валідацію.
-Тобто `dict`, який Ви отримаєте як `weights`, матиме ключі типу `int` та значення типу `float`.
+Тобто `dict`, який ви отримаєте як `weights`, матиме ключі типу `int` та значення типу `float`.
///
-## Підсумок
+## Підсумок { #recap }
-З **FastAPI** Ви маєте максимальну гнучкість завдяки моделям Pydantic, зберігаючи при цьому код простим, коротким та елегантним.
+З **FastAPI** ви маєте максимальну гнучкість завдяки моделям Pydantic, зберігаючи при цьому код простим, коротким та елегантним.
А також отримуєте всі переваги:
* Підтримка в редакторі (автодоповнення всюди!)
* Конвертація даних (парсинг/сериалізація)
-* Валідація даних
+* Валідацію даних
* Документація схем
* Автоматичне створення документації
diff --git a/docs/uk/docs/tutorial/body-updates.md b/docs/uk/docs/tutorial/body-updates.md
index e78b5a5bf3..2ae68291ca 100644
--- a/docs/uk/docs/tutorial/body-updates.md
+++ b/docs/uk/docs/tutorial/body-updates.md
@@ -1,8 +1,8 @@
-# Тіло – Оновлення
+# Тіло — Оновлення { #body-updates }
-## Оновлення з використанням `PUT`
+## Оновлення із заміною за допомогою `PUT` { #update-replacing-with-put }
-Щоб оновити елемент, Ви можете використати HTTP `PUT` операцію.
+Щоб оновити елемент, ви можете використати HTTP `PUT` операцію.
Ви можете використати `jsonable_encoder`, щоб перетворити вхідні дані на такі, які можна зберігати як JSON (наприклад, у NoSQL базі даних). Наприклад, перетворюючи `datetime` у `str`.
@@ -10,7 +10,7 @@
`PUT` використовується для отримання даних, які мають замінити чинні дані.
-### Попередження про заміну
+### Попередження про заміну { #warning-about-replacing }
Це означає, що якщо Ви хочете оновити елемент `bar`, використовуючи `PUT` з тілом:
@@ -26,7 +26,7 @@
І дані будуть збережені з цим "новим" значенням `tax` = `10.5`.
-## Часткові оновлення з `PATCH`
+## Часткові оновлення з `PATCH` { #partial-updates-with-patch }
Ви також можете використовувати операцію HTTP `PATCH` для *часткового* оновлення даних.
@@ -34,53 +34,37 @@
/// note | Примітка
-`PATCH` менш відомий і рідше використовується, ніж `PUT`.
+`PATCH` менш поширений і менш відомий, ніж `PUT`.
І багато команд використовують лише `PUT`, навіть для часткових оновлень.
-Ви **вільні** використовувати їх так, як хочете, **FastAPI** не накладає обмежень.
+Ви **вільні** використовувати їх так, як хочете, **FastAPI** не накладає жодних обмежень.
-Але цей посібник показує Вам більш-менш як їх задумано використовувати.
+Але цей посібник показує вам, більш-менш, як їх задумано використовувати.
///
-### Використання параметра `exclude_unset` у Pydantic
+### Використання параметра `exclude_unset` у Pydantic { #using-pydantics-exclude-unset-parameter }
-Якщо Ви хочете отримати часткові оновлення, дуже зручно використовувати параметр `exclude_unset` у методі `.model_dump()` моделі Pydantic.
+Якщо Ви хочете отримувати часткові оновлення, дуже корисно використовувати параметр `exclude_unset` у `.model_dump()` моделі Pydantic.
Наприклад: `item.model_dump(exclude_unset=True)`.
-/// info | Інформація
+Це згенерує `dict` лише з тими даними, які були встановлені під час створення моделі `item`, виключаючи значення за замовчуванням.
-У Pydantic v1 цей метод називався `.dict()`, він був застарілий (але все ще підтримується) у Pydantic v2, і був перейменований у `.model_dump()`.
-
-Приклади тут використовують `.dict()` для сумісності з Pydantic v1, але Вам слід використовувати `.model_dump()`, якщо можете використовувати Pydantic v2.
-
-///
-
-Це створить `dict` лише з тими даними, які були явно встановлені під час створення моделі `item`, виключаючи значення за замовчуванням.
-
-Тоді Ви можете використовувати це, щоб створити `dict` лише з даними, які були встановлені (надіслані у запиті), пропускаючи значення за замовчуванням:
+Тоді Ви можете використовувати це, щоб згенерувати `dict` лише з даними, які були встановлені (надіслані у запиті), пропускаючи значення за замовчуванням:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *}
-### Використання параметра `update` у Pydantic
+### Використання параметра `update` у Pydantic { #using-pydantics-update-parameter }
-Тепер Ви можете створити копію наявної моделі за допомогою `.model_copy()`, і передати параметр `update` з `dict` , який містить дані для оновлення.
-
-/// info | Інформація
-
-У Pydantic v1 метод називався `.copy()`, він був застарілий (але все ще підтримується) у Pydantic v2, і був перейменований у `.model_copy()`.
-
-Приклади тут використовують `.copy()` для сумісності з Pydantic v1, але якщо Ви можете використовувати Pydantic v2 — Вам слід використовувати `.model_copy()` замість цього.
-
-///
+Тепер Ви можете створити копію наявної моделі за допомогою `.model_copy()`, і передати параметр `update` з `dict`, який містить дані для оновлення.
Наприклад: `stored_item_model.model_copy(update=update_data)`:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
-### Підсумок часткових оновлень
+### Підсумок часткових оновлень { #partial-updates-recap }
У підсумку, щоб застосувати часткові оновлення, Ви:
@@ -101,7 +85,7 @@
Насправді Ви можете використовувати цю саму техніку і з операцією HTTP `PUT`.
-Але приклад тут використовує `PATCH`, тому що він був створений саме для таких випадків.
+Але приклад тут використовує `PATCH`, тому що він був створений для таких випадків.
///
@@ -109,7 +93,7 @@
Зверніть увагу, що модель запиту все ще проходить валідацію.
-Тож, якщо Ви хочете отримувати часткові оновлення, які можуть не містити жодного атрибута, Вам потрібно мати модель, де всі атрибути позначені як необов’язкові (зі значеннями за замовчуванням або `None`).
+Тож, якщо Ви хочете отримувати часткові оновлення, які можуть пропускати всі атрибути, Вам потрібно мати модель, де всі атрибути позначені як необов’язкові (зі значеннями за замовчуванням або `None`).
Щоб розрізняти моделі з усіма необов’язковими значеннями для **оновлення** і моделі з обов’язковими значеннями для **створення**, Ви можете скористатись ідеями, описаними у [Додаткові моделі](extra-models.md){.internal-link target=_blank}.
diff --git a/docs/uk/docs/tutorial/body.md b/docs/uk/docs/tutorial/body.md
index 38fed7bb8d..ca1f308ab5 100644
--- a/docs/uk/docs/tutorial/body.md
+++ b/docs/uk/docs/tutorial/body.md
@@ -1,14 +1,14 @@
-# Тіло запиту
+# Тіло запиту { #request-body }
Коли вам потрібно надіслати дані з клієнта (скажімо, браузера) до вашого API, ви надсилаєте їх як **тіло запиту**.
Тіло **запиту** — це дані, надіслані клієнтом до вашого API. Тіло **відповіді** — це дані, які ваш API надсилає клієнту.
-Ваш API майже завжди має надсилати тіло **відповіді**. Але клієнтам не обов’язково потрібно постійно надсилати тіла **запитів**.
+Ваш API майже завжди має надсилати тіло **відповіді**. Але клієнтам не обов’язково потрібно постійно надсилати тіла **запитів** — інколи вони лише запитують шлях, можливо з деякими параметрами запиту, але не надсилають тіло.
Щоб оголосити тіло **запиту**, ви використовуєте Pydantic моделі з усією їх потужністю та перевагами.
-/// info
+/// info | Інформація
Щоб надіслати дані, ви повинні використовувати один із: `POST` (більш поширений), `PUT`, `DELETE` або `PATCH`.
@@ -18,21 +18,22 @@
///
-## Імпортуйте `BaseModel` від Pydantic
+## Імпортуйте `BaseModel` від Pydantic { #import-pydantics-basemodel }
Спочатку вам потрібно імпортувати `BaseModel` з `pydantic`:
-{* ../../docs_src/body/tutorial001.py hl[4] *}
+{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
-## Створіть свою модель даних
+## Створіть свою модель даних { #create-your-data-model }
Потім ви оголошуєте свою модель даних як клас, який успадковується від `BaseModel`.
Використовуйте стандартні типи Python для всіх атрибутів:
-{* ../../docs_src/body/tutorial001.py hl[7:11] *}
+{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
-Так само, як і при оголошенні параметрів запиту, коли атрибут моделі має значення за замовчуванням, він не є обов’язковим. В іншому випадку це потрібно. Використовуйте `None`, щоб зробити його необов'язковим.
+
+Так само, як і при оголошенні параметрів запиту, коли атрибут моделі має значення за замовчуванням, він не є обов’язковим. В іншому випадку це потрібно. Використовуйте `None`, щоб зробити його просто необов'язковим.
Наприклад, ця модель вище оголошує JSON "`об'єкт`" (або Python `dict`), як:
@@ -54,15 +55,15 @@
}
```
-## Оголоси її як параметр
+## Оголосіть її як параметр { #declare-it-as-a-parameter }
Щоб додати модель даних до вашої *операції шляху*, оголосіть її так само, як ви оголосили параметри шляху та запиту:
-{* ../../docs_src/body/tutorial001.py hl[18] *}
+{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
...і вкажіть її тип як модель, яку ви створили, `Item`.
-## Результати
+## Результати { #results }
Лише з цим оголошенням типу Python **FastAPI** буде:
@@ -73,9 +74,9 @@
* Надавати отримані дані у параметрі `item`.
* Оскільки ви оголосили його у функції як тип `Item`, ви також матимете всю підтримку редактора (автозаповнення, тощо) для всіх атрибутів та їх типів.
* Генерувати JSON Schema визначення для вашої моделі, ви також можете використовувати їх де завгодно, якщо це має сенс для вашого проекту.
-* Ці схеми будуть частиною згенерованої схеми OpenAPI і використовуватимуться автоматичною документацією інтерфейсу користувача.
+* Ці схеми будуть частиною згенерованої схеми OpenAPI і використовуватимуться автоматичною документацією UIs.
-## Автоматична документація
+## Автоматична документація { #automatic-docs }
Схеми JSON ваших моделей будуть частиною вашої схеми, згенерованої OpenAPI, і будуть показані в інтерактивній API документації:
@@ -85,7 +86,7 @@
-## Підтримка редактора
+## Підтримка редактора { #editor-support }
У вашому редакторі, всередині вашої функції, ви будете отримувати підказки типу та завершення скрізь (це б не сталося, якби ви отримали `dict` замість моделі Pydantic):
@@ -107,7 +108,7 @@
-/// tip
+/// tip | Порада
Якщо ви використовуєте PyCharm як ваш редактор, ви можете використати Pydantic PyCharm Plugin.
@@ -121,42 +122,45 @@
///
-## Використовуйте модель
+## Використовуйте модель { #use-the-model }
Усередині функції ви можете отримати прямий доступ до всіх атрибутів об’єкта моделі:
-{* ../../docs_src/body/tutorial002.py hl[21] *}
+{* ../../docs_src/body/tutorial002_py310.py *}
-## Тіло запиту + параметри шляху
+## Тіло запиту + параметри шляху { #request-body-path-parameters }
Ви можете одночасно оголошувати параметри шляху та тіло запиту.
**FastAPI** розпізнає, що параметри функції, які відповідають параметрам шляху, мають бути **взяті з шляху**, а параметри функції, які оголошуються як моделі Pydantic, **взяті з тіла запиту**.
-{* ../../docs_src/body/tutorial003.py hl[17:18] *}
+{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
-## Тіло запиту + шлях + параметри запиту
+
+## Тіло запиту + шлях + параметри запиту { #request-body-path-query-parameters }
Ви також можете оголосити параметри **тіло**, **шлях** і **запит** одночасно.
**FastAPI** розпізнає кожен з них і візьме дані з потрібного місця.
-{* ../../docs_src/body/tutorial004.py hl[18] *}
+{* ../../docs_src/body/tutorial004_py310.py hl[16] *}
Параметри функції будуть розпізнаватися наступним чином:
* Якщо параметр також оголошено в **шляху**, він використовуватиметься як параметр шляху.
* Якщо параметр має **сингулярний тип** (наприклад, `int`, `float`, `str`, `bool` тощо), він буде інтерпретуватися як параметр **запиту**.
-* Якщо параметр оголошується як тип **Pydantic моделі**, він інтерпретується як **тіло** запиту.
+* Якщо параметр оголошується як тип **Pydantic моделі**, він інтерпретується як **тіло** **запиту**.
-/// note
+/// note | Примітка
-FastAPI буде знати, що значення "q" не є обов'язковим через значення за замовчуванням "= None".
+FastAPI буде знати, що значення `q` не є обов'язковим через значення за замовчуванням `= None`.
-`Optional` у `Optional[str]` не використовується FastAPI, але дозволить вашому редактору надати вам кращу підтримку та виявляти помилки.
+`str | None` (Python 3.10+) або `Union` у `Union[str, None]` (Python 3.9+) FastAPI не використовує, щоб визначити, що значення не є обов’язковим — він знатиме, що воно не є обов’язковим, тому що має значення за замовчуванням `= None`.
+
+Але додавання анотацій типів дозволить вашому редактору надати вам кращу підтримку та виявляти помилки.
///
-## Без Pydantic
+## Без Pydantic { #without-pydantic }
-Якщо ви не хочете використовувати моделі Pydantic, ви також можете використовувати параметри **Body**. Перегляньте документацію для [Тіло – Кілька параметрів: сингулярні значення в тілі](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
+Якщо ви не хочете використовувати моделі Pydantic, ви також можете використовувати параметри **Body**. Перегляньте документацію для [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
diff --git a/docs/uk/docs/tutorial/cookie-param-models.md b/docs/uk/docs/tutorial/cookie-param-models.md
index f070b6ac83..3c6407716e 100644
--- a/docs/uk/docs/tutorial/cookie-param-models.md
+++ b/docs/uk/docs/tutorial/cookie-param-models.md
@@ -1,32 +1,32 @@
-# Моделі для Cookie-параметрів
+# Моделі параметрів Cookie { #cookie-parameter-models }
-Якщо у Вас є група **cookies** параметрів, які пов'язані між собою, Ви можете створити **Pydantic-модель**, щоб оголосити їх. 🍪
+Якщо у Вас є група **cookies**, які пов'язані між собою, Ви можете створити **Pydantic-модель**, щоб оголосити їх. 🍪
Це дозволить Вам повторно **використовувати модель** у **різних місцях**, а також оголосити валідацію та метадані для всіх параметрів одночасно. 😎
-/// note | Нотатки
+/// note | Примітка
-Це підтримується з версії FastAPI `0.115.0`. 🤓
+Це підтримується з версії FastAPI `0.115.0`. 🤓
///
/// tip | Порада
-Ця ж техніка застосовується до `Query`, `Cookie`, та `Header`. 😎
+Ця ж техніка застосовується до `Query`, `Cookie` та `Header`. 😎
///
-## Cookie з Pydantic-моделлю
+## Cookie з Pydantic-моделлю { #cookies-with-a-pydantic-model }
-Оголосіть **cookie-параметри**, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть параметр як `Cookie`:
+Оголосіть **cookie**-параметри, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть параметр як `Cookie`:
{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *}
-**FastAPI** буде **витягувати** дані для **кожного поля** з **cookie** параметрів, отриманих у запиті, і передавати Вам Pydantic-модель, яку Ви визначили.
+**FastAPI** буде **витягувати** дані для **кожного поля** з **cookies**, отриманих у запиті, і передавати Вам Pydantic-модель, яку Ви визначили.
-## Перевірка у документації
+## Перевірка у документації { #check-the-docs }
-Ви можете побачити визначені cookie в інтерфейсі документації за адресою `/docs`:
+Ви можете побачити визначені cookies в інтерфейсі документації за адресою `/docs`:
@@ -34,29 +34,29 @@
/// info | Інформація
-Майте на увазі, що оскільки **браузери обробляють cookie** особливим чином і "за лаштунками", вони **не** дозволяють **JavaScript** легко з ними працювати.
+Майте на увазі, що оскільки **браузери обробляють cookies** особливим чином і «за лаштунками», вони **не** дозволяють **JavaScript** легко з ними працювати.
-Якщо Ви зайдете до **інтерфейсу документації API** за адресою `/docs`, Ви зможете побачити **документацію** для cookie у Ваших **операціях шляху**.
+Якщо Ви зайдете до **інтерфейсу документації API** за адресою `/docs`, Ви зможете побачити **документацію** для cookies у Ваших *операціях шляху*.
-Але навіть якщо Ви заповните дані й натиснете "Execute", оскільки інтерфейс документації працює з **JavaScript**, cookie не будуть відправлені, і Ви побачите **помилку**, ніби Ви не ввели жодних значень.
+Але навіть якщо Ви заповните дані й натиснете "Execute", оскільки інтерфейс документації працює з **JavaScript**, cookies не будуть відправлені, і Ви побачите **помилку**, ніби Ви не ввели жодних значень.
///
-## Заборона додаткових cookie
+## Заборона додаткових cookie { #forbid-extra-cookies }
-У деяких спеціальних випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** список cookie, які хочете отримувати.
+У деяких спеціальних випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** cookies, які хочете отримувати.
-Ваша API тепер має можливість контролювати власну згоду на cookie. 🤪🍪
+Ваша API тепер має можливість контролювати власну згоду на cookie. 🤪🍪
-Ви можете використовувати налаштування моделі Pydantic, щоб `заборонити` будь-які `додаткові` поля:
+Ви можете використовувати налаштування моделі Pydantic, щоб `forbid` будь-які `extra` поля:
-{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
+{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *}
-Якщо клієнт спробує надіслати якісь **додаткові cookie**, він отримає відповідь з **помилкою**.
+Якщо клієнт спробує надіслати якісь **додаткові cookies**, він отримає відповідь з **помилкою**.
-Бідні банери cookie, які так старанно намагаються отримати Вашу згоду, щоб API її відхилила. 🍪
+Бідні банери cookie, які так старанно намагаються отримати Вашу згоду, щоб API її відхилила. 🍪
-Наприклад, якщо клієнт спробує надіслати cookie `santa_tracker` зі значенням `good-list-please`, він отримає відповідь з помилкою, яка повідомить, що cookie `santa_tracker` не дозволено:
+Наприклад, якщо клієнт спробує надіслати cookie `santa_tracker` зі значенням `good-list-please`, він отримає відповідь з помилкою, яка повідомить, що `santa_tracker` cookie не дозволено:
```json
{
@@ -71,6 +71,6 @@
}
```
-## Підсумок
+## Підсумок { #summary }
-Ви можете використовувати **Pydantic-моделі** для оголошення cookie у FastAPI. 😎
+Ви можете використовувати **Pydantic-моделі** для оголошення **cookies** у **FastAPI**. 😎
diff --git a/docs/uk/docs/tutorial/cookie-params.md b/docs/uk/docs/tutorial/cookie-params.md
index b320645cb3..8a5b44e8aa 100644
--- a/docs/uk/docs/tutorial/cookie-params.md
+++ b/docs/uk/docs/tutorial/cookie-params.md
@@ -1,24 +1,25 @@
-# Параметри Cookie
+# Параметри Cookie { #cookie-parameters }
-Ви можете визначити параметри Cookie таким же чином, як визначаються параметри `Query` і `Path`.
+Ви можете визначати параметри Cookie таким же чином, як визначаються параметри `Query` і `Path`.
-## Імпорт `Cookie`
+## Імпорт `Cookie` { #import-cookie }
Спочатку імпортуйте `Cookie`:
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
-## Визначення параметрів `Cookie`
+## Визначення параметрів `Cookie` { #declare-cookie-parameters }
Потім визначте параметри cookie, використовуючи таку ж конструкцію як для `Path` і `Query`.
-Перше значення це значення за замовчуванням, ви можете також передати всі додаткові параметри валідації чи анотації:
+Ви можете визначити значення за замовчуванням, а також усі додаткові параметри валідації чи анотації:
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *}
-/// note | Технічні Деталі
+/// note | Технічні деталі
+
+`Cookie` це "сестра" класів `Path` і `Query`. Вони також наслідуються від одного спільного класу `Param`.
-`Cookie` це "сестра" класів `Path` і `Query`. Вони наслідуються від одного батьківського класу `Param`.
Але пам'ятайте, що коли ви імпортуєте `Query`, `Path`, `Cookie` та інше з `fastapi`, це фактично функції, що повертають спеціальні класи.
///
@@ -29,6 +30,16 @@
///
-## Підсумки
+/// info
+
+Майте на увазі, що оскільки **браузери обробляють cookies** спеціальним чином і за лаштунками, вони **не** дозволяють **JavaScript** легко взаємодіяти з ними.
+
+Якщо ви перейдете до **інтерфейсу документації API** за адресою `/docs`, ви зможете побачити **документацію** для cookies для ваших *операцій шляху*.
+
+Але навіть якщо ви **заповните дані** і натиснете "Execute", оскільки інтерфейс документації працює з **JavaScript**, cookies не буде надіслано, і ви побачите повідомлення про **помилку**, ніби ви не ввели жодних значень.
+
+///
+
+## Підсумки { #recap }
Визначайте cookies за допомогою `Cookie`, використовуючи той же спільний шаблон, що і `Query` та `Path`.
diff --git a/docs/uk/docs/tutorial/cors.md b/docs/uk/docs/tutorial/cors.md
index 95b204d0f7..f3ed8a7d94 100644
--- a/docs/uk/docs/tutorial/cors.md
+++ b/docs/uk/docs/tutorial/cors.md
@@ -1,8 +1,8 @@
-# CORS (Обмін ресурсами між різними джерелами)
+# CORS (Обмін ресурсами між різними джерелами) { #cors-cross-origin-resource-sharing }
-CORS або "Обмін ресурсами між різними джерелами" є ситуація, коли фронтенд, що працює в браузері, містить JavaScript-код, який взаємодіє з бекендом, розташованим в іншому "джерелі" (origin).
+CORS або "Cross-Origin Resource Sharing" є ситуація, коли фронтенд, що працює в браузері, містить JavaScript-код, який взаємодіє з бекендом, розташованим в іншому "джерелі" (origin).
-## Джерело (Origin)
+## Джерело (Origin) { #origin }
Джерело визначається комбінацією протоколу (`http`, `https`), домену (`myapp.com`, `localhost`, `localhost.tiangolo.com`), порту (`80`, `443`, `8080`).
@@ -15,7 +15,7 @@
Навіть якщо вони всі містять `localhost`, вони мають різні протоколи або порти, що робить їх окремими "джерелами".
-## Кроки
+## Кроки { #steps }
Припустимо, що Ваш фронтенд працює в браузері на `http://localhost:8080`, а його JavaScript намагається відправити запит до бекенду, який працює на `http://localhost` (Оскільки ми не вказуємо порт, браузер за замовчуванням припускає порт `80`).
@@ -25,15 +25,15 @@
У цьому випадку список має містити `http://localhost:8080`, щоб фронтенд на порту `:8080` працював коректно.
-## Символьне підставляння
+## Символьне підставляння { #wildcards }
Можна також оголосити список як `"*"` ("символьне підставляння"), що означає дозвіл для всіх джерел.
-Однак це дозволить лише певні типи комунікації, виключаючи все, що пов'язане з обліковими даними: Cookies, заголовки авторизації, такі як ті, що використовуються з Bearer токенами тощо.
+Однак це дозволить лише певні типи комунікації, виключаючи все, що пов'язане з обліковими даними: Cookies, заголовки авторизації, такі як ті, що використовуються з Bearer Tokens, тощо.
Тому для коректної роботи краще явно вказувати дозволені джерела.
-## Використання `CORSMiddleware`
+## Використання `CORSMiddleware` { #use-corsmiddleware }
Ви можете налаштувати це у Вашому додатку **FastAPI** за допомогою `CORSMiddleware`.
@@ -44,41 +44,44 @@
Також можна вказати, чи дозволяє Ваш бекенд:
-* Облікові дані (заголовки авторизації, сookies, тощо).
+* Облікові дані (заголовки авторизації, Cookies, тощо).
* Конкретні HTTP-методи (`POST`, `PUT`) або всі за допомогою `"*"`
* Конкретні HTTP-заголовки або всі за допомогою `"*"`.
-{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
+{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *}
-Параметри за замовчуванням у `CORSMiddleware` є досить обмеженими, тому Вам потрібно явно вказати конкретні джерела, методи або заголовки, щоб браузери могли використовувати їх у контексті запитів між різними доменами.
+Параметри за замовчуванням у реалізації `CORSMiddleware` є досить обмеженими, тому Вам потрібно явно увімкнути конкретні джерела, методи або заголовки, щоб браузерам було дозволено використовувати їх у міждоменному контексті.
Підтримуються такі аргументи:
-* `allow_origins` - Список джерел, яким дозволено здійснювати міждоменні запити. Наприклад `['https://example.org', 'https://www.example.org']`. Ви можете використовувати ['*'], щоб дозволити всі джерела.
+* `allow_origins` - Список джерел, яким дозволено здійснювати міждоменні запити. Наприклад `['https://example.org', 'https://www.example.org']`. Ви можете використовувати `['*']`, щоб дозволити будь-яке джерело.
* `allow_origin_regex` - Рядок регулярного виразу для відповідності джерелам, яким дозволено здійснювати міждоменні запити. Наприклад, `'https://.*\.example\.org'`.
* `allow_methods` - Список HTTP-методів, дозволених для міждоменних запитів. За замовчуванням `['GET']`. Ви можете використовувати `['*']`, щоб дозволити всі стандартні методи.
-* `allow_headers` - Список HTTP-заголовків, які підтримуються для міждоменних запитів. За замовчуванням `[]`. Ви можете використовувати `['*']`, щоб дозволити всі заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` і `Content-Type` завжди дозволені для простих CORS-запитів.
-* `allow_credentials` - Визначає, чи підтримуються файли cookie для міждоменних запитів. За замовчуванням `False`. Також, якщо потрібно дозволити обмін обліковими даними (`allow_credentials = True`), параметр `allow_origins` не може бути встановлений як `['*']`, необхідно вказати конкретні джерела.
+* `allow_headers` - Список HTTP-заголовків запиту, які підтримуються для міждоменних запитів. За замовчуванням `[]`. Ви можете використовувати `['*']`, щоб дозволити всі заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` і `Content-Type` завжди дозволені для простих CORS-запитів.
+* `allow_credentials` - Визначає, чи повинні підтримуватися cookies для міждоменних запитів. За замовчуванням `False`.
+
+ Жоден із параметрів `allow_origins`, `allow_methods` і `allow_headers` не можна встановлювати як `['*']`, якщо `allow_credentials` встановлено як `True`. Усі вони мають бути явно вказані.
+
* `expose_headers` - Вказує, які заголовки відповіді повинні бути доступні для браузера. За замовчуванням `[]`.
* `max_age` - Встановлює максимальний час (у секундах) для кешування CORS-відповідей у браузерах. За замовчуванням `600`.
Цей middleware обробляє два типи HTTP-запитів...
-### Попередні CORS-запити (preflight requests)
+### Попередні CORS-запити { #cors-preflight-requests }
Це будь-які `OPTIONS` - запити, що містять заголовки `Origin` та `Access-Control-Request-Method`.
У такому випадку middleware перехопить вхідний запит і відповість відповідними CORS-заголовками, повертаючи або `200`, або `400` для інформаційних цілей.
-### Прості запити
+### Прості запити { #simple-requests }
Будь-які запити із заголовком `Origin`. У цьому випадку middleware пропустить запит як звичайний, але додасть відповідні CORS-заголовки у відповідь.
-## Додаткова інформація
+## Додаткова інформація { #more-info }
-Більше про CORS можна дізнатися в документації Mozilla.
+Більше про CORS можна дізнатися в документації Mozilla про CORS.
/// note | Технічні деталі
diff --git a/docs/uk/docs/tutorial/debugging.md b/docs/uk/docs/tutorial/debugging.md
index b0e5344f81..0db418dcc9 100644
--- a/docs/uk/docs/tutorial/debugging.md
+++ b/docs/uk/docs/tutorial/debugging.md
@@ -1,16 +1,16 @@
-# Налагодження (Debugging)
+# Налагодження { #debugging }
-Ви можете під'єднати дебагер у Вашому редакторі коду, наприклад, у Visual Studio Code або PyCharm.
+Ви можете під'єднати дебагер у вашому редакторі коду, наприклад, у Visual Studio Code або PyCharm.
-## Виклик `uvicorn`
+## Виклик `uvicorn` { #call-uvicorn }
-У Вашому FastAPI-додатку імпортуйте та запустіть `uvicorn` безпосередньо:
+У вашому FastAPI-додатку імпортуйте та запустіть `uvicorn` безпосередньо:
-{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
+{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *}
-### Про `__name__ == "__main__"`
+### Про `__name__ == "__main__"` { #about-name-main }
-Головна мета використання `__name__ == "__main__"` — це забезпечення виконання певного коду тільки тоді, коли файл запускається безпосередньо:
+Головна мета використання `__name__ == "__main__"` — це забезпечення виконання певного коду лише тоді, коли ваш файл запускається так:
---
-Якщо Ви використовуєте PyCharm, ви можете:
-* Відкрити меню "Run".
-* Вибрати опцію "Debug...".
+Якщо ви використовуєте PyCharm, ви можете:
+
+* Відкрити меню «Run».
+* Вибрати опцію «Debug...».
* Потім з'явиться контекстне меню.
* Вибрати файл для налагодження (у цьому випадку, `main.py`).
-Це запустить сервер з Вашим **FastAPI** кодом, зупиниться на точках зупину тощо.
+Після цього він запустить сервер з вашим кодом **FastAPI**, зупиниться на точках зупину тощо.
Ось як це може виглядати:
diff --git a/docs/uk/docs/tutorial/encoder.md b/docs/uk/docs/tutorial/encoder.md
index f202c7989e..1b403d5bba 100644
--- a/docs/uk/docs/tutorial/encoder.md
+++ b/docs/uk/docs/tutorial/encoder.md
@@ -1,32 +1,32 @@
-# JSON Compatible Encoder
+# JSON-сумісний кодувальник { #json-compatible-encoder }
-Існують випадки, коли вам може знадобитися перетворити тип даних (наприклад, модель Pydantic) в щось сумісне з JSON (наприклад, `dict`, `list`, і т. д.).
+Існують випадки, коли вам може знадобитися перетворити тип даних (наприклад, модель Pydantic) на щось сумісне з JSON (наприклад, `dict`, `list` тощо).
Наприклад, якщо вам потрібно зберегти це в базі даних.
-Для цього, **FastAPI** надає `jsonable_encoder()` функцію.
+Для цього **FastAPI** надає функцію `jsonable_encoder()`.
-## Використання `jsonable_encoder`
+## Використання `jsonable_encoder` { #using-the-jsonable-encoder }
Давайте уявимо, що у вас є база даних `fake_db`, яка приймає лише дані, сумісні з JSON.
Наприклад, вона не приймає об'єкти типу `datetime`, оскільки вони не сумісні з JSON.
-Отже, об'єкт типу `datetime` потрібно перетворити в рядок `str`, який містить дані в ISO форматі.
+Отже, об'єкт типу `datetime` потрібно перетворити на `str`, який містить дані в форматі ISO.
-Тим самим способом ця база даних не прийматиме об'єкт типу Pydantic model (об'єкт з атрибутами), а лише `dict`.
+Так само ця база даних не прийматиме модель Pydantic (об'єкт з атрибутами), а лише `dict`.
Ви можете використовувати `jsonable_encoder` для цього.
-Вона приймає об'єкт, такий як Pydantic model, і повертає його версію, сумісну з JSON:
+Вона приймає об'єкт, такий як модель Pydantic, і повертає його версію, сумісну з JSON:
{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *}
-У цьому прикладі вона конвертує Pydantic model у `dict`, а `datetime` у `str`.
+У цьому прикладі вона конвертує модель Pydantic у `dict`, а `datetime` у `str`.
-Результат виклику цієї функції - це щось, що можна кодувати з використанням стандарту Python `json.dumps()`.
+Результат виклику цієї функції — це щось, що можна кодувати з використанням стандарту Python `json.dumps()`.
-Вона не повертає велику строку `str`, яка містить дані у форматі JSON (як строка). Вона повертає стандартну структуру даних Python (наприклад `dict`) із значеннями та підзначеннями, які є сумісними з JSON.
+Вона не повертає великий `str`, який містить дані у форматі JSON (як рядок). Вона повертає стандартну структуру даних Python (наприклад, `dict`) зі значеннями та підзначеннями, які є сумісними з JSON.
/// note | Примітка
diff --git a/docs/uk/docs/tutorial/extra-data-types.md b/docs/uk/docs/tutorial/extra-data-types.md
index 5da942b6e0..a3545e0746 100644
--- a/docs/uk/docs/tutorial/extra-data-types.md
+++ b/docs/uk/docs/tutorial/extra-data-types.md
@@ -1,13 +1,13 @@
-# Додаткові типи даних
+# Додаткові типи даних { #extra-data-types }
-До цього часу, ви використовували загальнопоширені типи даних, такі як:
+До цього часу ви використовували загальнопоширені типи даних, такі як:
* `int`
* `float`
* `str`
* `bool`
-Але можна також використовувати більш складні типи даних.
+Але ви також можете використовувати більш складні типи даних.
І ви все ще матимете ті ж можливості, які були показані до цього:
@@ -17,30 +17,30 @@
* Валідація даних.
* Автоматична анотація та документація.
-## Інші типи даних
+## Інші типи даних { #other-data-types }
Ось додаткові типи даних для використання:
* `UUID`:
- * Стандартний "Універсальний Унікальний Ідентифікатор", який часто використовується як ідентифікатор у багатьох базах даних та системах.
+ * Стандартний "Універсальний унікальний ідентифікатор", який часто використовується як ID у багатьох базах даних та системах.
* У запитах та відповідях буде представлений як `str`.
* `datetime.datetime`:
* Пайтонівський `datetime.datetime`.
- * У запитах та відповідях буде представлений як `str` в форматі ISO 8601, як: `2008-09-15T15:53:00+05:00`.
+ * У запитах та відповідях буде представлений як `str` у форматі ISO 8601, як: `2008-09-15T15:53:00+05:00`.
* `datetime.date`:
* Пайтонівський `datetime.date`.
- * У запитах та відповідях буде представлений як `str` в форматі ISO 8601, як: `2008-09-15`.
+ * У запитах та відповідях буде представлений як `str` у форматі ISO 8601, як: `2008-09-15`.
* `datetime.time`:
* Пайтонівський `datetime.time`.
- * У запитах та відповідях буде представлений як `str` в форматі ISO 8601, як: `14:23:55.003`.
+ * У запитах та відповідях буде представлений як `str` у форматі ISO 8601, як: `14:23:55.003`.
* `datetime.timedelta`:
* Пайтонівський `datetime.timedelta`.
* У запитах та відповідях буде представлений як `float` загальної кількості секунд.
- * Pydantic також дозволяє представляти це як "ISO 8601 time diff encoding", більше інформації дивись у документації.
+ * Pydantic також дозволяє представляти це як "ISO 8601 time diff encoding", дивіться документацію для отримання додаткової інформації.
* `frozenset`:
* У запитах і відповідях це буде оброблено так само, як і `set`:
- * У запитах список буде зчитано, дублікати будуть видалені та він буде перетворений на `set`.
- * У відповідях, `set` буде перетворений на `list`.
+ * У запитах список буде зчитано, дублікати буде видалено, і його буде перетворено на `set`.
+ * У відповідях `set` буде перетворено на `list`.
* Згенерована схема буде вказувати, що значення `set` є унікальними (з використанням JSON Schema's `uniqueItems`).
* `bytes`:
* Стандартний Пайтонівський `bytes`.
@@ -49,11 +49,11 @@
* `Decimal`:
* Стандартний Пайтонівський `Decimal`.
* У запитах і відповідях це буде оброблено так само, як і `float`.
-* Ви можете перевірити всі дійсні типи даних Pydantic тут: типи даних Pydantic.
+* Ви можете перевірити всі дійсні типи даних Pydantic тут: типи даних Pydantic.
-## Приклад
+## Приклад { #example }
-Ось приклад *path operation* з параметрами, використовуючи деякі з вищезазначених типів.
+Ось приклад *операції шляху* з параметрами, використовуючи деякі з вищезазначених типів.
{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *}
diff --git a/docs/uk/docs/tutorial/first-steps.md b/docs/uk/docs/tutorial/first-steps.md
index 3f861cb484..5f3750010c 100644
--- a/docs/uk/docs/tutorial/first-steps.md
+++ b/docs/uk/docs/tutorial/first-steps.md
@@ -1,126 +1,118 @@
-# Перші кроки
+# Перші кроки { #first-steps }
Найпростіший файл FastAPI може виглядати так:
-{* ../../docs_src/first_steps/tutorial001.py *}
+{* ../../docs_src/first_steps/tutorial001_py39.py *}
Скопіюйте це до файлу `main.py`.
-Запустіть сервер:
+Запустіть live-сервер:
get операцію
-/// info | `@decorator` Додаткова інформація
+/// info | `@decorator` Інформація
-Синтаксис `@something` у Python називається "декоратором".
+Синтаксис `@something` у Python називається «декоратором».
Ви розташовуєте його над функцією. Як гарний декоративний капелюх (мабуть, звідти походить термін).
-"Декоратор" приймає функцію нижче і виконує з нею якусь дію.
+«Декоратор» бере функцію нижче і виконує з нею якусь дію.
У нашому випадку, цей декоратор повідомляє **FastAPI**, що функція нижче відповідає **шляху** `/` і **операції** `get`.
-Це і є "декоратор операції шляху (path operation decorator)".
+Це і є «**декоратор операції шляху**».
///
-Можна також використовувати операції:
+Можна також використовувати інші операції:
* `@app.post()`
* `@app.put()`
@@ -271,58 +302,79 @@ https://example.com/items/foo
/// tip | Порада
-Ви можете використовувати кожну операцію (HTTP-метод) на свій розсуд.
+Ви можете використовувати кожну операцію (HTTP-метод) як забажаєте.
-**FastAPI** не нав'язує жодного певного значення для кожного методу.
+**FastAPI** не навʼязує жодного конкретного значення.
-Наведена тут інформація є рекомендацією, а не обов'язковою вимогою.
+Наведена тут інформація подана як настанова, а не вимога.
-Наприклад, під час використання GraphQL зазвичай усі дії виконуються тільки за допомогою `POST` операцій.
+Наприклад, під час використання GraphQL ви зазвичай виконуєте всі дії, використовуючи лише `POST` операції.
///
-### Крок 4: визначте **функцію операції шляху (path operation function)**
+### Крок 4: визначте **функцію операції шляху** { #step-4-define-the-path-operation-function }
-Ось "**функція операції шляху**":
+Ось наша «**функція операції шляху**»:
* **шлях**: це `/`.
* **операція**: це `get`.
-* **функція**: це функція, яка знаходиться нижче "декоратора" (нижче `@app.get("/")`).
+* **функція**: це функція нижче «декоратора» (нижче `@app.get("/")`).
-{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
+{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *}
-Це звичайна функція Python.
+Це функція Python.
-FastAPI викликатиме її щоразу, коли отримає запит до URL із шляхом "/", використовуючи операцію `GET`.
+**FastAPI** викликатиме її щоразу, коли отримає запит до URL «`/`», використовуючи операцію `GET`.
-У даному випадку це асинхронна функція.
+У цьому випадку це `async` функція.
---
Ви також можете визначити її як звичайну функцію замість `async def`:
-{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
+{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *}
/// note | Примітка
-Якщо не знаєте в чому різниця, подивіться [Конкурентність: *"Поспішаєш?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
+Якщо ви не знаєте різницю, подивіться [Асинхронність: *«Поспішаєте?»*](../async.md#in-a-hurry){.internal-link target=_blank}.
///
-### Крок 5: поверніть результат
+### Крок 5: поверніть вміст { #step-5-return-the-content }
-{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
+{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *}
-Ви можете повернути `dict`, `list`, а також окремі значення `str`, `int`, ітд.
+Ви можете повернути `dict`, `list`, а також окремі значення `str`, `int` тощо.
Також можна повернути моделі Pydantic (про це ви дізнаєтесь пізніше).
-Існує багато інших об'єктів і моделей, які будуть автоматично конвертовані в JSON (зокрема ORM тощо). Спробуйте використати свої улюблені, велика ймовірність, що вони вже підтримуються.
+Існує багато інших обʼєктів і моделей, які будуть автоматично конвертовані в JSON (зокрема ORM тощо). Спробуйте використати свої улюблені — велика ймовірність, що вони вже підтримуються.
-## Підіб'ємо підсумки
+### Крок 6: розгорніть його { #step-6-deploy-it }
-* Імпортуємо `FastAPI`.
-* Створюємо екземпляр `app`.
-* Пишемо **декоратор операції шляху** як `@app.get("/")`.
-* Пишемо **функцію операції шляху**; наприклад, `def root(): ...`.
-* Запускаємо сервер у режимі розробки `fastapi dev`.
+Розгорніть ваш застосунок у **FastAPI Cloud** однією командою: `fastapi deploy`. 🎉
+
+#### Про FastAPI Cloud { #about-fastapi-cloud }
+
+**FastAPI Cloud** створено тим самим автором і командою, які стоять за **FastAPI**.
+
+Він спрощує процес **створення**, **розгортання** та **доступу** до API з мінімальними зусиллями.
+
+Він переносить той самий **досвід розробника** зі створення застосунків на FastAPI на **розгортання** їх у хмарі. 🎉
+
+FastAPI Cloud — основний спонсор і джерело фінансування для open source проєктів *FastAPI and friends*. ✨
+
+#### Розгортання в інших хмарних провайдерах { #deploy-to-other-cloud-providers }
+
+FastAPI — це open source і базується на стандартах. Ви можете розгортати FastAPI-застосунки у будь-якого хмарного провайдера на ваш вибір.
+
+Дотримуйтеся інструкцій вашого хмарного провайдера, щоб розгорнути FastAPI-застосунки з їхньою допомогою. 🤓
+
+## Підібʼємо підсумки { #recap }
+
+* Імпортуйте `FastAPI`.
+* Створіть екземпляр `app`.
+* Напишіть **декоратор операції шляху**, використовуючи декоратори на кшталт `@app.get("/")`.
+* Визначте **функцію операції шляху**; наприклад, `def root(): ...`.
+* Запустіть сервер розробки командою `fastapi dev`.
+* За бажанням розгорніть ваш застосунок за допомогою `fastapi deploy`.
diff --git a/docs/uk/docs/tutorial/handling-errors.md b/docs/uk/docs/tutorial/handling-errors.md
index 32de73b2a0..53b8b12f61 100644
--- a/docs/uk/docs/tutorial/handling-errors.md
+++ b/docs/uk/docs/tutorial/handling-errors.md
@@ -1,10 +1,10 @@
-# Обробка Помилок
+# Обробка помилок { #handling-errors }
-Є багато ситуацій, коли потрібно повідомити клієнта, який використовує Ваш API, про помилку.
+Є багато ситуацій, коли вам потрібно повідомити про помилку клієнта, який використовує ваш API.
Цим клієнтом може бути браузер із фронтендом, код іншого розробника, IoT-пристрій тощо.
-Можливо, Вам потрібно повідомити клієнта, що:
+Можливо, вам потрібно повідомити клієнта, що:
* У нього недостатньо прав для виконання цієї операції.
* Він не має доступу до цього ресурсу.
@@ -13,37 +13,37 @@
У таких випадках зазвичай повертається **HTTP статус-код** в діапазоні **400** (від 400 до 499).
-Це схоже на HTTP статус-коди 200 (від 200 до 299). Ці "200" статус-коди означають, що запит пройшов успішно.
+Це схоже на HTTP статус-коди 200 (від 200 до 299). Ці «200» статус-коди означають, що якимось чином запит був «успішним».
Статус-коди в діапазоні 400 означають, що сталася помилка з боку клієнта.
-Пам'ятаєте всі ці помилки **404 Not Found** (і жарти про них)?
+Пам'ятаєте всі ці помилки **«404 Not Found»** (і жарти про них)?
-## Використання `HTTPException`
+## Використання `HTTPException` { #use-httpexception }
Щоб повернути HTTP-відповіді з помилками клієнту, використовуйте `HTTPException`.
-### Імпорт `HTTPException`
+### Імпорт `HTTPException` { #import-httpexception }
-{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
+{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *}
-### Використання `HTTPException` у коді
+### Згенеруйте `HTTPException` у своєму коді { #raise-an-httpexception-in-your-code }
`HTTPException` — це звичайна помилка Python із додатковими даними, які стосуються API.
-Оскільки це помилка Python, Ви не `повертаєте` його, а `генеруєте` (генеруєте помилку).
+Оскільки це помилка Python, ви не `return` її, а `raise` її.
-Це також означає, що якщо Ви перебуваєте всередині допоміжної функції, яку викликаєте всередині своєї *функції операції шляху*, і там генеруєте `HTTPException`, всередині цієї допоміжної функції, то решта коду в *функції операції шляху* не буде виконана. Запит одразу завершиться, і HTTP-помилка з `HTTPException` буде надіслана клієнту.
+Це також означає, що якщо ви перебуваєте всередині допоміжної функції, яку викликаєте всередині своєї *функції операції шляху*, і там згенеруєте `HTTPException` всередині цієї допоміжної функції, то решта коду в *функції операції шляху* не буде виконана. Запит одразу завершиться, і HTTP-помилка з `HTTPException` буде надіслана клієнту.
-Перевага використання `генерації` (raise) помилки замість `повернення` значення (return) стане більш очевидним в розділі про Залежності та Безпеку.
+Перевага генерації виключення замість повернення значення стане більш очевидною в розділі про залежності та безпеку.
-У цьому прикладі, якщо клієнт запитує елемент за ID, якого не існує, буде згенеровано помилку зі статус-кодом `404`:
+У цьому прикладі, коли клієнт запитує елемент за ID, якого не існує, згенеруйте виключення зі статус-кодом `404`:
-{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
+{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *}
-### Отримана відповідь
+### Отримана відповідь { #the-resulting-response }
-Якщо клієнт робить запит за шляхом `http://example.com/items/foo` (де `item_id` `"foo"`), він отримає статус-код 200 і JSON відповідь:
+Якщо клієнт робить запит за шляхом `http://example.com/items/foo` (де `item_id` `"foo"`), він отримає HTTP статус-код 200 і JSON відповідь:
```JSON
{
@@ -51,7 +51,7 @@
}
```
-Але якщо клієнт робить запит на `http://example.com/items/bar` (де `item_id` має не існуюче значення `"bar"`), то отримає статус-код 404 (помилка "не знайдено") та відповідь:
+Але якщо клієнт робить запит на `http://example.com/items/bar` (де `item_id` має не існуюче значення `"bar"`), то отримає HTTP статус-код 404 (помилка «не знайдено») та JSON відповідь:
```JSON
{
@@ -61,7 +61,7 @@
/// tip | Порада
-Під час виклику `HTTPException` Ви можете передати будь-яке значення, яке може бути перетворене в JSON, як параметр `detail`, а не лише рядок (`str`).
+Під час генерації `HTTPException` ви можете передати будь-яке значення, яке може бути перетворене в JSON, як параметр `detail`, а не лише `str`.
Ви можете передати `dict`, `list` тощо.
@@ -69,33 +69,33 @@
///
-## Додавання власних заголовків
+## Додавання власних заголовків { #add-custom-headers }
-Іноді потрібно додати власні заголовки до HTTP-помилки, наприклад, для певних типів безпеки.
+Є деякі ситуації, коли корисно мати можливість додавати власні заголовки до HTTP-помилки. Наприклад, для деяких типів безпеки.
-Ймовірно, Вам не доведеться використовувати це безпосередньо у своєму коді.
+Ймовірно, вам не доведеться використовувати це безпосередньо у своєму коді.
-Але якщо Вам знадобиться це для складного сценарію, Ви можете додати власні заголовки:
+Але якщо вам знадобиться це для складного сценарію, ви можете додати власні заголовки:
-{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
+{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *}
-## Встановлення власних обробників помилок
+## Встановлення власних обробників виключень { #install-custom-exception-handlers }
-Ви можете додати власні обробники помилок за допомогою тих самих утиліт обробки помилок зі Starlette.
+Ви можете додати власні обробники виключень за допомогою тих самих утиліт для виключень зі Starlette.
-Припустимо, у Вас є власний обʼєкт помилки `UnicornException`, яке Ви (або бібліотека, яку Ви використовуєте) може `згенерувати` (`raise`).
+Припустімо, у вас є власне виключення `UnicornException`, яке ви (або бібліотека, яку ви використовуєте) можете `raise`.
-І Ви хочете обробляти це виключення глобально за допомогою FastAPI.
+І ви хочете обробляти це виключення глобально за допомогою FastAPI.
Ви можете додати власний обробник виключень за допомогою `@app.exception_handler()`:
-{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *}
+{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *}
-Тут, якщо Ви звернетеся до `/unicorns/yolo`, то згенерується помилка `UnicornException`.
+Тут, якщо ви звернетеся до `/unicorns/yolo`, *операція шляху* згенерує (`raise`) `UnicornException`.
Але вона буде оброблена функцією-обробником `unicorn_exception_handler`.
-Отже, Ви отримаєте зрозумілу помилку зі HTTP-статусом `418` і JSON-відповіддю:
+Отже, ви отримаєте зрозумілу помилку зі HTTP-статусом `418` і JSON-вмістом:
```JSON
{"message": "Oops! yolo did something. There goes a rainbow..."}
@@ -105,31 +105,31 @@
Ви також можете використовувати `from starlette.requests import Request` і `from starlette.responses import JSONResponse`.
-**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette. Те ж саме стосується і `Request`.
+**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette. Те ж саме з `Request`.
///
-## Перевизначення обробників помилок за замовчуванням
+## Перевизначення обробників виключень за замовчуванням { #override-the-default-exception-handlers }
-**FastAPI** має кілька обробників помилок за замовчуванням.
+**FastAPI** має кілька обробників виключень за замовчуванням.
-Ці обробники відповідають за повернення стандартних JSON-відповідей, коли Ви `генеруєте` (`raise`) `HTTPException`, а також коли запит містить некоректні дані.
+Ці обробники відповідають за повернення стандартних JSON-відповідей, коли ви `raise` `HTTPException`, а також коли запит містить некоректні дані.
-Ви можете перевизначити ці обробники, створивши власні.
+Ви можете перевизначити ці обробники виключень власними.
-### Перевизначення помилок валідації запиту
+### Перевизначення виключень валідації запиту { #override-request-validation-exceptions }
-Коли запит містить некоректні дані, **FastAPI** генерує `RequestValidationError`.
+Коли запит містить некоректні дані, **FastAPI** внутрішньо генерує `RequestValidationError`.
-І також включає обробник помилок за замовчуванням для нього.
+І також включає обробник виключень за замовчуванням для нього.
-Щоб перевизначити його, імпортуйте `RequestValidationError` і використовуйте його з `@app.exception_handler(RequestValidationError)` для декорування обробника помилок.
+Щоб перевизначити його, імпортуйте `RequestValidationError` і використовуйте його з `@app.exception_handler(RequestValidationError)` для декорування обробника виключень.
-Обробник помилок отримує `Request` і саму помилку.
+Обробник виключень отримає `Request` і саме виключення.
-{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *}
+{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *}
-Тепер, якщо Ви перейдете за посиланням `/items/foo`, замість того, щоб отримати стандартну JSON-помилку:
+Тепер, якщо ви перейдете за посиланням `/items/foo`, замість того, щоб отримати стандартну JSON-помилку:
```JSON
{
@@ -146,55 +146,44 @@
}
```
-Ви отримаєте текстову версію:
+ви отримаєте текстову версію:
```
-1 validation error
-path -> item_id
- value is not a valid integer (type=type_error.integer)
+Validation errors:
+Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer
```
-#### `RequestValidationError` проти `ValidationError`
+### Перевизначення обробника помилок `HTTPException` { #override-the-httpexception-error-handler }
-/// warning | Увага
+Аналогічно, ви можете перевизначити обробник `HTTPException`.
-Це технічні деталі, які Ви можете пропустити, якщо вони зараз не важливі для Вас.
+Наприклад, ви можете захотіти повернути відповідь у вигляді простого тексту замість JSON для цих помилок:
-///
-
-`RequestValidationError` є підкласом Pydantic `ValidationError`.
-
-**FastAPI** використовує його для того, якщо Ви використовуєте модель Pydantic у `response_model` і у ваших даних є помилка, Ви побачили помилку у своєму журналі.
-
-Але клієнт/користувач не побачить її. Натомість клієнт отримає "Internal Server Error" зі статусом HTTP `500`.
-
-Так має бути, якщо у Вас виникла `ValidationError` Pydantic у *відповіді* або деінде у вашому коді (не у *запиті* клієнта), це насправді є помилкою у Вашому коді.
-
-І поки Ви її виправляєте, клієнти/користувачі не повинні мати доступу до внутрішньої інформації про помилку, оскільки це може призвести до вразливості безпеки.
-
-### Перевизначення обробника помилок `HTTPException`
-
-Аналогічно, Ви можете перевизначити обробник `HTTPException`.
-
-Наприклад, Ви можете захотіти повернути текстову відповідь замість JSON для цих помилок:
-
-{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}
+{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *}
/// note | Технічні деталі
Ви також можете використовувати `from starlette.responses import PlainTextResponse`.
-**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.
+**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.
///
-### Використання тіла `RequestValidationError`
+/// warning | Попередження
+
+Пам’ятайте, що `RequestValidationError` містить інформацію про назву файлу та рядок, де сталася помилка валідації, щоб за потреби ви могли показати це у своїх логах із відповідною інформацією.
+
+Але це означає, що якщо ви просто перетворите це на рядок і повернете цю інформацію напряму, ви можете розкрити трохи інформації про вашу систему, тому тут код витягає та показує кожну помилку незалежно.
+
+///
+
+### Використання тіла `RequestValidationError` { #use-the-requestvalidationerror-body }
`RequestValidationError` містить `body`, який він отримав із некоректними даними.
Ви можете використовувати це під час розробки свого додатка, щоб логувати тіло запиту та налагоджувати його, повертати користувачеві тощо.
-{* ../../docs_src/handling_errors/tutorial005.py hl[14] *}
+{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *}
Тепер спробуйте надіслати некоректний елемент, наприклад:
@@ -204,8 +193,8 @@ path -> item_id
"size": "XL"
}
```
-Ви отримаєте відповідь, яка повідомить Вам, які саме дані є некоректні у вашому тілі запиту:
+Ви отримаєте відповідь, яка повідомить вам, що дані є некоректними, і міститиме отримане тіло запиту:
```JSON hl_lines="12-15"
{
@@ -226,30 +215,30 @@ path -> item_id
}
```
-#### `HTTPException` FastAPI проти `HTTPException` Starlette
+#### `HTTPException` FastAPI проти `HTTPException` Starlette { #fastapis-httpexception-vs-starlettes-httpexception }
**FastAPI** має власний `HTTPException`.
-І клас помилки `HTTPException` в **FastAPI** успадковується від класу помилки `HTTPException` в Starlette.
+І клас помилки `HTTPException` в **FastAPI** успадковується від класу помилки `HTTPException` у Starlette.
Єдина різниця полягає в тому, що `HTTPException` в **FastAPI** приймає будь-які дані, які можна перетворити на JSON, для поля `detail`, тоді як `HTTPException` у Starlette приймає тільки рядки.
-Отже, Ви можете продовжувати використовувати `HTTPException` в **FastAPI** як зазвичай у своєму коді.
+Отже, ви можете продовжувати генерувати `HTTPException` **FastAPI** як зазвичай у своєму коді.
-Але коли Ви реєструєте обробник виключень, слід реєструвати його для `HTTPException` зі Starlette.
+Але коли ви реєструєте обробник виключень, слід реєструвати його для `HTTPException` зі Starlette.
-Таким чином, якщо будь-яка частина внутрішнього коду Starlette або розширення чи плагін Starlette згенерує (raise) `HTTPException`, Ваш обробник зможе перехопити та обробити її.
+Таким чином, якщо будь-яка частина внутрішнього коду Starlette або розширення чи плагін Starlette згенерує Starlette `HTTPException`, ваш обробник зможе перехопити та обробити її.
-У цьому прикладі, щоб мати можливість використовувати обидва `HTTPException` в одному коді, помилка Starlette перейменовується на `StarletteHTTPException`:
+У цьому прикладі, щоб мати можливість використовувати обидва `HTTPException` в одному коді, виключення Starlette перейменовується на `StarletteHTTPException`:
```Python
from starlette.exceptions import HTTPException as StarletteHTTPException
```
-### Повторне використання обробників помилок **FastAPI**
+### Повторне використання обробників виключень **FastAPI** { #reuse-fastapis-exception-handlers }
-Якщо Ви хочете використовувати помилки разом із такими ж обробниками помилок за замовчуванням, як у **FastAPI**, Ви можете імпортувати та повторно використовувати їх із `fastapi.exception_handlers`:
+Якщо ви хочете використовувати виключення разом із такими ж обробниками виключень за замовчуванням, як у **FastAPI**, ви можете імпортувати та повторно використати обробники виключень за замовчуванням із `fastapi.exception_handlers`:
-{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *}
+{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *}
-У цьому прикладі Ви просто використовуєте `print` для виведення дуже інформативного повідомлення, але Ви зрозуміли основну ідею. Ви можете обробити помилку та повторно використовувати обробники помилок за замовчуванням.
+У цьому прикладі ви просто друкуєте помилку з дуже виразним повідомленням, але ви зрозуміли основну ідею. Ви можете використовувати виключення, а потім просто повторно використати обробники виключень за замовчуванням.
diff --git a/docs/uk/docs/tutorial/header-param-models.md b/docs/uk/docs/tutorial/header-param-models.md
index 6f7b0bdae7..c080c19f06 100644
--- a/docs/uk/docs/tutorial/header-param-models.md
+++ b/docs/uk/docs/tutorial/header-param-models.md
@@ -1,26 +1,24 @@
-# Моделі Параметрів Заголовків
+# Моделі параметрів заголовків { #header-parameter-models }
-Якщо у Вас є група пов’язаних параметрів заголовків, Ви можете створити **Pydantic модель** для їх оголошення.
+Якщо у Вас є група пов’язаних **параметрів заголовків**, Ви можете створити **Pydantic модель** для їх оголошення.
Це дозволить Вам повторно **використовувати модель** в **різних місцях**, а також оголосити валідації та метадані для всіх параметрів одночасно. 😎
-/// note | Нотатки
+/// note | Примітка
Ця можливість підтримується починаючи з версії FastAPI `0.115.0`. 🤓
///
-## Параметри Заголовків з Використанням Pydantic Model
+## Параметри заголовків з використанням Pydantic моделі { #header-parameters-with-a-pydantic-model }
Оголосіть потрібні **параметри заголовків** у **Pydantic моделі**, а потім оголосіть параметр як `Header`:
{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *}
-FastAPI буде витягувати дані для кожного поля з заголовків у запиті та передавати їх у створену Вами Pydantic модель.
-
**FastAPI** буде **витягувати** дані для **кожного поля** з **заголовків** у запиті та передавати їх у створену Вами Pydantic модель.
-## Перевірка в Документації
+## Перевірка в документації { #check-the-docs }
Ви можете побачити необхідні заголовки в інтерфейсі документації за адресою `/docs`:
@@ -28,7 +26,7 @@ FastAPI буде витягувати дані для кожного поля з
contact поля| Параметр | Тип | Опис |
|---|---|---|
name | str | Ім'я контактної особи або організації. |
url | str | URL з інформацією для контакту. Повинен бути у форматі URL. |
email | str | Email контактної особи або організації. Повинен бути у форматі електронної пошти. |
license_info поля| Параметр | Тип | Опис |
|---|---|---|
name | str | ОБОВ'ЯЗКОВО (якщо встановлено license_info). Назва ліцензії для API. |
identifier | str | Ліцензійний вираз за SPDX для API. Поле identifier взаємовиключне з полем url. Доступно з OpenAPI 3.1.0, FastAPI 0.99.0. |
url | str | URL до ліцензії, яка використовується для API. Повинен бути у форматі URL. |
contact поля| Параметр | Тип | Опис |
|---|---|---|
name | str | Ідентифікаційне ім'я контактної особи або організації. |
url | str | URL, що вказує на контактну інформацію. МАЄ бути у форматі URL. |
email | str | Адреса електронної пошти контактної особи або організації. МАЄ бути у форматі адреси електронної пошти. |
license_info поля| Параметр | Тип | Опис |
|---|---|---|
name | str | ОБОВ'ЯЗКОВО (якщо встановлено license_info). Назва ліцензії для API. |
identifier | str | Ліцензійний вираз за SPDX для API. Поле identifier взаємовиключне з полем url. Доступно з OpenAPI 3.1.0, FastAPI 0.99.0. |
url | str | URL до ліцензії, яка використовується для API. МАЄ бути у форматі URL. |
-## Ідентифікатор ліцензії
+## Ідентифікатор ліцензії { #license-identifier }
З початку використання OpenAPI 3.1.0 та FastAPI 0.99.0 Ви також можете налаштувати `license_info` за допомогою `identifier` замість `url`.
Наприклад:
-{* ../../docs_src/metadata/tutorial001_1.py hl[31] *}
+{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *}
-## Метадані для тегів
+## Метадані для тегів { #metadata-for-tags }
Ви також можете додати додаткові метадані для різних тегів, які використовуються для групування операцій шляхів, за допомогою параметра `openapi_tags`.
@@ -46,53 +46,53 @@
Кожен словник може містити:
-* `name` (**обов'язково**): `str` з тією ж назвою тегу, яку Ви використовуєте у параметрі `tags` у Ваших *операціях шляху* та `APIRouter`s.
-* `description`: `str` з коротким описом тегу. Може містити Markdown і буде відображено в інтерфейсі документації.
-* `externalDocs`: `dict` який описує зовнішню документацію з такими полями:
+* `name` (**обов'язково**): `str` з тією ж назвою тегу, яку Ви використовуєте у параметрі `tags` у Ваших *операціях шляху* та `APIRouter`s.
+* `description`: `str` з коротким описом тегу. Може містити Markdown і буде показано в інтерфейсі документації.
+* `externalDocs`: `dict`, який описує зовнішню документацію з такими полями:
* `description`: `str` з коротким описом зовнішньої документації.
- * `url` (**обов'язково**): `str`з URL-адресою зовнішньої документації.
+ * `url` (**обов'язково**): `str` з URL-адресою зовнішньої документації.
-### Створення метаданих для тегів
+### Створення метаданих для тегів { #create-metadata-for-tags }
Спробуймо це на прикладі з тегами для `users` та `items`.
-Створіть метадані для своїх тегів і передайте їх у параметр `openapi_tags`:
+Створіть метадані для своїх тегів і передайте їх у параметр `openapi_tags`:
-{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
+{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *}
Зверніть увагу, що в описах можна використовувати Markdown, наприклад, "login" буде показано жирним шрифтом (**login**), а "fancy" буде показано курсивом (_fancy_).
/// tip | Порада
-Не обов'язково додавати метадані для всіх тегів, які Ви використовуєте.
+Вам не потрібно додавати метадані для всіх тегів, які Ви використовуєте.
///
-### Використання тегів
+### Використовуйте свої теги { #use-your-tags }
-Використовуйте параметр `tags` зі своїми *операціями шляху* (і `APIRouter`) для призначення їх до різних тегів:
+Використовуйте параметр `tags` зі своїми *операціями шляху* (і `APIRouter`s), щоб призначити їх до різних тегів:
-{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
+{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *}
/// info | Інформація
-Детальніше про теги читайте в розділі [Конфігурація шляхів операцій](path-operation-configuration.md#tags){.internal-link target=_blank}.
+Детальніше про теги читайте в розділі [Конфігурація операції шляху](path-operation-configuration.md#tags){.internal-link target=_blank}.
///
-### Перевірка документації
+### Перевірте документацію { #check-the-docs }
-Якщо Ви зараз перевірите документацію, вона покаже всі додаткові метадані:
+Тепер, якщо Ви перевірите документацію, вона покаже всі додаткові метадані:
-### Порядок тегів
+### Порядок тегів { #order-of-tags }
Порядок кожного словника метаданих тегу також визначає порядок відображення в інтерфейсі документації.
-Наприклад, хоча `users` мав би йти після `items` в алфавітному порядку, він відображається перед ними, оскільки ми додали його метадані як перший словник у списку.
+Наприклад, хоча `users` мав би йти після `items` в алфавітному порядку, він відображається перед ними, оскільки ми додали їхні метадані як перший словник у списку.
-## URL для OpenAPI
+## URL для OpenAPI { #openapi-url }
За замовчуванням схема OpenAPI надається за адресою `/openapi.json`.
@@ -100,11 +100,11 @@
Наприклад, щоб налаштувати його на `/api/v1/openapi.json`:
-{* ../../docs_src/metadata/tutorial002.py hl[3] *}
+{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *}
Якщо Ви хочете повністю вимкнути схему OpenAPI, Ви можете встановити `openapi_url=None`, це також вимкне інтерфейси документації, які її використовують.
-## URL-адреси документації
+## URL-адреси документації { #docs-urls }
Ви можете налаштувати два інтерфейси користувача для документації, які включені:
@@ -117,4 +117,4 @@
Наприклад, щоб налаштувати Swagger UI на `/documentation` і вимкнути ReDoc:
-{* ../../docs_src/metadata/tutorial003.py hl[3] *}
+{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *}
diff --git a/docs/uk/docs/tutorial/middleware.md b/docs/uk/docs/tutorial/middleware.md
index 13ce8573de..2d1580e49e 100644
--- a/docs/uk/docs/tutorial/middleware.md
+++ b/docs/uk/docs/tutorial/middleware.md
@@ -1,45 +1,43 @@
-# Middleware (Проміжний шар)
+# Middleware { #middleware }
-У **FastAPI** можна додавати middleware (проміжний шар).
+У **FastAPI** можна додавати middleware.
-"Middleware" — це функція, яка працює з кожним **запитом** перед його обробкою будь-якою конкретною *операцією шляху* (*path operation*), а також з кожною **відповіддю** перед її поверненням.
+«Middleware» — це функція, яка працює з кожним **запитом** перед його обробкою будь-якою конкретною *операцією шляху*. А також з кожною **відповіддю** перед її поверненням.
-* Middleware отримує кожен **запит**, що надходить до Вашого застосунку.
-* Може виконати певні дії із цим **запитом** або запустити необхідний код.
-* Далі передає **запит** для обробки основним застосунком (*операцією шляху*).
-* Отримує **відповідь**, сформовану застосунком (*операцією шляху*).
-* Може змінити цю **відповідь** або виконати додатковий код.
-* Повертає **відповідь** клієнту.
+* Вона отримує кожен **запит**, що надходить до вашого застосунку.
+* Потім вона може виконати певні дії із цим **запитом** або запустити необхідний код.
+* Далі вона передає **запит** для обробки рештою застосунку (якоюсь *операцією шляху*).
+* Потім вона отримує **відповідь**, сформовану застосунком (якоюсь *операцією шляху*).
+* Вона може виконати певні дії із цією **відповіддю** або запустити необхідний код.
+* Потім вона повертає **відповідь**.
/// note | Технічні деталі
-Якщо у Вас є залежності з `yield`, код виходу виконається *після* middleware.
+Якщо у вас є залежності з `yield`, код виходу виконається *після* middleware.
-Якщо були заплановані фонові задачі (background tasks - розглянуто далі), вони виконаються *після* всіх middleware.
+Якщо були заплановані фонові задачі (розглянуто в розділі [Background Tasks](background-tasks.md){.internal-link target=_blank}, ви побачите це пізніше), вони виконаються *після* всіх middleware.
///
-## Створення middleware
+## Створення middleware { #create-a-middleware }
-Щоб створити middleware, Ви використовуєте декоратор `@app.middleware("http")` на функції.
+Щоб створити middleware, ви використовуєте декоратор `@app.middleware("http")` над функцією.
Функція middleware отримує:
-* `Запит`.
-* Функцію `call_next`, яка приймає `запит` як параметр.
- * Ця функція передає `запит` відповідній *операції шляху*.
- * Потім вона повертає `відповідь`, згенеровану цією *операцією шляху*.
+* `request`.
+* Функцію `call_next`, яка отримає `request` як параметр.
+ * Ця функція передасть `request` відповідній *операції шляху*.
+ * Потім вона поверне `response`, згенеровану відповідною *операцією шляху*.
+* Потім ви можете додатково змінити `response` перед тим, як повернути її.
-* Ви можете ще змінити `відповідь` перед тим, як повернути її.
-
-
-{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
+{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *}
/// tip | Порада
-Не забувайте, що власні заголовки можна додавати, використовуючи префікс 'X-'.
+Пам’ятайте, що власні пропрієтарні заголовки можна додавати використовуючи префікс `X-`.
-Але якщо у Вас є власні заголовки, які Ви хочете, щоб браузерний клієнт міг побачити, потрібно додати їх до Вашої конфігурації CORS (див. [CORS (Обмін ресурсами між різними джерелами)](cors.md){.internal-link target=_blank} за допомогою параметра `expose_headers`, описаного в документації Starlette по CORS.
+Але якщо у вас є власні заголовки, які ви хочете, щоб клієнт у браузері міг побачити, потрібно додати їх до ваших конфігурацій CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) за допомогою параметра `expose_headers`, описаного в документації Starlette по CORS.
///
@@ -47,28 +45,50 @@
Ви також можете використати `from starlette.requests import Request`.
-**FastAPI** надає це для Вашої зручності як розробника. Але він походить безпосередньо зі Starlette.
+**FastAPI** надає це для вашої зручності як розробника. Але воно походить безпосередньо зі Starlette.
///
-### До і після `response`(`відповіді`)
+### До і після `response` { #before-and-after-the-response }
-Ви можете додати код, який буде виконуватися з `запитом` (`request`), до того, як його обробить будь-яка *операція шляху* (*path operation*).
+Ви можете додати код, який буде виконуватися з `request`, до того, як його отримає будь-яка *операція шляху*.
-Також Ви можете додати код, який буде виконуватися після того, як `відповідь` (`response`) буде згенеровано, перед тим як його повернути.
+Також ви можете додати код, який буде виконуватися після того, як `response` буде згенеровано, перед тим як її повернути.
-Наприклад, Ви можете додати власний заголовок `X-Process-Time`, який міститиме час у секундах, який витратився на обробку запиту та генерацію відповіді:
+Наприклад, ви можете додати власний заголовок `X-Process-Time`, який міститиме час у секундах, який витратився на обробку запиту та генерацію відповіді:
-{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
+{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *}
-
-/// tip | Підказка
+/// tip | Порада
Тут ми використовуємо `time.perf_counter()` замість `time.time()` оскільки він може бути більш точним для таких випадків. 🤓
///
-## Інші middlewares
+## Порядок виконання кількох middleware { #multiple-middleware-execution-order }
+
+Коли ви додаєте кілька middleware, використовуючи або декоратор `@app.middleware()` або метод `app.add_middleware()`, кожен новий middleware обгортає застосунок, утворюючи стек. Останній доданий middleware є *зовнішнім*, а перший — *внутрішнім*.
+
+На шляху запиту першим виконується *зовнішній* middleware.
+
+На шляху відповіді він виконується останнім.
+
+Наприклад:
+
+```Python
+app.add_middleware(MiddlewareA)
+app.add_middleware(MiddlewareB)
+```
+
+Це призводить до такого порядку виконання:
+
+* **Запит**: MiddlewareB → MiddlewareA → route
+
+* **Відповідь**: route → MiddlewareA → MiddlewareB
+
+Така поведінка стеку гарантує, що middleware виконуються у передбачуваному та керованому порядку.
+
+## Інші middlewares { #other-middlewares }
Ви можете пізніше прочитати більше про інші middlewares в [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}.
diff --git a/docs/uk/docs/tutorial/path-params-numeric-validations.md b/docs/uk/docs/tutorial/path-params-numeric-validations.md
index 8ee4f480fe..f6aa920193 100644
--- a/docs/uk/docs/tutorial/path-params-numeric-validations.md
+++ b/docs/uk/docs/tutorial/path-params-numeric-validations.md
@@ -1,8 +1,8 @@
-# Path Параметри та валідація числових даних
+# Параметри шляху та валідація числових даних { #path-parameters-and-numeric-validations }
-Так само як Ви можете оголошувати додаткові перевірки та метадані для query параметрів за допомогою `Query`, Ви можете оголошувати той самий тип перевірок і метаданих для параметрів шляху за допомогою `Path`.
+Так само як ви можете оголошувати додаткові перевірки та метадані для query параметрів за допомогою `Query`, ви можете оголошувати той самий тип перевірок і метаданих для параметрів шляху за допомогою `Path`.
-## Імпорт Path
+## Імпорт `Path` { #import-path }
Спочатку імпортуйте `Path` з `fastapi` і імпортуйте `Annotated`:
@@ -10,70 +10,69 @@
/// info | Інформація
-FastAPI додав підтримку `Annotated` (і почав рекомендувати його використання) у версії 0.95.0.
+FastAPI додав підтримку `Annotated` (і почав рекомендувати його використання) у версії 0.95.0.
-Якщо у Вас стара версія, при спробі використати `Annotated` можуть виникати помилки.
+Якщо у вас стара версія, при спробі використати `Annotated` можуть виникати помилки.
-Переконайтеся, що Ви [оновили версію FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} принаймні до версії 0.95.1 перед використанням `Annotated`.
+Переконайтеся, що ви [оновили версію FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} принаймні до версії 0.95.1 перед використанням `Annotated`.
///
-## Оголошення метаданих
+## Оголошення метаданих { #declare-metadata }
Ви можете оголошувати всі ті ж параметри, що і для `Query`.
-Наприклад, щоб оголосити значення метаданих `title` для параметра шляху `item_id`, Ви можете написати:
+Наприклад, щоб оголосити значення метаданих `title` для параметра шляху `item_id`, ви можете написати:
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *}
/// note | Примітка
-Параметр шляху завжди є обов’язковим, оскільки він має бути частиною шляху. Навіть якщо Ви оголосите його зі значенням `None` або встановите значення за замовчуванням — він все одно залишатиметься обов’язковим.
+Параметр шляху завжди є обов’язковим, оскільки він має бути частиною шляху. Навіть якщо ви оголосите його зі значенням `None` або встановите значення за замовчуванням — це ні на що не вплине, він все одно завжди буде обов’язковим.
///
-## Упорядковуйте параметри, як Вам потрібно
+## Упорядковуйте параметри, як вам потрібно { #order-the-parameters-as-you-need }
-/// tip | Підказка
+/// tip | Порада
-Це, мабуть, не настільки важливо або необхідно, якщо Ви використовуєте `Annotated`.
+Це, мабуть, не настільки важливо або необхідно, якщо ви використовуєте `Annotated`.
///
-Припустимо, Ви хочете оголосити параметр запиту `q` як обов’язковий `str`.
+Припустимо, ви хочете оголосити параметр запиту `q` як обов’язковий `str`.
-І Вам не потрібно оголошувати нічого іншого для цього параметра, тому немає потреби використовувати `Query`.
+І вам не потрібно оголошувати нічого іншого для цього параметра, тому вам насправді не потрібно використовувати `Query`.
-Але Вам все одно потрібно використовувати `Path` для параметра шляху `item_id`. І з певних причин Ви не хочете використовувати `Annotated`.
+Але вам все одно потрібно використовувати `Path` для параметра шляху `item_id`. І з певних причин ви не хочете використовувати `Annotated`.
Python видасть помилку, якщо розмістити значення з "default" перед значенням, яке не має "default".
-Але Ви можете змінити порядок і розмістити значення без значення за замовчуванням (параметр запиту `q`) першим.
+Але ви можете змінити порядок і розмістити значення без значення за замовчуванням (параметр запиту `q`) першим.
+Для **FastAPI** порядок не має значення. Він визначає параметри за їхніми іменами, типами та оголошеннями за замовчуванням (`Query`, `Path` тощо) і не звертає уваги на порядок.
-Для **FastAPI** порядок не має значення. Він визначає параметри за їх іменами, типами та значеннями за замовчуванням (`Query`, `Path` тощо) і не звертає уваги на порядок.
+Тому ви можете оголосити вашу функцію так:
-Тому Ви можете оголосити Вашу функцію так:
+{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *}
-{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
-
-Але майте на увазі, що якщо Ви використовуєте `Annotated`, ця проблема не виникне, оскільки Ви не використовуєте значення за замовчуванням для параметрів `Query()` або `Path()`.
+Але майте на увазі, що якщо ви використовуєте `Annotated`, цієї проблеми не буде, це не матиме значення, оскільки ви не використовуєте значення за замовчуванням параметрів функції для `Query()` або `Path()`.
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
-## Упорядковуйте параметри за потребою, хитрощі
+## Упорядковуйте параметри, як вам потрібно: хитрощі { #order-the-parameters-as-you-need-tricks }
-/// tip | Підказка
+/// tip | Порада
-Це, мабуть, не настільки важливо або необхідно, якщо Ви використовуєте `Annotated`.
+Це, мабуть, не настільки важливо або необхідно, якщо ви використовуєте `Annotated`.
///
Ось **невелика хитрість**, яка може стати в пригоді, хоча вона рідко знадобиться.
-Якщо Ви хочете:
+Якщо ви хочете:
-* оголосити параметр запиту `q` без використання `Query` або значення за замовчуванням
+* оголосити параметр запиту `q` без `Query` і без жодного значення за замовчуванням
* оголосити параметр шляху `item_id`, використовуючи `Path`
* розмістити їх у різному порядку
* не використовувати `Annotated`
@@ -84,72 +83,72 @@ Python видасть помилку, якщо розмістити значен
Python нічого не зробить із цією `*`, але розпізнає, що всі наступні параметри слід викликати як аргументи за ключовим словом (пари ключ-значення), також відомі як kwargs. Навіть якщо вони не мають значення за замовчуванням.
-{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
-### Краще з `Annotated`
+### Краще з `Annotated` { #better-with-annotated }
-Майте на увазі, якщо Ви використовуєте `Annotated`, оскільки Ви не використовуєте значення за замовчуванням для параметрів функції, цієї проблеми не виникне, і, швидше за все, Вам не потрібно буде використовувати `*`.
+Майте на увазі, що якщо ви використовуєте `Annotated`, оскільки ви не використовуєте значення за замовчуванням параметрів функції, цієї проблеми не буде, і, ймовірно, вам не потрібно буде використовувати `*`.
{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
-## Валідація числових даних: більше або дорівнює
+## Валідація числових даних: більше або дорівнює { #number-validations-greater-than-or-equal }
-За допомогою `Query` і `Path` (та інших, які Ви побачите пізніше) можна оголошувати числові обмеження.
+За допомогою `Query` і `Path` (та інших, які ви побачите пізніше) можна оголошувати числові обмеження.
Тут, завдяки `ge=1`, `item_id` має бути цілим числом, яке "`g`reater than or `e`qual" (більше або дорівнює) `1`.
{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
-## Валідація числових даних: більше ніж і менше або дорівнює
+## Валідація числових даних: більше ніж і менше або дорівнює { #number-validations-greater-than-and-less-than-or-equal }
Те саме застосовується до:
-* `gt`: `g`reater `t`han (більше ніж)
-* `le`: `l`ess than or `e`qual (менше або дорівнює)
+* `gt`: `g`reater `t`han
+* `le`: `l`ess than or `e`qual
{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
-## Валідація числових даних: float, більше ніж і менше ніж
+## Валідація числових даних: float, більше ніж і менше ніж { #number-validations-floats-greater-than-and-less-than }
Валідація чисел також працює для значень типу `float`.
-Ось де стає важливо мати можливість оголошувати gt, а не тільки ge. Це дозволяє, наприклад, вимагати, щоб значення було більше `0`, навіть якщо воно менше `1`.
+Ось де стає важливо мати можливість оголошувати gt, а не тільки ge. Це дозволяє, наприклад, вимагати, щоб значення було більше `0`, навіть якщо воно менше `1`.
Таким чином, значення `0.5` буде допустимим. Але `0.0` або `0` — ні.
-Те саме стосується lt.
+Те саме стосується lt.
{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *}
-## Підсумок
+## Підсумок { #recap }
-За допомогою `Query`, `Path` (і інших параметрів, які Ви ще не бачили) можна оголошувати метадані та перевірки рядків, так само як у [Query параметри та валідація рядків](query-params-str-validations.md){.internal-link target=_blank}.
+За допомогою `Query`, `Path` (і інших параметрів, які ви ще не бачили) можна оголошувати метадані та перевірки рядків так само як у [Query параметри та валідація рядків](query-params-str-validations.md){.internal-link target=_blank}.
Також можна оголошувати числові перевірки:
-* `gt`: `g`reater `t`han (більше ніж)
-* `ge`: `g`reater than or `e`qual (більше або дорівнює)
-* `lt`: `l`ess `t`han (менше ніж)
-* `le`: `l`ess than or `e`qual (менше або дорівнює)
+* `gt`: `g`reater `t`han
+* `ge`: `g`reater than or `e`qual
+* `lt`: `l`ess `t`han
+* `le`: `l`ess than or `e`qual
/// info | Інформація
-`Query`, `Path` та інші класи, які Ви побачите пізніше, є підкласами спільного класу `Param`.
+`Query`, `Path` та інші класи, які ви побачите пізніше, є підкласами спільного класу `Param`.
-Всі вони мають однакові параметри для додаткових перевірок і метаданих, які Ви вже бачили.
+Всі вони мають однакові параметри для додаткових перевірок і метаданих, які ви вже бачили.
///
/// note | Технічні деталі
-Коли Ви імпортуєте `Query`, `Path` та інші з `fastapi`, насправді це функції.
+Коли ви імпортуєте `Query`, `Path` та інші з `fastapi`, насправді це функції.
При виклику вони повертають екземпляри класів з такими ж іменами.
-Тобто Ви імпортуєте `Query`, яка є функцією. А коли Ви її викликаєте, вона повертає екземпляр класу, який теж називається `Query`.
+Тобто ви імпортуєте `Query`, яка є функцією. А коли ви її викликаєте, вона повертає екземпляр класу, який теж називається `Query`.
-Ці функції створені таким чином (замість використання класів напряму), щоб Ваш редактор не відзначав їхні типи як помилки.
+Ці функції створені таким чином (замість використання класів напряму), щоб ваш редактор не відзначав їхні типи як помилки.
-Таким чином, Ви можете користуватися своїм звичайним редактором і інструментами для програмування без додаткових налаштувань для ігнорування таких помилок.
+Таким чином, ви можете користуватися своїм звичайним редактором і інструментами для програмування без додаткових налаштувань для ігнорування таких помилок.
///
diff --git a/docs/uk/docs/tutorial/path-params.md b/docs/uk/docs/tutorial/path-params.md
index da4ff2f9ea..0598905495 100644
--- a/docs/uk/docs/tutorial/path-params.md
+++ b/docs/uk/docs/tutorial/path-params.md
@@ -1,34 +1,34 @@
-# Path Параметри
+# Параметри шляху { #path-parameters }
-Ви можете визначити "параметри" або "змінні" шляху, використовуючи синтаксис форматованих рядків:
+Ви можете оголосити «параметри» або «змінні» шляху, використовуючи той самий синтаксис, що й у форматованих рядках Python:
-{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
+{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
-Значення параметра шляху `item_id` передається у функцію як аргумент `item_id`.
+Значення параметра шляху `item_id` буде передано у вашу функцію як аргумент `item_id`.
-Якщо запустити цей приклад та перейти за посиланням http://127.0.0.1:8000/items/foo, то отримаємо таку відповідь:
+Отже, якщо ви запустите цей приклад і перейдете за посиланням http://127.0.0.1:8000/items/foo, то побачите відповідь:
```JSON
{"item_id":"foo"}
```
-## Path параметри з типами
+## Параметри шляху з типами { #path-parameters-with-types }
-Ви можете визначити тип параметра шляху у функції, використовуючи стандартні анотації типів Python:
+Ви можете оголосити тип параметра шляху у функції, використовуючи стандартні анотації типів Python:
-{* ../../docs_src/path_params/tutorial002.py hl[7] *}
+{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *}
-У такому випадку `item_id` визначається як `int`.
+У цьому випадку `item_id` оголошено як `int`.
/// check | Примітка
-Це дасть можливість підтримки редактора всередині функції з перевірками помилок, автодоповнення тощо.
+Це дасть вам підтримку редактора всередині функції з перевірками помилок, автодоповненням тощо.
///
-## Перетворення даних
+## Перетворення даних { #data-conversion }
-Якщо запустити цей приклад і перейти за посиланням http://127.0.0.1:8000/items/3, то отримаєте таку відповідь:
+Якщо ви запустите цей приклад і відкриєте у браузері http://127.0.0.1:8000/items/3, то побачите відповідь:
```JSON
{"item_id":3}
@@ -36,15 +36,15 @@
/// check | Примітка
-Зверніть увагу, що значення, яке отримала (і повернула) ваша функція, — це `3`. Це Python `int`, а не рядок `"3"`.
+Зверніть увагу, що значення, яке отримала (і повернула) ваша функція, — це `3`, як Python `int`, а не рядок `"3"`.
-Отже, з таким оголошенням типу **FastAPI** автоматично виконує "парсинг" запитів.
+Отже, з таким оголошенням типу **FastAPI** надає вам автоматичний «parsing» запиту.
///
-## Перевірка даних
+## Валідація даних { #data-validation }
-Якщо ж відкрити у браузері посилання http://127.0.0.1:8000/items/foo, то побачимо цікаву HTTP-помилку:
+Але якщо ви перейдете у браузері за посиланням http://127.0.0.1:8000/items/foo, ви побачите гарну HTTP-помилку:
```JSON
{
@@ -61,144 +61,136 @@
]
}
```
-тому що параметр шляху має значення `"foo"`, яке не є типом `int`.
-Таку саму помилку отримаємо, якщо передати `float` замість `int`, як бачимо, у цьому прикладі: http://127.0.0.1:8000/items/4.2
+тому що параметр шляху `item_id` мав значення `"foo"`, яке не є `int`.
+
+Та сама помилка з’явиться, якщо ви передасте `float` замість `int`, як у: http://127.0.0.1:8000/items/4.2
/// check | Примітка
-Отже, **FastAPI** надає перевірку типів з таким самим оголошенням типу в Python.
+Отже, з тим самим оголошенням типу в Python **FastAPI** надає вам валідацію даних.
-Зверніть увагу, що помилка також чітко вказує саме на те місце, де валідація не пройшла.
+Зверніть увагу, що помилка також чітко вказує саме місце, де валідація не пройшла.
-Це неймовірно корисно під час розробки та дебагінгу коду, що взаємодіє з вашим API.
+Це неймовірно корисно під час розробки та налагодження коду, що взаємодіє з вашим API.
///
-## Документація
+## Документація { #documentation }
-Тепер коли відкриєте свій браузер за посиланням http://127.0.0.1:8000/docs, то побачите автоматично згенеровану, інтерактивну API-документацію:
+А коли ви відкриєте у браузері http://127.0.0.1:8000/docs, ви побачите автоматичну, інтерактивну, API-документацію на кшталт:
/// check | Примітка
-Знову ж таки, лише з цим самим оголошенням типу в Python, FastAPI надає вам автоматичну, інтерактивну документацію (з інтеграцією Swagger UI).
-
-Зверніть увагу, що параметр шляху оголошений як ціле число.
+Знову ж таки, лише з тим самим оголошенням типу в Python **FastAPI** надає вам автоматичну, інтерактивну документацію (з інтеграцією Swagger UI).
+Зверніть увагу, що параметр шляху оголошено як ціле число.
///
-## Переваги стандартизації, альтернативна документація
+## Переваги стандартів, альтернативна документація { #standards-based-benefits-alternative-documentation }
І оскільки згенерована схема відповідає стандарту OpenAPI, існує багато сумісних інструментів.
-З цієї причини FastAPI також надає альтернативну документацію API (використовуючи ReDoc), до якої можна отримати доступ за посиланням http://127.0.0.1:8000/redoc:
+Через це **FastAPI** також надає альтернативну API-документацію (використовуючи ReDoc), до якої ви можете отримати доступ за посиланням http://127.0.0.1:8000/redoc:
-Таким чином, існує багато сумісних інструментів, включаючи інструменти для генерації коду для багатьох мов.
+Так само, існує багато сумісних інструментів. Зокрема інструменти генерації коду для багатьох мов.
+## Pydantic { #pydantic }
-## Pydantic
+Уся валідація даних виконується за лаштунками за допомогою Pydantic, тож ви отримуєте всі переваги від його використання. І ви знаєте, що ви в надійних руках.
-Вся валідація даних виконується за лаштунками за допомогою Pydantic, тому Ви отримуєте всі переваги від його використання. І можете бути впевнені, що все в надійних руках.
+Ви можете використовувати ті самі оголошення типів з `str`, `float`, `bool` та багатьма іншими складними типами даних.
-Ви можете використовувати ті самі оголошення типів з `str`, `float`, `bool` та багатьма іншими складними типами даних.
+Декілька з них розглядаються в наступних розділах посібника.
-Декілька з них будуть розглянуті в наступних розділах посібника.
+## Порядок має значення { #order-matters }
-## Порядок має значення
+Під час створення *операцій шляху* можуть виникати ситуації, коли у вас є фіксований шлях.
-При створенні *операцій шляху* можуть виникати ситуації, коли шлях фіксований.
+Наприклад, `/users/me` — припустімо, це для отримання даних про поточного користувача.
-Наприклад, `/users/me`. Припустимо, що це шлях для отримання даних про поточного користувача.
+І тоді у вас також може бути шлях `/users/{user_id}` для отримання даних про конкретного користувача за його ID.
-А також у вас може бути шлях `/users/{user_id}`, щоб отримати дані про конкретного користувача за його ID.
+Оскільки *операції шляху* оцінюються по черзі, вам потрібно переконатися, що шлях для `/users/me` оголошено перед шляхом для `/users/{user_id}`:
-Оскільки *операції шляху* оцінюються по черзі, Ви повинні переконатися, що шлях для `/users/me` оголошений перед шляхом для `/users/{user_id}`:
+{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *}
-{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
+Інакше шлях для `/users/{user_id}` також відповідатиме `/users/me`, «вважаючи», що отримує параметр `user_id` зі значенням `"me"`.
-Інакше шлях для `/users/{user_id}` також буде відповідати для `/users/me`, "вважаючи", що він отримує параметр `user_id` зі значенням `"me"`.
+Так само ви не можете перевизначити операцію шляху:
-Аналогічно, Ви не можете оголосити операцію шляху:
+{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *}
-{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *}
+Завжди використовуватиметься перша, оскільки шлях збігається першим.
-Перша операція буде завжди використовуватися, оскільки шлях збігається першим.
-## Попередньо визначені значення
+## Попередньо визначені значення { #predefined-values }
-Якщо у вас є *операція шляху*, яка приймає *параметр шляху*, але Ви хочете, щоб можливі допустимі значення *параметра шляху* були попередньо визначені, Ви можете використати стандартний Python Enum.
+Якщо у вас є *операція шляху*, яка отримує *параметр шляху*, але ви хочете, щоб можливі коректні значення *параметра шляху* були попередньо визначені, ви можете використати стандартний Python `Enum`.
-### Створення класу `Enum`
+### Створіть клас `Enum` { #create-an-enum-class }
Імпортуйте `Enum` і створіть підклас, що наслідується від `str` та `Enum`.
-Наслідуючи від `str`, документація API зможе визначити, що значення повинні бути типу `string`, і правильно їх відобразить.
+Завдяки наслідуванню від `str` документація API зможе визначити, що значення повинні бути типу `string`, і зможе коректно їх відобразити.
-Після цього створіть атрибути класу з фіксованими значеннями, які будуть доступними допустимими значеннями:
+Після цього створіть атрибути класу з фіксованими значеннями, які будуть доступними коректними значеннями:
-{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
-
-/// info | Додаткова інформація
-
-Перелічення (або enums) доступні в Python починаючи з версії 3.4.
-
-///
+{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *}
/// tip | Порада
-Якщо вам цікаво, "AlexNet", "ResNet" та "LeNet" — це просто назви ML моделей Machine Learning.
+Якщо вам цікаво, «AlexNet», «ResNet» та «LeNet» — це просто назви Machine Learning models.
///
-
-### Оголосіть *параметр шляху*
+### Оголосіть *параметр шляху* { #declare-a-path-parameter }
Потім створіть *параметр шляху* з анотацією типу, використовуючи створений вами клас enum (`ModelName`):
-{* ../../docs_src/path_params/tutorial005.py hl[16] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
-### Перевірка документації
+### Перевірте документацію { #check-the-docs }
-Оскільки доступні значення для *параметра шляху* визначені заздалегідь, інтерактивна документація зможе красиво їх відобразити:
+Оскільки доступні значення для *параметра шляху* визначені заздалегідь, інтерактивна документація може красиво їх показати:
-### Робота з *перелічуваннями* у Python
+### Робота з Python *переліченнями* { #working-with-python-enumerations }
-Значення *параметра шляху* буде елементом *перелічування*.
+Значення *параметра шляху* буде *елементом перелічування*.
-#### Порівняння *елементів перелічування*
+#### Порівняйте *елементи перелічування* { #compare-enumeration-members }
-Ви можете порівнювати його з *елементами перелічування* у створеному вами enum `ModelName`:
+Ви можете порівнювати його з *елементом перелічування* у створеному вами enum `ModelName`:
-{* ../../docs_src/path_params/tutorial005.py hl[17] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *}
-#### Отримання *значення перелічування*
+#### Отримайте *значення перелічування* { #get-the-enumeration-value }
Ви можете отримати фактичне значення (у цьому випадку це `str`), використовуючи `model_name.value`, або загалом `your_enum_member.value`:
-{* ../../docs_src/path_params/tutorial005.py hl[20] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *}
/// tip | Порада
-Ви також можете отримати доступ до значення `"lenet"`, використовуючи `ModelName.lenet.value`.
+Ви також можете отримати доступ до значення `"lenet"` через `ModelName.lenet.value`.
///
+#### Поверніть *елементи перелічування* { #return-enumeration-members }
-#### Повернення *елементів перелічування*
-
-Ви можете повертати *елементи перелічування* з вашої *операції шляху*, навіть вкладені у JSON-тіло (наприклад, `dict`).
+Ви можете повертати *елементи enum* з вашої *операції шляху*, навіть вкладені у JSON-тіло (наприклад, `dict`).
Вони будуть перетворені на відповідні значення (у цьому випадку рядки) перед поверненням клієнту:
-{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *}
-На стороні клієнта Ви отримаєте відповідь у форматі JSON, наприклад:
+На стороні клієнта ви отримаєте відповідь у форматі JSON, наприклад:
```JSON
{
@@ -207,36 +199,35 @@
}
```
-## Path-параметри, що містять шляхи
+## Параметри шляху, що містять шляхи { #path-parameters-containing-paths }
-Припустимо, у вас є *операція шляху* з маршрутом `/files/{file_path}`.
+Припустімо, у вас є *операція шляху* зі шляхом `/files/{file_path}`.
-Але вам потрібно, щоб `file_path` містив *шлях*, наприклад `home/johndoe/myfile.txt`.
+Але вам потрібно, щоб `file_path` сам містив *шлях*, наприклад `home/johndoe/myfile.txt`.
-Отже, URL для цього файлу виглядатиме так: `/files/home/johndoe/myfile.txt`.
+Отже, URL для цього файлу виглядатиме приблизно так: `/files/home/johndoe/myfile.txt`.
+### Підтримка OpenAPI { #openapi-support }
+OpenAPI не підтримує спосіб оголошення *параметра шляху*, який має містити всередині *шлях*, оскільки це може призвести до сценаріїв, які складно тестувати та визначати.
-### Підтримка OpenAPI
+Проте ви все одно можете зробити це в **FastAPI**, використовуючи один із внутрішніх інструментів Starlette.
-OpenAPI не підтримує спосіб оголошення *параметра шляху*, що містить *шлях* всередині, оскільки це може призвести до сценаріїв, які складно тестувати та визначати.
+І документація все ще працюватиме, хоча й не додаватиме жодної документації, яка б казала, що параметр має містити шлях.
-Однак (одначе), Ви все одно можете зробити це в **FastAPI**, використовуючи один із внутрішніх інструментів Starlette.
+### Конвертер шляху { #path-convertor }
-Документація все ще працюватиме, хоча й не додаватиме опису про те, що параметр повинен містити шлях.
-
-### Конвертер шляху
-
-Використовуючи опцію безпосередньо зі Starlette, Ви можете оголосити *параметр шляху*, що містить *шлях*, використовуючи URL на кшталт:
+Використовуючи опцію безпосередньо зі Starlette, ви можете оголосити *параметр шляху*, що містить *шлях*, використовуючи URL на кшталт:
```
/files/{file_path:path}
```
-У цьому випадку ім'я параметра — `file_path`, а остання частина `:path` вказує на те, що параметр повинен відповідати будь-якому *шляху*.
-Отже, Ви можете використати його так:
+У цьому випадку ім’я параметра — `file_path`, а остання частина `:path` вказує, що параметр має відповідати будь-якому *шляху*.
-{* ../../docs_src/path_params/tutorial004.py hl[6] *}
+Отже, ви можете використати його так:
+
+{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *}
/// tip | Порада
@@ -246,15 +237,15 @@ OpenAPI не підтримує спосіб оголошення *параме
///
-## Підсумок
+## Підсумок { #recap }
-З **FastAPI**, використовуючи короткі, інтуїтивно зрозумілі та стандартні оголошення типів Python, Ви отримуєте:
+З **FastAPI**, використовуючи короткі, інтуїтивно зрозумілі та стандартні оголошення типів Python, ви отримуєте:
-* Підтримку в редакторі: перевірка помилок, автодоповнення тощо.
-* "Парсинг" даних
+* Підтримку редактора: перевірка помилок, автодоповнення тощо.
+* Перетворення даних «parsing»
* Валідацію даних
* Анотацію API та автоматичну документацію
І вам потрібно оголосити їх лише один раз.
-Це, ймовірно, основна видима перевага **FastAPI** порівняно з альтернативними фреймворками (окрім високої продуктивності).
+Це, ймовірно, основна видима перевага **FastAPI** порівняно з альтернативними фреймворками (окрім сирої продуктивності).
diff --git a/docs/uk/docs/tutorial/query-param-models.md b/docs/uk/docs/tutorial/query-param-models.md
index 97eb82fa1b..a28bf6c27c 100644
--- a/docs/uk/docs/tutorial/query-param-models.md
+++ b/docs/uk/docs/tutorial/query-param-models.md
@@ -1,4 +1,4 @@
-# Моделі Query параметрів
+# Моделі параметрів запиту { #query-parameter-models }
Якщо у Вас є група **query параметрів**, які пов’язані між собою, Ви можете створити **Pydantic-модель** для їх оголошення.
@@ -10,7 +10,7 @@
///
-## Query параметри з Pydantic-моделлю
+## Query параметри з Pydantic-моделлю { #query-parameters-with-a-pydantic-model }
Оголосіть **query параметри**, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть цей параметр як `Query`:
@@ -18,7 +18,7 @@
**FastAPI** буде **витягувати** дані для **кожного поля** з **query параметрів** у запиті та передавати їх у визначену вами Pydantic-модель.
-## Перевірте документацію
+## Перевірте документацію { #check-the-docs }
Ви можете побачити параметри запиту в UI документації за `/docs`:
@@ -26,7 +26,7 @@
-## Заборона зайвих Query параметрів
+## Заборона зайвих Query параметрів { #forbid-extra-query-parameters }
У деяких особливих випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** query параметри, які дозволено отримувати.
@@ -34,7 +34,7 @@
{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *}
-Якщо клієнт спробує надіслати **зайві** дані у **query параметрах**, він отримає **помилку**.
+Якщо клієнт спробує надіслати **зайві** дані у **query параметрах**, він отримає **помилку** відповідь.
Наприклад, якщо клієнт спробує надіслати query параметр `tool` зі значенням `plumbus`, як у цьому запиті:
@@ -57,11 +57,11 @@ https://example.com/items/?limit=10&tool=plumbus
}
```
-## Підсумок
+## Підсумок { #summary }
Ви можете використовувати **Pydantic-моделі** для оголошення **query параметрів** у **FastAPI**. 😎
-/// tip | Підказка
+/// tip | Порада
Спойлер: Ви також можете використовувати Pydantic-моделі для оголошення cookie та заголовків, але про це Ви дізнаєтеся пізніше в цьому посібнику. 🤫
diff --git a/docs/uk/docs/tutorial/query-params-str-validations.md b/docs/uk/docs/tutorial/query-params-str-validations.md
index cd3f4ad935..414987880b 100644
--- a/docs/uk/docs/tutorial/query-params-str-validations.md
+++ b/docs/uk/docs/tutorial/query-params-str-validations.md
@@ -1,26 +1,26 @@
-# Query параметри та валідація рядків
+# Query параметри та валідація рядків { #query-parameters-and-string-validations }
-**FastAPI** дозволяє оголошувати додаткову інформацію та виконувати валідацію для Ваших параметрів.
+**FastAPI** дозволяє оголошувати додаткову інформацію та виконувати валідацію для ваших параметрів.
Розглянемо цей додаток як приклад:
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
-Query параметр `q` має тип `str | None`, що означає, що він може бути як `str`, так і `None`. За замовчуванням він має значення `None`, тому FastAPI розуміє, що цей параметр не є обов'язковим.
+Query параметр `q` має тип `str | None`, що означає, що він має тип `str`, але також може бути `None`, і справді, значення за замовчуванням — `None`, тож FastAPI знатиме, що він не є обов'язковим.
/// note | Примітка
-FastAPI знає, що `q` не є обов’язковим, завдяки значенню за замовчуванням `= None`.
+FastAPI знатиме, що значення `q` не є обов’язковим, завдяки значенню за замовчуванням `= None`.
-Використання `str | None` дозволить Вашому редактору коду надавати кращу підтримку та виявляти помилки.
+Використання `str | None` дозволить вашому редактору коду надавати кращу підтримку та виявляти помилки.
///
-## Додаткова валідація
+## Додаткова валідація { #additional-validation }
-Ми хочемо, щоб навіть якщо `q` є необов’язковим, **його довжина не перевищувала 50 символів**, якщо він все ж буде переданий.
+Ми хочемо, щоб навіть якщо `q` є необов’язковим, коли його передають, **його довжина не перевищувала 50 символів**.
-### Імпорт `Query` та `Annotated`
+### Імпорт `Query` та `Annotated` { #import-query-and-annotated }
Щоб це зробити, спочатку імпортуємо:
@@ -33,13 +33,13 @@ FastAPI знає, що `q` не є обов’язковим, завдяки з
FastAPI додав підтримку `Annotated` (і почав рекомендувати його) у версії 0.95.0.
-Якщо у Вас старіша версія, під час використання `Annotated` можуть виникати помилки.
+Якщо у вас старіша версія, під час використання `Annotated` можуть виникати помилки.
-Переконайтеся, що Ви [оновили версію FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} до принаймні 0.95.1, перш ніж використовувати `Annotated`.
+Переконайтеся, що ви [оновили версію FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} до принаймні 0.95.1, перш ніж використовувати `Annotated`.
///
-## Використання `Annotated` у типі параметра `q`
+## Використання `Annotated` у типі параметра `q` { #use-annotated-in-the-type-for-the-q-parameter }
Пам’ятаєте, як я раніше розповідав, що `Annotated` можна використовувати для додавання метаданих до параметрів у [Вступі до типів Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}?
@@ -55,7 +55,7 @@ q: str | None = None
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
q: Union[str, None] = None
@@ -73,7 +73,7 @@ q: Annotated[str | None] = None
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
q: Annotated[Union[str, None]] = None
@@ -85,33 +85,33 @@ q: Annotated[Union[str, None]] = None
А тепер переходимо до цікавого! 🎉
-## Додавання `Query` до `Annotated` у параметр `q`
+## Додавання `Query` до `Annotated` у параметр `q` { #add-query-to-annotated-in-the-q-parameter }
-Тепер, коли у нас є `Annotated`, де ми можемо додавати додаткову інформацію (зокрема валідацію), додамо `Query` всередину `Annotated` і встановимо параметр `max_length` у `50`:
+Тепер, коли у нас є `Annotated`, де ми можемо додавати додаткову інформацію (у цьому випадку — додаткову валідацію), додамо `Query` всередину `Annotated` і встановимо параметр `max_length` у `50`:
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *}
Зверніть увагу, що значення за замовчуванням усе ще `None`, тому параметр залишається необов'язковим.
-Але тепер, додавши `Query(max_length=50)` всередину `Annotated`, ми повідомляємо FastAPI, що хочемо **додаткову валідацію** для цього значення — воно має містити максимум 50 символів. 😎
+Але тепер, додавши `Query(max_length=50)` всередину `Annotated`, ми повідомляємо FastAPI, що хочемо **додаткову валідацію** для цього значення: ми хочемо, щоб воно мало максимум 50 символів. 😎
-/// tip | Підказка
+/// tip | Порада
-Ми використовуємо `Query()`, оскільки це **query параметр**. Далі ми розглянемо інші варіанти, як-от `Path()`, `Body()`, `Header()` та `Cookie()`, які приймають ті самі аргументи, що й `Query()`.
+Тут ми використовуємо `Query()`, оскільки це **query параметр**. Далі ми розглянемо інші варіанти, як-от `Path()`, `Body()`, `Header()` та `Cookie()`, які приймають ті самі аргументи, що й `Query()`.
///
Тепер FastAPI:
-* **Перевірить** дані, щоб переконатися, що їхня довжина не перевищує 50 символів
+* **Перевірить** дані, щоб переконатися, що їхня максимальна довжина — 50 символів
* Покажe **чітку помилку** клієнту, якщо дані недійсні
* **Задокументує** параметр в OpenAPI-схемі *операції шляху* (що відобразиться в **автоматично згенерованій документації**)
-## Альтернативний (застарілий) метод: Query як значення за замовчуванням
+## Альтернативний (застарілий) метод: `Query` як значення за замовчуванням { #alternative-old-query-as-the-default-value }
-У попередніх версіях FastAPI (до 0.95.0) `Query` використовувався як значення за замовчуванням для параметра, а не всередині `Annotated`. Ви, ймовірно, побачите код, який використовує цей підхід, тому варто розглянути його.
+У попередніх версіях FastAPI (до 0.95.0) потрібно було використовувати `Query` як значення за замовчуванням параметра, замість того, щоб додавати його в `Annotated`. Є висока ймовірність, що ви зустрінете код із таким підходом, тож я поясню його.
-/// tip | Підказка
+/// tip | Порада
Для нового коду та коли це можливо, використовуйте `Annotated`, як показано вище. Це має багато переваг (пояснених нижче) і не має недоліків. 🍰
@@ -121,7 +121,7 @@ q: Annotated[Union[str, None]] = None
{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *}
-Оскільки в цьому випадку (без `Annotated`) нам потрібно замінити `None` у функції на `Query()`, тепер ми повинні явно встановити значення за замовчуванням через параметр `Query(default=None)`. Це виконує ту саму роль визначення значення за замовчуванням (принаймні для FastAPI).
+Оскільки в цьому випадку (без `Annotated`) нам потрібно замінити значення за замовчуванням `None` у функції на `Query()`, тепер ми повинні встановити значення за замовчуванням через параметр `Query(default=None)`. Це виконує ту саму роль визначення значення за замовчуванням (принаймні для FastAPI).
Таким чином:
@@ -135,9 +135,10 @@ q: str | None = Query(default=None)
```Python
q: str | None = None
```
-Але у версії з `Query` ми явно вказуємо, що це query параметр.
-Далі ми можемо передавати `Query` додаткові параметри, зокрема `max_length`, який застосовується до рядків:
+Але у версії з `Query` ми явно вказуємо, що це query параметр.
+
+Далі ми можемо передавати `Query` додаткові параметри. У цьому випадку — параметр `max_length`, який застосовується до рядків:
```Python
q: str | None = Query(default=None, max_length=50)
@@ -145,11 +146,11 @@ q: str | None = Query(default=None, max_length=50)
Це забезпечить валідацію даних, виведе зрозумілу помилку у разі недійсних даних і задокументує параметр у схемі OpenAPI *операції шляху*.
-### `Query` як значення за замовчуванням або всередині `Annotated`
+### `Query` як значення за замовчуванням або всередині `Annotated` { #query-as-the-default-value-or-in-annotated }
Важливо пам’ятати, якщо використовувати `Query` всередині `Annotated`, не можна задавати параметр `default` у `Query`.
-Замість цього використовуйте значення за замовчуванням у самій функції. Інакше це буде нелогічно.
+Замість цього використовуйте фактичне значення за замовчуванням параметра функції. Інакше це буде непослідовно.
Наприклад, цей варіант є некоректним:
@@ -159,39 +160,39 @@ q: Annotated[str, Query(default="rick")] = "morty"
...тому, що не зрозуміло, яке значення має бути значенням за замовчуванням: `"rick"` чи `"morty"`.
-Коректні варіанти:
+Тож ви будете використовувати (бажано):
```Python
q: Annotated[str, Query()] = "rick"
```
-...або у старих кодових базах Ви знайдете:
+...або у старих кодових базах ви знайдете:
```Python
q: str = Query(default="rick")
```
-### Переваги використання `Annotated`
+### Переваги використання `Annotated` { #advantages-of-annotated }
**Використання `Annotated` є рекомендованим** замість задання значення за замовчуванням у параметрах функції, оскільки воно **краще** з кількох причин. 🤓
-Значення **за замовчуванням** параметра **функції** є його **фактичним значенням за замовчуванням**, що є більш інтуїтивним у Python загалом. 😌
+Значення **за замовчуванням** параметра **функції** є **фактичним значенням за замовчуванням**, що є більш інтуїтивним у Python загалом. 😌
-Ви можете **викликати** ту саму функцію **в інших місцях** без FastAPI, і вона **працюватиме очікувано**. Якщо параметр є **обов’язковим** (без значення за замовчуванням), Ваш **редактор** повідомить про помилку, а **Python** також видасть помилку, якщо Ви виконаєте функцію без передавання цього параметра.
+Ви можете **викликати** ту саму функцію **в інших місцях** без FastAPI, і вона **працюватиме очікувано**. Якщо параметр є **обов’язковим** (без значення за замовчуванням), ваш **редактор** повідомить про помилку, а **Python** також видасть помилку, якщо ви виконаєте функцію без передавання цього параметра.
-Якщо Ви не використовуєте `Annotated`, а використовуєте **(старий) стиль значень за замовчуванням**, то при виклику цієї функції без FastAPI **в інших місцях**, потрібно **не забути** передати їй аргументи, інакше значення будуть відрізнятися від очікуваних (наприклад, Ви отримаєте `QueryInfo` або подібне замість `str`). Ваш редактор не повідомить про помилку, і Python також не видасть помилку при запуску функції, поки не виникне помилка під час виконання операцій усередині.
+Якщо ви не використовуєте `Annotated`, а використовуєте **(старий) стиль значень за замовчуванням**, то при виклику цієї функції без FastAPI **в інших місцях**, потрібно **пам’ятати** передати їй аргументи, щоб вона працювала коректно, інакше значення будуть відрізнятися від очікуваних (наприклад, ви отримаєте `QueryInfo` або щось подібне замість `str`). І ваш редактор не повідомить про помилку, і Python не скаржитиметься під час запуску цієї функції — лише коли операції всередині завершаться помилкою.
-Оскільки `Annotated` може містити кілька анотацій метаданих, Ви навіть можете використовувати ту саму функцію з іншими інструментами, такими як Typer. 🚀
+Оскільки `Annotated` може містити кілька анотацій метаданих, тепер ви навіть можете використовувати ту саму функцію з іншими інструментами, такими як Typer. 🚀
-## Додавання додаткових валідацій
+## Додавання додаткових валідацій { #add-more-validations }
Ви також можете додати параметр `min_length`:
{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *}
-## Додавання регулярних виразів
+## Додавання регулярних виразів { #add-regular-expressions }
-Ви можете визначити регулярний вираз pattern, якому має відповідати параметр:
+Ви можете визначити regular expression `pattern`, якому має відповідати параметр:
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
@@ -201,41 +202,27 @@ q: str = Query(default="rick")
* `fixedquery`: точно відповідає значенню `fixedquery`.
* `$`: закінчується тут, після `fixedquery` немає жодних символів.
-Якщо Ви почуваєтеся розгублено щодо **"регулярних виразів"**, не хвилюйтеся. Вони є складною темою для багатьох людей. Ви все одно можете зробити багато речей без їх використання.
+Якщо ви почуваєтеся розгублено щодо **«regular expression»**, не хвилюйтеся. Це складна тема для багатьох людей. Ви все одно можете робити багато речей без використання регулярних виразів.
-Але тепер Ви знаєте, що коли вони знадобляться, їх можна застосовувати у **FastAPI**.
+Тепер ви знаєте, що коли вони знадобляться, їх можна застосовувати у **FastAPI**.
-### Pydantic v1 `regex` замість `pattern`
+## Значення за замовчуванням { #default-values }
-До версії Pydantic 2 і FastAPI 0.100.0 параметр називався `regex` замість `pattern`, але тепер він застарів.
+Ви можете, звісно, використовувати значення за замовчуванням, відмінні від `None`.
-Ви все ще можете зустріти код, який використовує його:
-
-//// tab | Pydantic v1
-
-{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *}
-
-////
-
-Але майте на увазі, що він є застарілим і його слід оновити до нового параметра `pattern`. 🤓
-
-## Значення за замовчуванням
-
-Ви можете використовувати значення за замовчуванням, відмінні від `None`.
-
-Наприклад, якщо Ви хочете оголосити параметр запиту `q` з `min_length` `3` і значенням за замовчуванням `"fixedquery"`:
+Припустімо, що ви хочете оголосити query параметр `q` з `min_length` `3` і значенням за замовчуванням `"fixedquery"`:
{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *}
-/// note | Технічні деталі
+/// note | Примітка
Наявність значення за замовчуванням будь-якого типу, включаючи `None`, робить параметр необов’язковим (not required).
///
-## Обов’язкові параметри
+## Обов’язкові параметри { #required-parameters }
-Якщо нам не потрібно вказувати додаткові перевірки або метадані, ми можемо зробити параметр `q` обов’язковим, просто не оголошуючи значення за замовчуванням, наприклад:
+Якщо нам не потрібно оголошувати додаткові валідації або метадані, ми можемо зробити query параметр `q` обов’язковим, просто не вказуючи значення за замовчуванням, наприклад:
```Python
q: str
@@ -247,43 +234,39 @@ q: str
q: str | None = None
```
-Але тепер ми оголошуємо його з `Query`, наприклад:
-
-//// tab | Annotated
+Але тепер ми оголошуємо його з `Query`, наприклад так:
```Python
q: Annotated[str | None, Query(min_length=3)] = None
```
-////
-
-Тому, якщо Вам потрібно зробити значення обов’язковим, використовуючи `Query`, просто не вказуйте значення за замовчуванням:
+Тому, якщо вам потрібно оголосити значення як обов’язкове під час використання `Query`, просто не вказуйте значення за замовчуванням:
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
-### Обов’язкове значення, яке може бути `None`
+### Обов’язковий, може бути `None` { #required-can-be-none }
-Ви можете вказати, що параметр може приймати `None`, але при цьому залишається обов’язковим. Це змусить клієнтів надіслати значення, навіть якщо воно дорівнює `None`.
+Ви можете вказати, що параметр може приймати `None`, але при цьому залишається обов’язковим. Це змусить клієнтів надіслати значення, навіть якщо значення дорівнює `None`.
-Щоб зробити це, оголосіть, що `None` є допустимим типом, але не вказуйте значення за замовчуванням:
+Щоб зробити це, оголосіть, що `None` є допустимим типом, але просто не вказуйте значення за замовчуванням:
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
-## Список параметрів запиту / кілька значень
+## Список query параметрів / кілька значень { #query-parameter-list-multiple-values }
-Якщо Ви визначаєте параметр запиту за допомогою `Query`, Ви також можете дозволити отримання списку значень, тобто дозволити отримання кількох значень.
+Коли ви явно визначаєте query параметр за допомогою `Query`, ви також можете оголосити, що він має приймати список значень, або, іншими словами, кілька значень.
-Наприклад, щоб дозволити параметру запиту `q` з'являтися кілька разів в URL, можна написати:
+Наприклад, щоб оголосити query параметр `q`, який може з’являтися в URL кілька разів, можна написати:
{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *}
-Тоді, у випадку запиту за URL:
+Тоді, у випадку URL:
```
http://localhost:8000/items/?q=foo&q=bar
```
-Ви отримаєте кілька значень *query параметра* `q` (`foo` і `bar`) у вигляді списку `list` в Python у Вашій *функції обробки шляху*, у *параметрі функції* `q`.
+ви отримаєте кілька значень `q` *query параметрів* (`foo` і `bar`) у вигляді Python `list` у вашій *функції операції шляху*, у *параметрі функції* `q`.
Отже, відповідь на цей URL буде:
@@ -296,9 +279,9 @@ http://localhost:8000/items/?q=foo&q=bar
}
```
-/// tip | Підказка
+/// tip | Порада
-Щоб оголосити параметр запиту з типом `list`, як у наведеному вище прикладі, потрібно явно використовувати `Query`, інакше він буде інтерпретований як тіло запиту.
+Щоб оголосити query параметр з типом `list`, як у наведеному вище прикладі, потрібно явно використовувати `Query`, інакше він буде інтерпретований як тіло запиту.
///
@@ -306,19 +289,19 @@ http://localhost:8000/items/?q=foo&q=bar
-### Список параметрів запиту / кілька значень за замовчуванням
+### Список query параметрів / кілька значень за замовчуванням { #query-parameter-list-multiple-values-with-defaults }
-Ви також можете визначити значення за замовчуванням для `list`, якщо жодне значення не було передане:
+Ви також можете визначити значення за замовчуванням `list`, якщо жодне значення не було передане:
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
-Якщо Ви перейдете за посиланням:
+Якщо ви перейдете за посиланням:
```
http://localhost:8000/items/
```
-то значення `q` за замовчуванням буде: `["foo", "bar"]`, і Ваша відповідь виглядатиме так:
+то значення `q` за замовчуванням буде: `["foo", "bar"]`, і ваша відповідь виглядатиме так:
```JSON
{
@@ -329,35 +312,35 @@ http://localhost:8000/items/
}
```
-#### Використання тільки `list`
+#### Використання тільки `list` { #using-just-list }
-Ви також можете використовувати `list` без уточнення типу, замість `list[str]`:
+Ви також можете використовувати `list` напряму замість `list[str]`:
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
-/// note | Технічні деталі
+/// note | Примітка
Майте на увазі, що в цьому випадку FastAPI не перевірятиме вміст списку.
-Наприклад, `list[int]` перевірятиме (і документуватиме), що всі елементи списку є цілими числами. Але `list` без уточнення цього не робитиме.
+Наприклад, `list[int]` перевірятиме (і документуватиме), що вміст списку — цілі числа. Але `list` без уточнення цього не робитиме.
///
-## Додавання додаткових метаданих
+## Оголосити більше метаданих { #declare-more-metadata }
Ви можете додати більше інформації про параметр.
-Ця інформація буде включена у згенерований OpenAPI та використана в інтерфейсах документації та зовнішніх інструментах.
+Ця інформація буде включена у згенерований OpenAPI та використана інтерфейсами документації та зовнішніми інструментами.
-/// note | Технічні деталі
+/// note | Примітка
Майте на увазі, що різні інструменти можуть мати різний рівень підтримки OpenAPI.
-Деякі з них можуть ще не відображати всю додаткову інформацію, хоча в більшості випадків ця функція вже запланована для розробки.
+Деякі з них можуть ще не відображати всю додаткову інформацію, хоча в більшості випадків відсутню функцію вже заплановано до реалізації.
///
-Ви можете додати `title` :
+Ви можете додати `title`:
{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *}
@@ -365,9 +348,9 @@ http://localhost:8000/items/
{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
-## Аліаси параметрів
+## Аліаси параметрів { #alias-parameters }
-Уявіть, що Ви хочете, щоб параметр називався `item-query`.
+Уявіть, що ви хочете, щоб параметр називався `item-query`.
Наприклад:
@@ -379,19 +362,19 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
Найближчий допустимий варіант — `item_query`.
-Проте Вам потрібно, щоб параметр залишався саме `item-query`...
+Проте вам потрібно, щоб параметр залишався саме `item-query`...
У такому випадку можна оголосити `alias`, і саме він буде використовуватися для отримання значення параметра:
{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
-## Виведення параметрів як застарілих
+## Позначення параметрів як застарілих { #deprecating-parameters }
-Припустимо, що Ви більше не хочете використовувати цей параметр.
+Припустімо, що вам більше не подобається цей параметр.
-Вам потрібно залишити його на деякий час, оскільки ним користуються клієнти, але Ви хочете, щоб документація чітко показувала, що він є застарілим.
+Вам потрібно залишити його на деякий час, оскільки ним користуються клієнти, але ви хочете, щоб документація чітко показувала, що він є deprecated.
-Тоді Ви можете передати параметр `deprecated=True` до `Query`:
+Тоді передайте параметр `deprecated=True` до `Query`:
{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *}
@@ -399,27 +382,27 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
-## Виняток параметрів з OpenAPI
+## Виняток параметрів з OpenAPI { #exclude-parameters-from-openapi }
-Щоб виключити параметр запиту зі згенерованої схеми OpenAPI (і, таким чином, з автоматичних систем документації), встановіть параметр `include_in_schema` для `Query` в `False`:
+Щоб виключити query параметр зі згенерованої схеми OpenAPI (і, таким чином, з автоматичних систем документації), встановіть параметр `include_in_schema` для `Query` в `False`:
{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *}
-## Кастомна валідація
+## Кастомна валідація { #custom-validation }
-Можуть бути випадки, коли Вам потрібно провести **кастомну валідацію**, яку не можна реалізувати за допомогою параметрів, показаних вище.
+Можуть бути випадки, коли вам потрібно провести **кастомну валідацію**, яку не можна реалізувати за допомогою параметрів, показаних вище.
-У таких випадках ви можете використати **кастомну функцію валідації**, яка буде застосована після звичайної валідації (наприклад, після перевірки, що значення є типом `str`).
+У таких випадках ви можете використати **кастомну функцію-валідатор**, яка буде застосована після звичайної валідації (наприклад, після перевірки, що значення є типом `str`).
Це можна досягти за допомогою Pydantic's `AfterValidator` в середині `Annotated`.
-/// tip | Підказка
+/// tip | Порада
Pydantic також має `BeforeValidator` та інші. 🤓
///
-Наприклад, цей кастомний валідатор перевіряє, чи починається ID елемента з `isbn-` для номера книги ISBN або з `imdb-` для ID URL фільму на IMDB:
+Наприклад, цей кастомний валідатор перевіряє, чи починається ID елемента з `isbn-` для номера книги ISBN або з `imdb-` для ID URL фільму на IMDB:
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
@@ -429,49 +412,49 @@ Pydantic також має ітерабельний об'єкт із кортежами, що містять ключ і значення для кожного елемента словника.
+За допомогою `data.items()` ми отримуємо iterable object із кортежами, що містять ключ і значення для кожного елемента словника.
-Ми перетворюємо цей ітерабельний об'єкт у звичайний `list` за допомогою `list(data.items())`.
+Ми перетворюємо цей iterable object у звичайний `list` за допомогою `list(data.items())`.
-Потім, використовуючи `random.choice()`, ми можемо отримати випадкове значення зі списку, тобто отримуємо кортеж із `(id, name)`. Це може бути щось на зразок `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`.
+Потім, використовуючи `random.choice()`, ми можемо отримати **випадкове значення** зі списку, тобто отримуємо кортеж із `(id, name)`. Це може бути щось на зразок `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`.
Далі ми **присвоюємо ці два значення** кортежу змінним `id` і `name`.
Тож, якщо користувач не вказав ID елемента, він все одно отримає випадкову рекомендацію.
-...і все це реалізовано в **одному рядку коду**. 🤯 Хіба не прекрасний Python? 🐍
+...ми робимо все це в **одному простому рядку**. 🤯 Хіба ви не любите Python? 🐍
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *}
-## Підсумок
+## Підсумок { #recap }
-Ви можете оголошувати додаткові валідації та метаінформацію для своїх параметрів.
+Ви можете оголошувати додаткові валідації та метадані для ваших параметрів.
-Загальні валідації та метаінформація:
+Загальні валідації та метадані:
* `alias`
* `title`
@@ -486,6 +469,6 @@ Pydantic також має "Парсинг" даних
+* Підтримка в редакторі (очевидно)
+* «parsing» даних
* Валідація даних
* Автоматична документація
+## Значення за замовчуванням { #defaults }
-## Значення за замовчуванням
-
-Оскільки query параметри не є фіксованою частиною шляху, вони можуть бути необов’язковими та мати значення за замовчуванням.
+Оскільки параметри query не є фіксованою частиною шляху, вони можуть бути необов’язковими та мати значення за замовчуванням.
У наведеному вище прикладі вони мають значення за замовчуванням: `skip=0` і `limit=10`.
-Отже, результат переходу за URL:
+Отже, перехід за URL:
```
http://127.0.0.1:8000/items/
```
+
буде таким самим, як і перехід за посиланням:
```
http://127.0.0.1:8000/items/?skip=0&limit=10
```
-Але якщо Ви перейдете, наприклад, за посиланням:
+Але якщо ви перейдете, наприклад, за посиланням:
```
http://127.0.0.1:8000/items/?skip=20
@@ -54,12 +54,12 @@ http://127.0.0.1:8000/items/?skip=20
Значення параметрів у вашій функції будуть такими:
-* `skip=20`: оскільки Ви вказали його в URL
+* `skip=20`: оскільки ви вказали його в URL
* `limit=10`: оскільки це значення за замовчуванням
-## Необов'язкові параметри
+## Необов'язкові параметри { #optional-parameters }
-Аналогічно, Ви можете оголосити необов’язкові query параметри, встановивши для них значення за замовчуванням `None`:
+Так само ви можете оголосити необов’язкові параметри query, встановивши для них значення за замовчуванням `None`:
{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *}
@@ -67,18 +67,17 @@ http://127.0.0.1:8000/items/?skip=20
/// check | Примітка
-Також зверніть увагу, що **FastAPI** достатньо розумний, щоб визначити, що параметр шляху `item_id` є параметром шляху, а `q` — ні, отже, це query параметр.
+Також зверніть увагу, що **FastAPI** достатньо розумний, щоб визначити, що параметр шляху `item_id` є параметром шляху, а `q` — ні, отже, це параметр query.
///
-## Перетворення типу Query параметра
+## Перетворення типу параметра query { #query-parameter-type-conversion }
Ви також можете оголошувати параметри типу `bool`, і вони будуть автоматично конвертовані:
{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *}
-У цьому випадку, якщо Ви звернетесь до:
-
+У цьому випадку, якщо ви перейдете за:
```
http://127.0.0.1:8000/items/foo?short=1
@@ -108,38 +107,38 @@ http://127.0.0.1:8000/items/foo?short=on
http://127.0.0.1:8000/items/foo?short=yes
```
-або будь-який інший варіант написання (великі літери, перша літера велика тощо), ваша функція побачить параметр `short` зі значенням `True` з типом даних `bool`. В іншому випадку – `False`.
-
-## Кілька path і query параметрів
-
-Ви можете одночасно оголошувати кілька path і query параметрів, і **FastAPI** автоматично визначить, який з них до чого належить.
+або будь-який інший варіант написання (великі літери, перша літера велика тощо), ваша функція побачить параметр `short` зі значенням `True` типу `bool`. В іншому випадку — `False`.
-Не потрібно дотримуватись певного порядку їх оголошення.
+## Кілька path і query параметрів { #multiple-path-and-query-parameters }
+
+Ви можете одночасно оголошувати кілька параметрів шляху та параметрів query, **FastAPI** знає, який з них який.
+
+І вам не потрібно оголошувати їх у якомусь конкретному порядку.
Вони визначаються за назвою:
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
-## Обов’язкові Query параметри
+## Обов’язкові параметри query { #required-query-parameters }
-Якщо Ви оголошуєте значення за замовчуванням для параметрів, які не є path-параметрами (у цьому розділі ми бачили поки що лише path параметри), тоді вони стають необов’язковими.
+Коли ви оголошуєте значення за замовчуванням для не-path-параметрів (поки що ми бачили лише параметри query), тоді вони не є обов’язковими.
-Якщо Ви не хочете вказувати конкретні значення, але хочете зробити параметр опціональним, задайте `None` як значення за замовчуванням.
+Якщо ви не хочете задавати конкретне значення, а просто зробити параметр необов’язковим, задайте `None` як значення за замовчуванням.
-Але якщо Ви хочете зробити query параметр обов’язковим, просто не вказуйте для нього значення за замовчуванням:
+Але якщо ви хочете зробити параметр query обов’язковим, просто не вказуйте для нього значення за замовчуванням:
-{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
+{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
-Тут `needy` – обов’язковий query параметр типу `str`.
+Тут параметр query `needy` — обов’язковий параметр query типу `str`.
-Якщо Ви відкриєте у браузері URL-адресу:
+Якщо ви відкриєте у браузері URL-адресу:
```
http://127.0.0.1:8000/items/foo-item
```
-...без додавання обов’язкового параметра `needy`, Ви побачите помилку:
+...без додавання обов’язкового параметра `needy`, ви побачите помилку на кшталт:
```JSON
{
@@ -163,7 +162,7 @@ http://127.0.0.1:8000/items/foo-item
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
```
-...цей запит поверне:
+...це спрацює:
```JSON
{
@@ -172,20 +171,18 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
}
```
-
-Звичайно, Ви можете визначити деякі параметри як обов’язкові, інші зі значенням за замовчуванням, а ще деякі — повністю опціональні:
+І звісно, ви можете визначити деякі параметри як обов’язкові, деякі — зі значенням за замовчуванням, а деякі — повністю необов’язкові:
{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *}
-У цьому випадку є 3 query параметри:
+У цьому випадку є 3 параметри query:
* `needy`, обов’язковий `str`.
* `skip`, `int` зі значенням за замовчуванням `0`.
-* `limit`, опціональний `int`.
+* `limit`, необов’язковий `int`.
+/// tip | Порада
-/// tip | Підказка
-
-Ви також можете використовувати `Enum`-и, так само як і з [Path Parameters](path-params.md#predefined-values){.internal-link target=_blank}.
+Ви також можете використовувати `Enum` так само, як і з [Path Parameters](path-params.md#predefined-values){.internal-link target=_blank}.
///
diff --git a/docs/uk/docs/tutorial/request-files.md b/docs/uk/docs/tutorial/request-files.md
index 18b7cc01c9..a6ff70dc09 100644
--- a/docs/uk/docs/tutorial/request-files.md
+++ b/docs/uk/docs/tutorial/request-files.md
@@ -1,56 +1,56 @@
-# Запит файлів
+# Запит файлів { #request-files }
Ви можете визначити файли, які будуть завантажуватися клієнтом, використовуючи `File`.
/// info | Інформація
-Щоб отримувати завантажені файли, спочатку встановіть python-multipart.
+Щоб отримувати завантажені файли, спочатку встановіть `python-multipart`.
-Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його та встановили пакет, наприклад:
+Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили пакет, наприклад:
```console
$ pip install python-multipart
```
-Це необхідно, оскільки завантажені файли передаються у вигляді "форматованих даних форми".
+Це необхідно, оскільки завантажені файли передаються у вигляді «form data».
///
-## Імпорт `File`
+## Імпорт `File` { #import-file }
Імпортуйте `File` та `UploadFile` з `fastapi`:
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
-## Визначення параметрів `File`
+## Визначення параметрів `File` { #define-file-parameters }
-Створіть параметри файлів так само як Ви б створювали `Body` або `Form`:
+Створіть параметри файлів так само як ви б створювали `Body` або `Form`:
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *}
/// info | Інформація
-`File` — це клас, який безпосередньо успадковує `Form`.
+`File` — це клас, який безпосередньо успадковує `Form`.
-Але пам’ятайте, що коли Ви імпортуєте `Query`, `Path`, `File` та інші з `fastapi`, це насправді функції, які повертають спеціальні класи.
+Але пам’ятайте, що коли ви імпортуєте `Query`, `Path`, `File` та інші з `fastapi`, це насправді функції, які повертають спеціальні класи.
///
-/// tip | Підказка
+/// tip | Порада
-Щоб оголосити тіла файлів, Вам потрібно використовувати `File`, тому що інакше параметри будуть інтерпретовані як параметри запиту або параметри тіла (JSON).
+Щоб оголосити тіла файлів, вам потрібно використовувати `File`, тому що інакше параметри будуть інтерпретовані як параметри запиту або параметри тіла (JSON).
///
-Файли будуть завантажені у вигляді "форматованих даних форми".
+Файли будуть завантажені у вигляді «form data».
-Якщо Ви оголосите тип параметра функції обробника маршруту як `bytes`, **FastAPI** прочитає файл за Вас, і Ви отримаєте його вміст у вигляді `bytes`.
+Якщо ви оголосите тип параметра *функції операції шляху* як `bytes`, **FastAPI** прочитає файл за вас, і ви отримаєте його вміст у вигляді `bytes`.
-Однак майте на увазі, що весь вміст буде збережено в пам'яті. Це працюватиме добре для малих файлів.
+Майте на увазі, що це означає, що весь вміст буде збережено в пам'яті. Це працюватиме добре для малих файлів.
-Але в деяких випадках Вам може знадобитися `UploadFile`.
+Але є кілька випадків, у яких вам може бути корисно використовувати `UploadFile`.
-## Параметри файлу з `UploadFile`
+## Параметри файлу з `UploadFile` { #file-parameters-with-uploadfile }
Визначте параметр файлу з типом `UploadFile`:
@@ -59,38 +59,39 @@ $ pip install python-multipart
Використання `UploadFile` має кілька переваг перед `bytes`:
* Вам не потрібно використовувати `File()` у значенні за замовчуванням параметра.
-* Використовується "буферизований" файл:
- * Файл зберігається в пам'яті до досягнення певного обмеження, після чого він записується на диск.
-* Це означає, що він добре працює для великих файлів, таких як зображення, відео, великі двійкові файли тощо, не споживаючи всю пам'ять.
-Ви можете отримати метадані про завантажений файл.
-* Він має file-like `асинхронний файловий інтерфейс` interface.
-* Він надає фактичний об'єкт Python `SpooledTemporaryFile`, який можна передавати безпосередньо іншим бібліотекам.
+* Використовується «spooled» файл:
+ * Файл зберігається в пам'яті до досягнення максимального обмеження розміру, після чого він буде збережений на диску.
+* Це означає, що він добре працюватиме для великих файлів, таких як зображення, відео, великі двійкові файли тощо, не споживаючи всю пам'ять.
+* Ви можете отримати метадані про завантажений файл.
+* Він має file-like `async` інтерфейс.
+* Він надає фактичний об'єкт Python `SpooledTemporaryFile`, який можна передавати безпосередньо іншим бібліотекам, що очікують file-like об'єкт.
-### `UploadFile`
+### `UploadFile` { #uploadfile }
`UploadFile` має такі атрибути:
* `filename`: Рядок `str` з оригінальною назвою файлу, який був завантажений (наприклад, `myimage.jpg`).
-* `content_type`: Рядок `str` з MIME-типом (наприклад, `image/jpeg`).
-* `file`: Об'єкт SpooledTemporaryFile (файлоподібний об'єкт). Це фактичний файловий об'єкт Python, який можна безпосередньо передавати іншим функціям або бібліотекам, що очікують "файлоподібний" об'єкт.
+* `content_type`: Рядок `str` з типом вмісту (MIME type / media type) (наприклад, `image/jpeg`).
+* `file`: `SpooledTemporaryFile` (file-like об'єкт). Це фактичний файловий об'єкт Python, який ви можете передавати безпосередньо іншим функціям або бібліотекам, що очікують «file-like» об'єкт.
-`UploadFile` має такі асинхронні `async` методи. Вони викликають відповідні методи файлу під капотом (використовуючи внутрішній `SpooledTemporaryFile`).
+`UploadFile` має такі асинхронні `async` методи. Вони всі викликають відповідні методи файлу під капотом (використовуючи внутрішній `SpooledTemporaryFile`).
* `write(data)`: Записує `data` (`str` або `bytes`) у файл.
* `read(size)`: Читає `size` (`int`) байтів/символів з файлу.
-* `seek(offset)`: Переміщується до позиції `offset` (`int`) у файлі.
- * Наприклад, `await myfile.seek(0)` поверне курсор на початок файлу.
- * This is especially useful if you run `await myfile.read()` once and then need to read the contents again. Це особливо корисно, якщо Ви виконуєте await `await myfile.read()` один раз, а потім потрібно знову прочитати вміст.
+* `seek(offset)`: Переходить до байтової позиції `offset` (`int`) у файлі.
+ * Наприклад, `await myfile.seek(0)` перейде на початок файлу.
+ * Це особливо корисно, якщо ви виконаєте `await myfile.read()` один раз, а потім потрібно знову прочитати вміст.
* `close()`: Закриває файл.
-Оскільки всі ці методи є асинхронними `async`, Вам потрібно використовувати "await":
+Оскільки всі ці методи є асинхронними `async` методами, вам потрібно їх «await»-ити.
-Наприклад, всередині `async` *функції обробки шляху* Ви можете отримати вміст за допомогою:
+Наприклад, всередині `async` *функції операції шляху* ви можете отримати вміст за допомогою:
```Python
contents = await myfile.read()
```
-Якщо Ви знаходитесь у звичайній `def` *функції обробки шляху*, Ви можете отримати доступ до `UploadFile.file` безпосередньо, наприклад:
+
+Якщо ви знаходитесь у звичайній `def` *функції операції шляху*, ви можете отримати доступ до `UploadFile.file` безпосередньо, наприклад:
```Python
contents = myfile.file.read()
@@ -98,57 +99,57 @@ contents = myfile.file.read()
/// note | Технічні деталі `async`
-Коли Ви використовуєте `async` методи, **FastAPI** виконує файлові операції у пулі потоків та очікує їх завершення.
+Коли ви використовуєте `async` методи, **FastAPI** виконує файлові методи у пулі потоків і очікує на них.
///
/// note | Технічні деталі Starlette
-`UploadFile` у **FastAPI** успадковується безпосередньо від `UploadFile` у **Starlette**, але додає деякі необхідні частини, щоб зробити його сумісним із **Pydantic** та іншими компонентами FastAPI.
+`UploadFile` у **FastAPI** успадковується безпосередньо від `UploadFile` у **Starlette**, але додає деякі необхідні частини, щоб зробити його сумісним із **Pydantic** та іншими частинами FastAPI.
///
-## Що таке "Form Data"
+## Що таке «Form Data» { #what-is-form-data }
-Спосіб, у який HTML-форми (``) надсилають дані на сервер, зазвичай використовує "спеціальне" кодування, відмінне від JSON.
+Спосіб, у який HTML-форми (``) надсилають дані на сервер, зазвичай використовує «спеціальне» кодування для цих даних, відмінне від JSON.
-**FastAPI** забезпечує правильне зчитування цих даних з відповідної частини запиту, а не з JSON.
+**FastAPI** забезпечить зчитування цих даних з правильного місця, а не з JSON.
/// note | Технічні деталі
-Дані з форм зазвичай кодуються за допомогою "media type" `application/x-www-form-urlencoded`, якщо вони не містять файлів.
+Дані з форм зазвичай кодуються за допомогою «media type» `application/x-www-form-urlencoded`, якщо вони не містять файлів.
-Але якщо форма містить файли, вона кодується у форматі `multipart/form-data`. Якщо Ви використовуєте `File`, **FastAPI** визначить, що потрібно отримати файли з відповідної частини тіла запиту.
+Але якщо форма містить файли, вона кодується як `multipart/form-data`. Якщо ви використовуєте `File`, **FastAPI** знатиме, що потрібно отримати файли з правильної частини тіла.
-Щоб дізнатися більше про ці типи кодування та формові поля, ознайомтеся з документацією MDN щодо POST.
+Якщо ви хочете дізнатися більше про ці типи кодування та формові поля, ознайомтеся з MDN web docs для POST.
///
-/// warning | Увага
+/// warning | Попередження
-Ви можете оголосити кілька параметрів `File` і `Form` в *операції шляху*, але Ви не можете одночасно оголошувати поля `Body`, які мають надходити у форматі JSON, оскільки тіло запиту буде закодоване у форматі `multipart/form-data`, а не `application/json`.
+Ви можете оголосити кілька параметрів `File` і `Form` в *операції шляху*, але ви не можете одночасно оголошувати поля `Body`, які ви очікуєте отримати як JSON, оскільки запит матиме тіло, закодоване як `multipart/form-data`, а не `application/json`.
-Це не обмеження **FastAPI**, а особливість протоколу HTTP.
+Це не обмеження **FastAPI**, а частина протоколу HTTP.
///
-## Опціональне Завантаження Файлів
+## Необов’язкове завантаження файлу { #optional-file-upload }
-Файл можна зробити необов’язковим, використовуючи стандартні анотації типів і встановлюючи значення за замовчуванням `None`:
+Ви можете зробити файл необов’язковим, використовуючи стандартні анотації типів і встановивши значення за замовчуванням `None`:
{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
-## `UploadFile` із Додатковими Мета Даними
+## `UploadFile` із додатковими метаданими { #uploadfile-with-additional-metadata }
-Ви також можете використовувати `File()` разом із `UploadFile`, наприклад, для встановлення додаткових метаданих:
+Ви також можете використовувати `File()` разом із `UploadFile`, наприклад, щоб встановити додаткові метадані:
{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
-## Завантаження Кількох Файлів
+## Завантаження кількох файлів { #multiple-file-uploads }
Можна завантажувати кілька файлів одночасно.
-Вони будуть пов’язані з одним і тим самим "form field", який передається у вигляді "form data".
+Вони будуть пов’язані з одним і тим самим «form field», який передається у вигляді «form data».
Щоб це реалізувати, потрібно оголосити список `bytes` або `UploadFile`:
@@ -160,16 +161,16 @@ contents = myfile.file.read()
Ви також можете використати `from starlette.responses import HTMLResponse`.
-**FastAPI** надає ті ж самі `starlette.responses`, що й `fastapi.responses`, для зручності розробників. Однак більшість доступних відповідей надходять безпосередньо від Starlette.
+**FastAPI** надає ті ж самі `starlette.responses`, що й `fastapi.responses`, просто для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо від Starlette.
///
-### Завантаження декількох файлів із додатковими метаданими
+### Завантаження кількох файлів із додатковими метаданими { #multiple-file-uploads-with-additional-metadata }
-Так само як і раніше, Ви можете використовувати `File()`, щоб встановити додаткові параметри навіть для `UploadFile`:
+Так само як і раніше, ви можете використовувати `File()`, щоб встановити додаткові параметри навіть для `UploadFile`:
{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
-## Підсумок
+## Підсумок { #recap }
-Використовуйте `File`, `bytes`та `UploadFile`, щоб оголошувати файли для завантаження у запитах, які надсилаються у вигляді form data.
+Використовуйте `File`, `bytes` та `UploadFile`, щоб оголошувати файли для завантаження в запиті, надіслані у вигляді form data.
diff --git a/docs/uk/docs/tutorial/request-form-models.md b/docs/uk/docs/tutorial/request-form-models.md
index 7f5759e79a..1bfd368d60 100644
--- a/docs/uk/docs/tutorial/request-form-models.md
+++ b/docs/uk/docs/tutorial/request-form-models.md
@@ -1,12 +1,12 @@
-# Моделі форм (Form Models)
+# Моделі форм { #form-models }
-У FastAPI Ви можете використовувати **Pydantic-моделі** для оголошення **полів форми**.
+У FastAPI ви можете використовувати **Pydantic-моделі** для оголошення **полів форми**.
/// info | Інформація
-Щоб використовувати форми, спочатку встановіть python-multipart.
+Щоб використовувати форми, спочатку встановіть `python-multipart`.
-Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили бібліотеку, наприклад:
+Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили його, наприклад:
```console
$ pip install python-multipart
@@ -14,21 +14,21 @@ $ pip install python-multipart
///
-/// note | Підказка
+/// note | Примітка
-Ця функція підтримується, починаючи з FastAPI версії `0.113.0`. 🤓
+Це підтримується, починаючи з FastAPI версії `0.113.0`. 🤓
///
-## Використання Pydantic-моделей для форм
+## Pydantic-моделі для форм { #pydantic-models-for-forms }
-Вам просто потрібно оголосити **Pydantic-модель** з полями, які Ви хочете отримати як **поля форми**, а потім оголосити параметр як `Form`:
+Вам просто потрібно оголосити **Pydantic-модель** з полями, які ви хочете отримати як **поля форми**, а потім оголосити параметр як `Form`:
{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *}
-**FastAPI** **витягне** дані для **кожного поля** з **формових даних** у запиті та надасть вам Pydantic-модель, яку Ви визначили.
+**FastAPI** **витягне** дані для **кожного поля** з **формових даних** у запиті та надасть вам Pydantic-модель, яку ви визначили.
-## Перевірка документації
+## Перевірте документацію { #check-the-docs }
Ви можете перевірити це в UI документації за `/docs`:
@@ -36,13 +36,13 @@ $ pip install python-multipart
-## Заборона додаткових полів форми
+## Забороніть додаткові поля форми { #forbid-extra-form-fields }
-У деяких особливих випадках (ймовірно, рідко) Ви можете **обмежити** форму лише тими полями, які були оголошені в Pydantic-моделі, і **заборонити** будь-які **додаткові** поля.
+У деяких особливих випадках (ймовірно, не дуже поширених) ви можете **обмежити** поля форми лише тими, які були оголошені в Pydantic-моделі. І **заборонити** будь-які **додаткові** поля.
-/// note | Підказка
+/// note | Примітка
-Ця функція підтримується, починаючи з FastAPI версії `0.114.0`. 🤓
+Це підтримується, починаючи з FastAPI версії `0.114.0`. 🤓
///
@@ -52,7 +52,7 @@ $ pip install python-multipart
Якщо клієнт спробує надіслати додаткові дані, він отримає **відповідь з помилкою**.
-Наприклад, якщо клієнт спробує надіслати наступні поля форми:
+Наприклад, якщо клієнт спробує надіслати поля форми:
* `username`: `Rick`
* `password`: `Portal Gun`
@@ -73,6 +73,6 @@ $ pip install python-multipart
}
```
-## Підсумок
+## Підсумок { #summary }
-Ви можете використовувати Pydantic-моделі для оголошення полів форми у FastAPI. 😎
+У FastAPI ви можете використовувати Pydantic-моделі для оголошення полів форми. 😎
diff --git a/docs/uk/docs/tutorial/request-forms-and-files.md b/docs/uk/docs/tutorial/request-forms-and-files.md
index a089ef945a..e809bee225 100644
--- a/docs/uk/docs/tutorial/request-forms-and-files.md
+++ b/docs/uk/docs/tutorial/request-forms-and-files.md
@@ -1,10 +1,10 @@
-# Запити з формами та файлами
+# Запити з формами та файлами { #request-forms-and-files }
-У FastAPI Ви можете одночасно отримувати файли та поля форми, використовуючи `File` і `Form`.
+Ви можете одночасно визначати файли та поля форми, використовуючи `File` і `Form`.
/// info | Інформація
-Щоб отримувати завантажені файли та/або дані форми, спочатку встановіть python-multipart.
+Щоб отримувати завантажені файли та/або дані форми, спочатку встановіть `python-multipart`.
Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили бібліотеку, наприклад:
@@ -14,21 +14,21 @@ $ pip install python-multipart
///
-## Імпорт `File` та `Form`
+## Імпорт `File` та `Form` { #import-file-and-form }
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *}
-## Оголошення параметрів `File` та `Form`
+## Оголошення параметрів `File` та `Form` { #define-file-and-form-parameters }
Створіть параметри файлів та форми так само як і для `Body` або `Query`:
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *}
-Файли та поля форми будуть завантажені як формові дані, і Ви отримаєте як файли, так і введені користувачем поля.
+Файли та поля форми будуть завантажені як формові дані, і Ви отримаєте файли та поля форми.
Ви також можете оголосити деякі файли як `bytes`, а деякі як `UploadFile`.
-/// warning | Увага
+/// warning | Попередження
Ви можете оголосити кілька параметрів `File` і `Form` в операції *шляху*, але не можете одночасно оголошувати `Body`-поля, які очікуєте отримати у форматі JSON, оскільки запит матиме тіло, закодоване за допомогою `multipart/form-data`, а не `application/json`.
@@ -36,6 +36,6 @@ $ pip install python-multipart
///
-## Підсумок
+## Підсумок { #recap }
-Використовуйте `File` та `Form` разом, коли вам потрібно отримувати дані форми та файли в одному запиті.
+Використовуйте `File` та `Form` разом, коли вам потрібно отримувати дані та файли в одному запиті.
diff --git a/docs/uk/docs/tutorial/request-forms.md b/docs/uk/docs/tutorial/request-forms.md
index 10c58a73e4..2a22ad922a 100644
--- a/docs/uk/docs/tutorial/request-forms.md
+++ b/docs/uk/docs/tutorial/request-forms.md
@@ -1,12 +1,12 @@
-# Дані форми
+# Дані форми { #form-data }
-Якщо Вам потрібно отримувати поля форми замість JSON, Ви можете використовувати `Form`.
+Якщо вам потрібно отримувати поля форми замість JSON, ви можете використовувати `Form`.
/// info | Інформація
Щоб використовувати форми, спочатку встановіть `python-multipart`.
-Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, і потім встановили бібліотеку, наприклад:
+Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, і потім встановили бібліотеку, наприклад:
```console
$ pip install python-multipart
@@ -14,23 +14,23 @@ $ pip install python-multipart
///
-## Імпорт `Form`
+## Імпорт `Form` { #import-form }
Імпортуйте `Form` з `fastapi`:
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *}
-## Оголошення параметрів `Form`
+## Оголошення параметрів `Form` { #define-form-parameters }
-Створюйте параметри форми так само як Ви б створювали `Body` або `Query`:
+Створюйте параметри форми так само як ви б створювали `Body` або `Query`:
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *}
Наприклад, один зі способів використання специфікації OAuth2 (так званий "password flow") вимагає надсилати `username` та `password` як поля форми.
-spec вимагає, щоб ці поля мали точні назви `username` і `password` та надсилалися у вигляді полів форми, а не JSON.
+spec вимагає, щоб ці поля мали точні назви `username` і `password` та надсилалися у вигляді полів форми, а не JSON.
-З `Form` Ви можете оголошувати ті ж конфігурації, що і з `Body` (та `Query`, `Path`, `Cookie`), включаючи валідацію, приклади, псевдоніми (наприклад, `user-name` замість `username`) тощо.
+З `Form` ви можете оголошувати ті ж конфігурації, що і з `Body` (та `Query`, `Path`, `Cookie`), включаючи валідацію, приклади, псевдоніми (наприклад, `user-name` замість `username`) тощо.
/// info | Інформація
@@ -44,7 +44,7 @@ $ pip install python-multipart
///
-## Про "поля форми"
+## Про "поля форми" { #about-form-fields }
HTML-форми (``) надсилають дані на сервер у "спеціальному" кодуванні, яке відрізняється від JSON.
@@ -56,18 +56,18 @@ HTML-форми (``) надсилають дані на серве
Але якщо форма містить файли, вона кодується як `multipart/form-data`. Ви дізнаєтеся про обробку файлів у наступному розділі.
-Якщо Ви хочете дізнатися більше про ці кодування та поля форм, зверніться до MDN вебдокументації для POST.
+Якщо ви хочете дізнатися більше про ці кодування та поля форм, зверніться до MDN вебдокументації для POST.
///
/// warning | Попередження
-Ви можете оголосити кілька параметрів `Form` в *операції шляху*, але не можете одночасно оголосити поля `Body`, які Ви очікуєте отримати у форматі JSON, оскільки тіло запиту буде закодовано у форматі `application/x-www-form-urlencoded`, а не `application/json`.
+Ви можете оголосити кілька параметрів `Form` в *операції шляху*, але не можете одночасно оголосити поля `Body`, які ви очікуєте отримати у форматі JSON, оскільки запит матиме тіло, закодоване як `application/x-www-form-urlencoded`, а не `application/json`.
Це не обмеження **FastAPI**, а частина HTTP-протоколу.
///
-## Підсумок
+## Підсумок { #recap }
Використовуйте `Form` для оголошення вхідних параметрів у вигляді даних форми.
diff --git a/docs/uk/docs/tutorial/response-model.md b/docs/uk/docs/tutorial/response-model.md
index def1f8a2d5..2fcad94387 100644
--- a/docs/uk/docs/tutorial/response-model.md
+++ b/docs/uk/docs/tutorial/response-model.md
@@ -1,36 +1,35 @@
-# Модель відповіді — Тип, що повертається
+# Модель відповіді — Тип, що повертається { #response-model-return-type }
-Ви можете оголосити тип, який використовуватиметься у відповіді, за допомогою *анотації типу, що повертається* *функцією операцією шляху* (path operation)
+Ви можете оголосити тип, який використовуватиметься у відповіді, анотувавши **тип повернення** *функції операції шляху*.
-**Анотацію типу** можна вказати так само як і для вхідних **параметрів** функції: це можуть бути моделі Pydantic, списки (lists), словники (dictionaries), скалярні значення, як-от цілі числа (integers), булеві значення (booleans) тощо.
+**Анотації типів** можна використовувати так само, як і для вхідних даних у **параметрах** функції: можна використовувати моделі Pydantic, списки, словники, скалярні значення, як-от цілі числа, булеві значення тощо.
{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
-FastAPI використовуватиме цей тип, щоб:
+FastAPI використовуватиме цей тип повернення, щоб:
* **Перевірити правильність** повернених даних.
- * Якщо дані не валідні (наприклад, відсутнє поле), це означає, що Ваш код додатку працює некоректно і не повертає те, що повинен. У такому випадку FastAPI поверне помилку сервера, замість того щоб віддати недопустимі дані. Так Ви та Ваші клієнти будете впевнені, що отримуєте очікувані дані у правильному форматі.
-
-* Додати **JSON Schema** відповіді до специфікації OpenAPI в *операціях шляху*.
+ * Якщо дані не валідні (наприклад, відсутнє поле), це означає, що *ваш* код застосунку зламаний, не повертає те, що повинен, і буде повернуто помилку сервера замість некоректних даних. Так ви та ваші клієнти можете бути впевнені, що отримаєте дані й очікувану структуру даних.
+* Додати **JSON Schema** для відповіді в OpenAPI *операції шляху*.
* Це буде використано в **автоматичній документації**.
- * А також інструментами, які автоматично генерують клієнтський код.
+ * Це також буде використано інструментами, які автоматично генерують клієнтський код.
Але найголовніше:
-* FastAPI **обмежить та відфільтрує** вихідні дані відповідно до типу, вказаного у відповіді.
- * Це особливо важливо для **безпеки**. Деталі нижче.
+* Це **обмежить та відфільтрує** вихідні дані до того, що визначено в типі повернення.
+ * Це особливо важливо для **безпеки**, нижче ми побачимо про це більше.
-## Параметр `response_model`
+## Параметр `response_model` { #response-model-parameter }
-Іноді Вам потрібно або зручно повертати інші типи даних, ніж ті, що зазначені як тип відповіді.
+Є випадки, коли вам потрібно або ви хочете повертати дані, які не зовсім відповідають тому, що оголошено типом.
-Наприклад, Ви можете **повертати словник** або об’єкт бази даних, але **оголосити модель Pydantic** як модель відповіді. Тоді модель Pydantic автоматично оброблятиме валідацію, документацію тощо.
+Наприклад, ви можете захотіти **повертати словник** або об’єкт бази даних, але **оголосити його як модель Pydantic**. Таким чином модель Pydantic виконуватиме всю документацію даних, валідацію тощо для об’єкта, який ви повернули (наприклад, словника або об’єкта бази даних).
-Якщо Ви додасте анотацію типу для повернення, редактор коду або mypy можуть поскаржитися, що функція повертає інший тип (наприклад, dict замість Item).
+Якщо ви додали анотацію типу повернення, інструменти та редактори скаржитимуться (коректною) помилкою, повідомляючи, що ваша функція повертає тип (наприклад, dict), який відрізняється від того, що ви оголосили (наприклад, модель Pydantic).
-У таких випадках можна скористатися параметром `response_model` в декораторі маршруту (наприклад, @app.get()).
+У таких випадках можна скористатися параметром *декоратора операції шляху* `response_model` замість типу повернення.
-Параметр `response_model` працює з будь-яким *оператором шляху*:
+Ви можете використовувати параметр `response_model` у будь-якій з *операцій шляху*:
* `@app.get()`
* `@app.post()`
@@ -42,33 +41,33 @@ FastAPI використовуватиме цей тип, щоб:
/// note | Примітка
-Зверніть увагу, що `response_model` є параметром методу-декоратора (`get`, `post`, тощо), а не *функцією операцією шляху* (path operation function), як це робиться з параметрами або тілом запиту.
+Зверніть увагу, що `response_model` є параметром методу «декоратора» (`get`, `post` тощо). А не вашої *функції операції шляху*, як усі параметри та тіло.
///
-`response_model` приймає такий самий тип, який Ви б вказали для поля моделі Pydantic. Тобто це може бути як Pydantic-модель, так і, наприклад, `list` із моделей Pydantic — `List[Item]`.
+`response_model` приймає такий самий тип, який ви б вказали для поля моделі Pydantic, тобто це може бути модель Pydantic, але також це може бути, наприклад, `list` моделей Pydantic, як-от `List[Item]`.
-FastAPI використовуватиме `response_model` для створення документації, валідації даних та — найважливіше — **перетворення та фільтрації вихідних даних** згідно з оголошеним типом.
+FastAPI використовуватиме цей `response_model` для виконання всієї документації даних, валідації тощо, а також для **перетворення та фільтрації вихідних даних** до оголошеного типу.
/// tip | Порада
-Якщо у Вас увімкнено сувору перевірку типів у редакторі, mypy тощо, Ви можете оголосити тип повернення функції як `Any`.
+Якщо у вас увімкнено сувору перевірку типів у редакторі, mypy тощо, ви можете оголосити тип повернення функції як `Any`.
-Таким чином, Ви повідомляєте редактору, що свідомо повертаєте будь-що. Але FastAPI усе одно виконуватиме створення документації, валідацію, фільтрацію тощо за допомогою параметра `response_model`.
+Таким чином, ви повідомляєте редактору, що свідомо повертаєте будь-що. Але FastAPI усе одно виконуватиме документацію даних, валідацію, фільтрацію тощо за допомогою `response_model`.
///
-### Пріоритет `response_model`
+### Пріоритет `response_model` { #response-model-priority }
-Якщо Ви вказуєте і тип повернення, і `response_model`, то FastAPI використовуватиме `response_model` з пріоритетом.
+Якщо ви оголошуєте і тип повернення, і `response_model`, то `response_model` матиме пріоритет і буде використаний FastAPI.
-Таким чином, Ви можете додати правильні анотації типів до ваших функцій, навіть якщо вони повертають тип, відмінний від `response_model`. Це буде корисно для редакторів коду та інструментів, таких як mypy. І при цьому FastAPI продовжить виконувати валідацію даних, генерувати документацію тощо на основі `response_model`.
+Таким чином ви можете додати правильні анотації типів до ваших функцій, навіть коли повертаєте тип, відмінний від моделі відповіді, щоб це використовували редактор і інструменти на кшталт mypy. І при цьому FastAPI все одно виконуватиме валідацію даних, документацію тощо, використовуючи `response_model`.
-Ви також можете використати `response_model=None`, щоб вимкнути створення моделі відповіді для цієї *операції шляху*. Це може знадобитися, якщо Ви додаєте анотації типів до об'єктів, які не є допустимими полями Pydantic — приклад цього Ви побачите в одному з наступних розділів.
+Ви також можете використати `response_model=None`, щоб вимкнути створення моделі відповіді для цієї *операції шляху*; це може знадобитися, якщо ви додаєте анотації типів для речей, які не є валідними полями Pydantic, приклад цього ви побачите в одному з розділів нижче.
-## Повернути ті самі вхідні дані
+## Повернути ті самі вхідні дані { #return-the-same-input-data }
-Тут ми оголошуємо модель `UserIn`, яка містить звичайний текстовий пароль:
+Тут ми оголошуємо модель `UserIn`, вона міститиме пароль у відкритому вигляді:
{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *}
@@ -76,7 +75,7 @@ FastAPI використовуватиме `response_model` для створе
Щоб використовувати `EmailStr`, спочатку встановіть `email-validator`.
-Переконайтесь, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили пакет, наприклад:
+Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили пакет, наприклад:
```console
$ pip install email-validator
@@ -90,29 +89,29 @@ $ pip install "pydantic[email]"
///
-І ми використовуємо цю модель, щоб оголосити і вхідні, і вихідні дані:
+І ми використовуємо цю модель, щоб оголосити наші вхідні дані, і цю ж модель, щоб оголосити наші вихідні дані:
{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *}
-Тепер, коли браузер створює користувача з паролем, API поверне той самий пароль у відповіді.
+Тепер, щоразу коли браузер створює користувача з паролем, API поверне той самий пароль у відповіді.
-У цьому випадку це може не бути проблемою, адже саме користувач надіслав пароль.
+У цьому випадку це може не бути проблемою, адже це той самий користувач надсилає пароль.
-Але якщо ми використаємо цю ж модель для іншої операції шляху, ми можемо випадково надіслати паролі наших користувачів кожному клієнту.
+Але якщо ми використаємо цю ж модель для іншої *операції шляху*, ми можемо надсилати паролі наших користувачів кожному клієнту.
/// danger | Обережно
-Ніколи не зберігайте пароль користувача у відкритому вигляді та не надсилайте його у відповіді, якщо тільки Ви не знаєте всі ризики і точно розумієте, що робите.
+Ніколи не зберігайте пароль користувача у відкритому вигляді та не надсилайте його у відповіді таким чином, якщо тільки ви не знаєте всіх застережень і точно розумієте, що робите.
///
-## Додайте окрему вихідну модель
+## Додати вихідну модель { #add-an-output-model }
-Замість цього ми можемо створити вхідну модель з відкритим паролем і вихідну модель без нього:
+Замість цього ми можемо створити вхідну модель з паролем у відкритому вигляді і вихідну модель без нього:
{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *}
-Тут, навіть якщо *функція операції шляху* повертає об'єкт користувача, який містить пароль:
+Тут, хоча наша *функція операції шляху* повертає того самого вхідного користувача, який містить пароль:
{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *}
@@ -120,107 +119,107 @@ $ pip install "pydantic[email]"
{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *}
-Таким чином, **FastAPI** автоматично відфільтрує всі дані, які не вказані у вихідній моделі (за допомогою Pydantic).
+Таким чином, **FastAPI** подбає про фільтрацію всіх даних, які не оголошені у вихідній моделі (використовуючи Pydantic).
-### `response_model` або тип повернення
+### `response_model` або тип повернення { #response-model-or-return-type }
-У цьому випадку, оскільки дві моделі різні, якщо ми анотуємо тип повернення функції як `UserOut`, редактор і такі інструменти, як mypy, видадуть помилку, бо фактично ми повертаємо інший тип.
+У цьому випадку, оскільки дві моделі різні, якщо ми анотуємо тип повернення функції як `UserOut`, редактор і інструменти скаржитимуться, що ми повертаємо невалідний тип, адже це різні класи.
-Тому в цьому прикладі ми використовуємо параметр `response_model`, а не анотацію типу повернення.
+Саме тому в цьому прикладі нам треба оголосити це через параметр `response_model`.
-...але читайте далі, щоб дізнатися, як обійти це обмеження.
+...але читайте далі нижче, щоб побачити, як це обійти.
-## Тип повернення і фільтрація даних
+## Тип повернення і фільтрація даних { #return-type-and-data-filtering }
-Продовжимо з попереднього прикладу. Ми хотіли **анотувати функцію одним типом**, але при цьому повертати з неї більше даних.
+Продовжимо з попереднього прикладу. Ми хотіли **анотувати функцію одним типом**, але хотіли мати змогу повертати з функції те, що насправді містить **більше даних**.
-Ми хочемо, щоб FastAPI продовжував **фільтрувати** ці дані за допомогою response_model. Тобто навіть якщо функція повертає більше інформації, у відповіді будуть лише ті поля, які вказані у response_model.
+Ми хочемо, щоб FastAPI продовжував **фільтрувати** дані, використовуючи модель відповіді. Тобто навіть якщо функція повертає більше даних, відповідь міститиме лише поля, оголошені в моделі відповіді.
-У попередньому прикладі, оскільки класи були різні, нам довелося використовувати параметр `response_model`. Але це означає, що ми не отримуємо підтримки з боку редактора коду та інструментів перевірки типів щодо типу, який повертає функція.
+У попередньому прикладі, оскільки класи були різні, нам довелося використовувати параметр `response_model`. Але це також означає, що ми не отримуємо підтримки від редактора та інструментів, які перевіряють тип повернення функції.
-Проте в більшості випадків, коли нам потрібно зробити щось подібне, ми просто хочемо, щоб модель **відфільтрувала або прибрала** частину даних, як у цьому прикладі.
+Проте в більшості випадків, коли нам потрібно зробити щось подібне, ми просто хочемо, щоб модель **відфільтрувала/прибрала** частину даних, як у цьому прикладі.
-У таких випадках ми можемо використати класи та спадкування, щоб скористатися **анотаціями типів** функцій — це дає кращу підтримку з боку редактора та інструментів типу mypy, і при цьому FastAPI продовжує виконувати **фільтрацію даних** у відповіді.
+І в таких випадках ми можемо використати класи та спадкування, щоб скористатися **анотаціями типів** функцій і отримати кращу підтримку в редакторі та інструментах, і при цьому зберегти **фільтрацію даних** у FastAPI.
{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *}
-Завдяки цьому ми отримуємо підтримку інструментів — від редакторів і mypy, оскільки цей код є коректним з точки зору типів, — але ми також отримуємо фільтрацію даних від FastAPI.
+Завдяки цьому ми отримуємо підтримку інструментів — від редакторів і mypy, адже цей код коректний з точки зору типів, — але ми також отримуємо фільтрацію даних від FastAPI.
Як це працює? Давайте розберемося. 🤓
-### Типи та підтримка інструментів
+### Анотації типів і підтримка інструментів { #type-annotations-and-tooling }
-Спершу подивимось, як це бачать редактори, mypy та інші інструменти.
+Спершу подивімося, як це бачать редактори, mypy та інші інструменти.
-`BaseUser` має базові поля. Потім `UserIn` успадковує `BaseUser` і додає поле `password`, отже, він матиме всі поля з обох моделей.
+`BaseUser` має базові поля. Потім `UserIn` успадковує `BaseUser` і додає поле `password`, отже він включатиме всі поля з обох моделей.
-Ми зазначаємо тип повернення функції як `BaseUser`, але фактично повертаємо екземпляр `UserIn`.
+Ми анотуємо тип повернення функції як `BaseUser`, але фактично повертаємо екземпляр `UserIn`.
-Редактор, mypy та інші інструменти не скаржитимуться на це, тому що з точки зору типізації `UserIn` є підкласом `BaseUser`, а це означає, що він є `валідним` типом, коли очікується будь-що, що є `BaseUser`.
+Редактор, mypy та інші інструменти не скаржитимуться на це, тому що з точки зору типізації `UserIn` є підкласом `BaseUser`, а це означає, що він є *валідним* типом, коли очікується будь-що, що є `BaseUser`.
-### Фільтрація даних у FastAPI
+### Фільтрація даних у FastAPI { #fastapi-data-filtering }
-Тепер для FastAPI він бачить тип повернення і переконується, що те, що Ви повертаєте, містить **тільки** поля, які оголошені у цьому типі.
+Тепер для FastAPI він побачить тип повернення і переконається, що те, що ви повертаєте, містить **лише** поля, які оголошені у цьому типі.
-FastAPI виконує кілька внутрішніх операцій з Pydantic, щоб гарантувати, що правила наслідування класів не застосовуються для фільтрації повернених даних, інакше Ви могли б повернути значно більше даних, ніж очікували.
+FastAPI виконує кілька внутрішніх операцій з Pydantic, щоб гарантувати, що ті самі правила наслідування класів не застосовуються для фільтрації повернених даних, інакше ви могли б зрештою повертати значно більше даних, ніж очікували.
-Таким чином, Ви отримуєте найкраще з двох світів: анотації типів **з підтримкою інструментів** і **фільтрацію даних**.
+Таким чином ви можете отримати найкраще з двох світів: анотації типів із **підтримкою інструментів** і **фільтрацію даних**.
-## Подивитись у документації
+## Подивитися в документації { #see-it-in-the-docs }
-Коли Ви дивитесь автоматичну документацію, Ви можете побачити, що вхідна модель і вихідна модель мають власну JSON-схему:
+Коли ви дивитеся автоматичну документацію, ви можете перевірити, що вхідна модель і вихідна модель матимуть власну JSON Schema:
-І обидві моделі використовуються для інтерактивної API-документації:
+І обидві моделі будуть використані для інтерактивної документації API:
-## Інші анотації типів повернення
+## Інші анотації типів повернення { #other-return-type-annotations }
-Існують випадки, коли Ви повертаєте щось, що не є допустимим полем Pydantic, але анотуєте це у функції лише для того, щоб отримати підтримку від інструментів (редактора, mypy тощо).
+Можуть бути випадки, коли ви повертаєте щось, що не є валідним полем Pydantic, і анотуєте це у функції лише для того, щоб отримати підтримку від інструментів (редактора, mypy тощо).
-### Повернення Response напряму
+### Повернути Response напряму { #return-a-response-directly }
Найпоширенішим випадком буде [повернення Response напряму, як пояснюється пізніше у розширеній документації](../advanced/response-directly.md){.internal-link target=_blank}.
-{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
+{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
Цей простий випадок автоматично обробляється FastAPI, тому що анотація типу повернення — це клас (або підклас) `Response`.
І інструменти також будуть задоволені, бо і `RedirectResponse`, і `JSONResponse` є підкласами `Response`, отже анотація типу коректна.
-### Анотація підкласу Response
+### Анотувати підклас Response { #annotate-a-response-subclass }
-Також можна використовувати підклас `Response` у анотації типу:
+Ви також можете використати підклас `Response` в анотації типу:
-{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
+{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
Це теж працюватиме, бо `RedirectResponse` — підклас `Response`, і FastAPI автоматично обробить цей простий випадок.
-### Некоректні анотації типу повернення
+### Некоректні анотації типу повернення { #invalid-return-type-annotations }
-Але коли Ви повертаєте якийсь інший довільний об’єкт, що не є валідним типом Pydantic (наприклад, об’єкт бази даних), і анотуєте його так у функції, FastAPI спробує створити Pydantic модель відповіді на основі цієї анотації типу, і це завершиться помилкою.
+Але коли ви повертаєте якийсь інший довільний об’єкт, що не є валідним типом Pydantic (наприклад, об’єкт бази даних), і анотуєте його так у функції, FastAPI спробує створити модель відповіді Pydantic на основі цієї анотації типу і це завершиться помилкою.
-Те саме станеться, якщо Ви використовуєте union між різними типами, де один або більше не є валідними типами Pydantic, наприклад, це спричинить помилку 💥:
+Те саме станеться, якщо ви використаєте union між різними типами, де один або більше не є валідними типами Pydantic, наприклад, це завершиться помилкою 💥:
{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *}
-...це не працює, тому що тип анотації не є типом Pydantic і не є просто класом `Response` або його підкласом, а є об’єднанням (union) — або `Response`, або `dict`.
+...це не працює, тому що анотація типу не є типом Pydantic і не є просто одним класом `Response` або його підкласом, це union (будь-який із двох) між `Response` і `dict`.
-### Відключення Моделі Відповіді
+### Вимкнути модель відповіді { #disable-response-model }
-Продовжуючи приклад вище, можливо, Ви не хочете використовувати стандартну валідацію даних, автоматичну документацію, фільтрацію тощо, які FastAPI виконує за замовчуванням.
+Продовжуючи приклад вище, можливо, ви не хочете мати стандартну валідацію даних, документацію, фільтрацію тощо, які виконує FastAPI.
-Але ви все одно можете залишити анотацію типу у функції, щоб зберегти підтримку з боку інструментів, таких як редактори коду або статичні перевірки типів (наприклад, mypy).
+Але ви можете все одно хотіти залишити анотацію типу повернення у функції, щоб отримати підтримку від інструментів, як-от редактори та перевірки типів (наприклад, mypy).
У такому випадку ви можете вимкнути генерацію моделі відповіді, встановивши `response_model=None`:
{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *}
-Це змусить FastAPI пропустити генерацію моделі відповіді, і таким чином Ви зможете використовувати будь-які анотації типів повернення без впливу на вашу FastAPI аплікацію. 🤓
+Це змусить FastAPI пропустити генерацію моделі відповіді, і таким чином ви зможете використовувати будь-які потрібні анотації типів повернення без впливу на ваш FastAPI застосунок. 🤓
-## Параметри кодування моделі відповіді
+## Параметри кодування моделі відповіді { #response-model-encoding-parameters }
Ваша модель відповіді може мати значення за замовчуванням, наприклад:
@@ -230,19 +229,19 @@ FastAPI виконує кілька внутрішніх операцій з Pyd
* `tax: float = 10.5` має значення за замовчуванням `10.5`.
* `tags: List[str] = []` має значення за замовчуванням порожній список: `[]`.
-Але Ви можете захотіти не включати їх у результат, якщо вони фактично не були збережені.
+але ви можете захотіти не включати їх у результат, якщо вони фактично не були збережені.
-Наприклад, якщо у Вас є моделі з багатьма необов’язковими атрибутами у NoSQL базі даних, але Ви не хочете відправляти дуже довгі JSON-відповіді, повні значень за замовчуванням.
+Наприклад, якщо у вас є моделі з багатьма необов’язковими атрибутами у NoSQL базі даних, але ви не хочете надсилати дуже довгі JSON-відповіді, повні значень за замовчуванням.
-### Використовуйте параметр `response_model_exclude_unset`
+### Використовуйте параметр `response_model_exclude_unset` { #use-the-response-model-exclude-unset-parameter }
-Ви можете встановити параметр декоратора шляху `response_model_exclude_unset=True`:
+Ви можете встановити параметр *декоратора операції шляху* `response_model_exclude_unset=True`:
{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *}
-і ці значення за замовчуванням не будуть включені у відповідь, тільки фактично встановлені значення.
+і ці значення за замовчуванням не будуть включені у відповідь, лише значення, які фактично встановлені.
-Отже, якщо Ви надішлете запит до цього оператора шляху для елемента з item_id `foo`, відповідь (без включення значень за замовчуванням) буде:
+Отже, якщо ви надішлете запит до цієї *операції шляху* для елемента з ID `foo`, відповідь (без включення значень за замовчуванням) буде:
```JSON
{
@@ -253,32 +252,18 @@ FastAPI виконує кілька внутрішніх операцій з Pyd
/// info | Інформація
-У Pydantic версії 1 метод називався `.dict()`, він був застарілий (але ще підтримується) у Pydantic версії 2 і перейменований у `.model_dump()`.
-
-Приклади тут використовують `.dict()` для сумісності з Pydantic v1, але Вам слід використовувати `.model_dump()`, якщо Ви можете використовувати Pydantic v2.
-
-///
-
-/// info | Інформація
-
-FastAPI використовує `.dict()` моделі Pydantic з параметром `exclude_unset`, щоб досягти цього.
-
-///
-
-/// info | Інформація
-
Ви також можете використовувати:
* `response_model_exclude_defaults=True`
* `response_model_exclude_none=True`
-як описано в документації Pydantic for `exclude_defaults` та `exclude_none`.
+як описано в документації Pydantic для `exclude_defaults` та `exclude_none`.
///
-#### Дані зі значеннями для полів із типовими значеннями
+#### Дані зі значеннями для полів із типовими значеннями { #data-with-values-for-fields-with-defaults }
-Але якщо Ваші дані мають значення для полів моделі з типовими значеннями, як у елемента з item_id `bar`:
+Але якщо ваші дані мають значення для полів моделі з типовими значеннями, як у елемента з ID `bar`:
```Python hl_lines="3 5"
{
@@ -288,11 +273,12 @@ FastAPI використовує `.dict()` моделі Pydantic з `http.HTTPStatus`.
+
+`status_code` також може, як альтернативу, приймати `IntEnum`, наприклад, Python `http.HTTPStatus`.
///
Він буде:
-* Повертати вказаний код статусу у відповіді.
-* Документувати його як такий у схемі OpenAPI (і, таким чином, в інтерфейсі користувача):
+* Повертати цей код статусу у відповіді.
+* Документувати його як такий у схемі OpenAPI (і, таким чином, в інтерфейсах користувача):
-/// note | Нотатка
+/// note | Примітка
Деякі коди відповіді (див. наступний розділ) вказують, що відповідь не має тіла.
-FastAPI знає про це і створить OpenAPI документацію, яка вказує, що тіла відповіді немає.
+FastAPI знає про це і створить документацію OpenAPI, яка вказує, що тіла відповіді немає.
///
-## Про HTTP статус коди
+## Про HTTP коди статусу { #about-http-status-codes }
-/// note | Нотатка
+/// note | Примітка
-Якщо Ви вже знаєте, що таке HTTP коди статусу, переходьте до наступного розділу.
+Якщо ви вже знаєте, що таке HTTP коди статусу, перейдіть до наступного розділу.
///
-В HTTP Ви надсилаєте числовий код статусу з 3 цифр як частину відповіді.
+В HTTP ви надсилаєте числовий код статусу з 3 цифр як частину відповіді.
-Ці коди статусу мають пов’язану назву для їх розпізнавання, але найважливішою частиною є саме число.
+Ці коди статусу мають пов’язану назву для їх розпізнавання, але важливою частиною є число.
Коротко:
-* **`100 - 199`** "Інформаційні" відповіді. Ви рідко використовуєте їх напряму. Відповіді з такими кодами не можуть мати тіла.
-* **`200 - 299`** "Успішні" відповіді. Це ті, які Ви використовуватимете найчастіше.
- * `200` - код за замовчуванням, який означає, що все пройшло "OK".
- * Інший приклад – `201`, "Created" (створено). Його зазвичай використовують після створення нового запису в базі даних.
- * Особливий випадок – `204`, "No Content" (немає вмісту). Ця відповідь використовується, коли немає даних для повернення клієнту, тому відповідь не повинна мати тіла.
-* **`300 - 399`** "Перенаправлення". Відповіді з цими кодами можуть мати або не мати тіла, за винятком `304`, "Not Modified" (не змінено), яка не повинна мати тіла.
-* **`400 - 499`** "Помилка клієнта". Це другий тип, який Ви, ймовірно, будете використовувати найчастіше.
- * Приклад `404`, "Not Found" (не знайдено).
- * Для загальних помилок клієнта можна використовувати `400`.
-* `500 - 599` "Помилки сервера". Ви майже ніколи не використовуєте їх напряму. Якщо в коді Вашого застосунку або на сервері щось пішло не так, автоматично буде повернено один із цих кодів статусу.
+* `100 - 199` — для «Information». Ви рідко використовуєте їх напряму. Відповіді з такими кодами статусу не можуть мати тіла.
+* **`200 - 299`** — для «Successful» відповідей. Це ті, які ви використовуватимете найчастіше.
+ * `200` — код статусу за замовчуванням, який означає, що все було «OK».
+ * Інший приклад — `201`, «Created». Його зазвичай використовують після створення нового запису в базі даних.
+ * Особливий випадок — `204`, «No Content». Цю відповідь використовують, коли немає вмісту для повернення клієнту, і тому відповідь не повинна мати тіла.
+* **`300 - 399`** — для «Redirection». Відповіді з цими кодами статусу можуть мати або не мати тіла, за винятком `304`, «Not Modified», яка не повинна мати тіла.
+* **`400 - 499`** — для відповідей «Client error». Це другий тип, який ви, ймовірно, будете використовувати найчастіше.
+ * Приклад — `404`, для відповіді «Not Found».
+ * Для загальних помилок з боку клієнта ви можете просто використовувати `400`.
+* `500 - 599` — для помилок сервера. Ви майже ніколи не використовуєте їх напряму. Коли щось піде не так у якійсь частині коду вашого застосунку або на сервері, автоматично буде повернено один із цих кодів статусу.
/// tip | Порада
-Щоб дізнатися більше про кожен код статусу і призначення кожного з них, перегляньте документацію MDN про HTTP коди статусу.
+Щоб дізнатися більше про кожен код статусу і для чого призначений кожен із них, перегляньте документацію MDN про HTTP коди статусу.
///
-## Легкий спосіб запам'ятати назви
+## Скорочення, щоб запам’ятати назви { #shortcut-to-remember-the-names }
-Розглянемо ще раз попередній приклад:
+Розглянемо попередній приклад ще раз:
-{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
+{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
-`201` - це код статусу для "Created" (створено).
+`201` — це код статусу для «Created».
-Але Вам не потрібно запам'ятовувати, що означає кожен із цих кодів.
+Але вам не потрібно запам'ятовувати, що означає кожен із цих кодів.
-Ви можете використовувати зручні змінні з `fastapi.status`
+Ви можете використовувати зручні змінні з `fastapi.status`.
-{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
+{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *}
-Ці змінні просто для зручності. Вони містять ті ж самі числа, але Ви можете скористатися автозаповненням в редакторі:
+Вони — просто для зручності, містять те саме число, але так ви можете скористатися автозаповненням редактора, щоб знайти їх:
@@ -91,10 +92,10 @@ FastAPI знає про це і створить OpenAPI документаці
Ви також можете використати `from starlette import status`.
-**FastAPI** надає ті ж самі змінні `starlette.status` як `fastapi.status`, просто для зручності розробника. Однак вони походять безпосередньо зі Starlette.
+**FastAPI** надає той самий `starlette.status` як `fastapi.status` просто як зручність для вас, розробника. Але він походить безпосередньо зі Starlette.
///
-## Зміна значення за замовчуванням
+## Зміна значення за замовчуванням { #changing-the-default }
-Далі, у Посібнику для досвідчених користувачів{.internal-link target=_blank}, Ви дізнаєтесь, як повернути інший код статусу, ніж той, який Ви оголосили тут.
+Пізніше, у [Посібнику для досвідчених користувачів](../advanced/response-change-status-code.md){.internal-link target=_blank}, ви побачите, як повертати інший код статусу, ніж значення за замовчуванням, яке ви оголошуєте тут.
diff --git a/docs/uk/docs/tutorial/schema-extra-example.md b/docs/uk/docs/tutorial/schema-extra-example.md
index 853fd5e659..54608c2ab1 100644
--- a/docs/uk/docs/tutorial/schema-extra-example.md
+++ b/docs/uk/docs/tutorial/schema-extra-example.md
@@ -1,44 +1,22 @@
-# Декларування прикладів вхідних даних
+# Декларування прикладів вхідних даних { #declare-request-example-data }
Ви можете задати приклади даних, які Ваш застосунок може отримувати.
Ось кілька способів, як це зробити.
-## Додаткові дані JSON-схеми в моделях Pydantic
+## Додаткові дані JSON-схеми в моделях Pydantic { #extra-json-schema-data-in-pydantic-models }
Ви можете задати `examples` для моделі Pydantic, які буде додано до згенерованої JSON-схеми.
-//// tab | Pydantic v2
-
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
-////
+Ця додаткова інформація буде додана як є до **JSON-схеми** для цієї моделі, і вона буде використана в документації до API.
-//// tab | Pydantic v1
-
-{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
-
-////
-
-Ця додаткова інформація буде додана як є до **JSON-схеми**, і вона буде використовуватися в документації до API.
-
-//// tab | Pydantic v2
-
-У версії Pydantic 2 використовується атрибут `model_config`, який приймає `dict`, як описано в документації Pydantic: Конфігурація.
+Ви можете використати атрибут `model_config`, який приймає `dict`, як описано в документації Pydantic: Configuration.
Ви можете встановити `"json_schema_extra"` як `dict`, що містить будь-які додаткові дані, які Ви хочете відобразити у згенерованій JSON-схемі, включаючи `examples`.
-////
-
-//// tab | Pydantic v1
-
-У версії Pydantic 1 використовується внутрішній клас `Config` і параметр `schema_extra`, як описано в документації Pydantic: Налаштування схеми.
-
-Ви можете задати `schema_extra` як `dict`, що містить будь-які додаткові дані, які Ви хочете бачити у згенерованій JSON-схемі, включаючи `examples`.
-
-////
-
-/// tip | Підказка
+/// tip | Порада
Ви можете використати ту ж техніку, щоб розширити JSON-схему і додати власну додаткову інформацію.
@@ -50,19 +28,19 @@
OpenAPI 3.1.0 (який використовується починаючи з FastAPI 0.99.0) додав підтримку `examples`, що є частиною стандарту **JSON-схеми**.
-До цього підтримувався лише ключ `example` з одним прикладом. Він все ще підтримується в OpenAPI 3.1.0, але є застарілим і не входить до стандарту JSON Schema. Тому рекомендується перейти з `example` на `examples`. 🤓
+До цього підтримувався лише ключ `example` з одним прикладом. Він все ще підтримується в OpenAPI 3.1.0, але є застарілим і не входить до стандарту JSON Schema. Тому рекомендується перейти з `example` на `examples`. 🤓
Більше про це можна прочитати в кінці цієї сторінки.
///
-## Додаткові аргументи `Field`
+## Додаткові аргументи `Field` { #field-additional-arguments }
Коли ви використовуєте `Field()` у моделях Pydantic, Ви також можете вказати додаткові `examples`:
{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *}
-## `examples` у JSON-схемі — OpenAPI
+## `examples` у JSON-схемі — OpenAPI { #examples-in-json-schema-openapi }
При використанні будь-кого з наступного:
@@ -76,41 +54,41 @@ OpenAPI 3.1.0 (який використовується починаючи з F
Ви також можете задати набір `examples` з додатковою інформацією, яка буде додана до їхніх **JSON-схем** у **OpenAPI**.
-### `Body` з `examples`
+### `Body` з `examples` { #body-with-examples }
Тут ми передаємо `examples`, які містять один приклад очікуваних даних у `Body()`:
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *}
-### Приклад у UI документації
+### Приклад у UI документації { #example-in-the-docs-ui }
-За допомогою будь-якого з наведених вище методів це виглядатиме так у документації за `/docs`:
+За допомогою будь-якого з наведених вище методів це виглядатиме так у `/docs`:
-### `Body` з кількома `examples`
+### `Body` з кількома `examples` { #body-with-multiple-examples }
Звичайно, Ви також можете передати кілька `examples`:
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *}
-Коли Ви це робите, приклади будуть частиною внутрішньої **JSON-схеми** для цих даних.
+Коли Ви це робите, приклади будуть частиною внутрішньої **JSON-схеми** для цих даних тіла.
-Втім, на момент написання цього (26 серпня 2023), Swagger UI — інструмент, який відповідає за відображення UI документації — не підтримує показ кількох прикладів у **JSON-схеми**. Але нижче можна прочитати про обхідний шлях.
+Втім, на момент написання цього (час написання цього), Swagger UI — інструмент, який відповідає за відображення UI документації — не підтримує показ кількох прикладів для даних у **JSON-схемі**. Але нижче можна прочитати про обхідний шлях.
-### Специфічні для OpenAPI `examples`
+### Специфічні для OpenAPI `examples` { #openapi-specific-examples }
-Ще до того, як **JSON-схема** почала підтримувати `examples`, OpenAPI вже мала підтримку поля з такою ж назвою — `examples`.
+Ще до того, як **JSON-схема** почала підтримувати `examples`, OpenAPI вже мала підтримку іншого поля, яке також називається `examples`.
-Це **специфічне для OpenAPI** поле `examples` розміщується в іншій частині специфікації OpenAPI — у **деталях кожної *операції шляху***, а не всередині самої JSON-схеми.
+Це **специфічне для OpenAPI** поле `examples` розміщується в іншому розділі специфікації OpenAPI. Воно розміщується в **деталях кожної *операції шляху***, а не всередині кожної JSON-схеми.
-Swagger UI вже давно підтримує це поле `examples`. Тому Ви можете використовувати його, щоб **відображати** кілька **прикладів у документації**.
+І Swagger UI вже давно підтримує це поле `examples`. Тому Ви можете використовувати його, щоб **відображати** різні **приклади в UI документації**.
-Це поле `examples` у специфікації OpenAPI — це `dict` (словник) з **кількома прикладами** (а не список `list`), кожен із яких може містити додаткову інформацію, що буде додана до **OpenAPI**.
+Форма цього специфічного для OpenAPI поля `examples` — це `dict` з **кількома прикладами** (а не `list`), кожен із яких має додаткову інформацію, яка також буде додана до **OpenAPI**.
-Воно не включається до JSON Schema кожного параметра, а розміщується зовні, безпосередньо в *операції шляху*.
+Воно не включається всередину кожної JSON-схеми, що міститься в OpenAPI, воно розміщується зовні, безпосередньо в *операції шляху*.
-### Використання параметра `openapi_examples`
+### Використання параметра `openapi_examples` { #using-the-openapi-examples-parameter }
Ви можете оголосити специфічні для OpenAPI `examples` у FastAPI за допомогою параметра `openapi_examples` для:
@@ -122,30 +100,32 @@ Swagger UI вже давно підтримує це поле `examples`. Том
* `Form()`
* `File()`
-Ключі словника (`dict`) ідентифікують кожен приклад, а кожне значення `dict` — кожен специфічний словник `dict` в `examples` може містити:
+Ключі `dict` ідентифікують кожен приклад, а кожне значення — це інший `dict`.
+
+Кожен специфічний `dict` прикладу в `examples` може містити:
* `summary`: короткий опис прикладу.
-* `description`: розгорнутий опис (може містити Markdown).
-* `value`: сам приклад, наприклад, словник (`dict`).
-* `externalValue`: альтернатива `value`, URL-адреса, що вказує на приклад. Проте ця опція може не підтримуватися більшістю інструментів, на відміну від `value`.
+* `description`: розгорнутий опис, який може містити Markdown.
+* `value`: це сам приклад, який буде показано, наприклад `dict`.
+* `externalValue`: альтернатива `value`, URL-адреса, що вказує на приклад. Проте це може не підтримуватися такою кількістю інструментів, як `value`.
Використання виглядає так:
{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *}
-### Приклади OpenAPI у UI документації
+### Приклади OpenAPI в UI документації { #openapi-examples-in-the-docs-ui }
-З параметром `openapi_examples`, доданим до `Body()`, документація `/docs` виглядатиме так:
+З `openapi_examples`, доданим до `Body()`, `/docs` виглядатиме так:
-## Технічні деталі
+## Технічні деталі { #technical-details }
-/// tip | Підказка
+/// tip | Порада
-Якщо Ви вже використовуєте **FastAPI** версії **0.99.0 або вище**, Ви можете **пропустити** цей розділ.
+Якщо Ви вже використовуєте **FastAPI** версії **0.99.0 або вище**, Ви, ймовірно, можете **пропустити** ці технічні деталі.
-Він більш актуальний для старих версій, до появи OpenAPI 3.1.0.
+Вони більш актуальні для старих версій, до появи OpenAPI 3.1.0.
Можна вважати це коротким **історичним екскурсом** у OpenAPI та JSON Schema. 🤓
@@ -155,68 +135,68 @@ Swagger UI вже давно підтримує це поле `examples`. Том
Це дуже технічна інформація про стандарти **JSON Schema** і **OpenAPI**.
-Якщо вищезгадані ідеї вже працюють у Вас — можете не заглиблюватися в ці деталі.
+Якщо вищезгадані ідеї вже працюють у Вас, цього може бути достатньо, і Вам, ймовірно, не потрібні ці деталі — можете пропустити.
///
-До OpenAPI 3.1.0 специфікація використовувала стару та модифіковану версію **JSON Schema**.
+До OpenAPI 3.1.0 OpenAPI використовував стару та модифіковану версію **JSON Schema**.
-Оскільки JSON Schema раніше не підтримувала `examples`, OpenAPI додала власне поле `examples`.
+JSON Schema не мала `examples`, тож OpenAPI додала власне поле `example` до своєї модифікованої версії.
-OpenAPI також додала `example` і `examples` до інших частин специфікації:
+OpenAPI також додала поля `example` і `examples` до інших частин специфікації:
-* `Parameter Object` (в специфікації) використовується FastAPI для:
+* `Parameter Object` (в специфікації), який використовувався утилітами FastAPI:
* `Path()`
* `Query()`
* `Header()`
* `Cookie()`
-* `Request Body Object`, в полі `content`, в `Media Type Object` (в специфікації) використовується FastAPI для:
+* `Request Body Object`, у полі `content`, у `Media Type Object` (в специфікації), який використовувався утилітами FastAPI:
* `Body()`
* `File()`
* `Form()`
/// info | Інформація
-Цей старий параметр `examples`, специфічний для OpenAPI, тепер називається `openapi_examples`, починаючи з FastAPI версії `0.103.0`.
+Цей старий специфічний для OpenAPI параметр `examples` тепер називається `openapi_examples`, починаючи з FastAPI `0.103.0`.
///
-### Поле `examples` у JSON Schema
+### Поле `examples` у JSON Schema { #json-schemas-examples-field }
Пізніше JSON Schema додала поле `examples` у нову версію специфікації.
-І вже OpenAPI 3.1.0 базується на цій новій версії (JSON Schema 2020-12), яка включає поле `examples`.
+А потім новий OpenAPI 3.1.0 базувався на найновішій версії (JSON Schema 2020-12), яка включала це нове поле `examples`.
-Тепер це поле `examples` є пріоритетним і замінює старе (і кастомне) поле `example`, яке стало застарілим.
+І тепер це нове поле `examples` має вищий пріоритет за старе одиночне (і кастомне) поле `example`, яке тепер є застарілим.
-Нове поле `examples` у JSON Schema — це **просто список (`list`)** прикладів, без додаткових метаданих (на відміну від OpenAPI).
+Це нове поле `examples` у JSON Schema — це **просто `list`** прикладів, а не `dict` з додатковими метаданими, як в інших місцях OpenAPI (описаних вище).
/// info | Інформація
-Навіть після того, як з'явився OpenAPI 3.1.0, який підтримував examples у JSON Schema, інструмент Swagger UI ще деякий час не підтримував цю версію (підтримка з’явилась з версії 5.0.0 🎉).
+Навіть після релізу OpenAPI 3.1.0 з цією новою простішою інтеграцією з JSON Schema, протягом певного часу Swagger UI, інструмент, який надає автоматичну документацію, не підтримував OpenAPI 3.1.0 (тепер підтримує, починаючи з версії 5.0.0 🎉).
Через це версії FastAPI до 0.99.0 все ще використовували версії OpenAPI нижчі за 3.1.0.
///
-### `Examples` в Pydantic і FastAPI
+### `examples` у Pydantic і FastAPI { #pydantic-and-fastapi-examples }
-Коли Ви додаєте `examples` у модель Pydantic через `schema_extra` або `Field(examples=["something"])`, ці приклади додаються до **JSON Schema** цієї моделі.
+Коли Ви додаєте `examples` у модель Pydantic через `schema_extra` або `Field(examples=["something"])`, цей приклад додається до **JSON Schema** для цієї моделі Pydantic.
-І ця **JSON Schema** Pydantic-моделі включається до **OpenAPI** Вашого API, а потім використовується в UI документації (docs UI).
+І ця **JSON Schema** Pydantic-моделі включається до **OpenAPI** Вашого API, а потім використовується в UI документації.
-У версіях FastAPI до 0.99.0 (починаючи з 0.99.0 використовується новіший OpenAPI 3.1.0), коли Ви використовували `example` або `examples` з іншими утилітами (`Query()`, `Body()` тощо), ці приклади не додавалися до JSON Schema, який описує ці дані (навіть не до власної версії JSON Schema у OpenAPI). Натомість вони додавалися безпосередньо до опису *обробника шляху* *(path operation)* в OpenAPI (тобто поза межами частин, які використовують JSON Schema).
+У версіях FastAPI до 0.99.0 (0.99.0 і вище використовують новіший OpenAPI 3.1.0), коли Ви використовували `example` або `examples` з будь-якими іншими утилітами (`Query()`, `Body()` тощо), ці приклади не додавалися до JSON Schema, що описує ці дані (навіть не до власної версії JSON Schema в OpenAPI), натомість вони додавалися безпосередньо до декларації *операції шляху* в OpenAPI (поза межами частин OpenAPI, які використовують JSON Schema).
-Але тепер, коли FastAPI 0.99.0 і вище використовують OpenAPI 3.1.0, а той — JSON Schema 2020-12, разом із Swagger UI 5.0.0 і вище — все стало більш узгодженим, і examples тепер включаються до JSON Schema.
+Але тепер, коли FastAPI 0.99.0 і вище використовує OpenAPI 3.1.0, який використовує JSON Schema 2020-12, і Swagger UI 5.0.0 і вище, все стало більш узгодженим, і приклади включаються до JSON Schema.
-### Swagger UI та специфічні для OpenAPI `examples`
+### Swagger UI та специфічні для OpenAPI `examples` { #swagger-ui-and-openapi-specific-examples }
-Раніше (станом на 26 серпня 2023 року) Swagger UI не підтримував кілька прикладів у JSON Schema, тому користувачі не мали можливості показати декілька прикладів у документації.
+Оскільки Swagger UI не підтримував кілька прикладів JSON Schema (станом на 2023-08-26), користувачі не мали можливості показати кілька прикладів у документації.
-Щоб вирішити це, FastAPI починаючи з версії 0.103.0 **додав підтримку** старого **OpenAPI-специфічного** поля `examples` через новий параметр `openapi_examples`. 🤓
+Щоб вирішити це, FastAPI `0.103.0` **додав підтримку** оголошення того самого старого **OpenAPI-специфічного** поля `examples` через новий параметр `openapi_examples`. 🤓
-### Підсумок
+### Підсумок { #summary }
-Раніше я казав, що не люблю історію... а тепер ось я — розповідаю "технічні історичні" лекції. 😅
+Раніше я казав, що не дуже люблю історію... а тепер подивіться на мене — читаю «технічні історичні» лекції. 😅
Коротко: **оновіться до FastAPI 0.99.0 або вище** — і все стане значно **простішим, узгодженим та інтуїтивно зрозумілим**, і Вам не доведеться знати всі ці історичні деталі. 😎
diff --git a/docs/uk/docs/tutorial/security/index.md b/docs/uk/docs/tutorial/security/index.md
index c3d94be8d9..f1fb25178d 100644
--- a/docs/uk/docs/tutorial/security/index.md
+++ b/docs/uk/docs/tutorial/security/index.md
@@ -1,70 +1,72 @@
-# Безпека
+# Безпека { #security }
Існує багато способів реалізувати безпеку, автентифікацію та авторизацію.
-Це зазвичай складна і "непроста" тема.
+І зазвичай це складна і «непроста» тема.
-У багатьох фреймворках і системах забезпечення безпеки та автентифікації займає величезну частину зусиль і коду (іноді — понад 50% всього написаного коду).
+У багатьох фреймворках і системах лише обробка безпеки та автентифікації потребує великих зусиль і коду (у багатьох випадках це може бути 50% або більше від усього написаного коду).
-**FastAPI** надає кілька інструментів, які допоможуть Вам впоратися з **безпекою** легко, швидко, стандартним способом, без необхідності вивчати всі специфікації безпеки.
+**FastAPI** надає кілька інструментів, які допоможуть вам працювати з **безпекою** легко, швидко, стандартним способом, без необхідності вивчати всі специфікації безпеки.
-Але спочатку — кілька коротких понять.
+Але спочатку розгляньмо кілька невеликих понять.
-## Поспішаєте?
+## Поспішаєте? { #in-a-hurry }
-Якщо Вам не цікаві всі ці терміни й просто потрібно *швидко* додати автентифікацію за логіном і паролем — переходьте до наступних розділів.
+Якщо вам не цікаві всі ці терміни й вам просто потрібно додати безпеку з автентифікацією на основі імені користувача та пароля *прямо зараз*, переходьте до наступних розділів.
-## OAuth2
+## OAuth2 { #oauth2 }
-OAuth2 — це специфікація, що описує кілька способів обробки автентифікації та авторизації.
+OAuth2 — це специфікація, що визначає кілька способів обробки автентифікації та авторизації.
-Це досить об'ємна специфікація, яка охоплює складні випадки використання.
+Це досить об'ємна специфікація, яка охоплює кілька складних випадків використання.
-Вона включає способи автентифікації через "третю сторону".
+Вона включає способи автентифікації через «третю сторону».
-Саме це лежить в основі "входу через Google, Facebook, X (Twitter), GitHub" тощо.
+Саме це лежить в основі всіх систем із «увійти через Facebook, Google, X (Twitter), GitHub».
-### OAuth 1
+### OAuth 1 { #oauth-1 }
-Раніше існував OAuth 1, який значно відрізняється від OAuth2 і є складнішим, оскільки містив специфікації для шифрування комунікацій.
+Раніше існував OAuth 1, який значно відрізняється від OAuth2 і є складнішим, оскільки містив прямі специфікації щодо того, як шифрувати комунікацію.
-Зараз майже не використовується.
+Зараз він не дуже популярний або використовується.
-OAuth2 не вказує, як саме шифрувати з'єднання — воно очікує, що ваш застосунок працює через HTTPS.
+OAuth2 не вказує, як саме шифрувати комунікацію — він очікує, що ваш застосунок доступний через HTTPS.
/// tip | Порада
-У розділі про **деплой** Ви побачите, як налаштувати HTTPS безкоштовно з Traefik та Let's Encrypt.
+У розділі про **деплой** ви побачите, як налаштувати HTTPS безкоштовно з Traefik та Let's Encrypt.
///
-## OpenID Connect
+## OpenID Connect { #openid-connect }
OpenID Connect — ще одна специфікація, побудована на основі **OAuth2**.
-Вона розширює OAuth2, уточнюючи деякі неоднозначності для досягнення кращої сумісності.
+Вона просто розширює OAuth2, уточнюючи деякі відносно неоднозначні речі в OAuth2, щоб зробити його більш сумісним.
-Наприклад, вхід через Google використовує OpenID Connect (який базується на OAuth2).
+Наприклад, вхід через Google використовує OpenID Connect (який під капотом використовує OAuth2).
-Але вхід через Facebook — ні. Він має власну реалізацію на базі OAuth2.
+Але вхід через Facebook не підтримує OpenID Connect. Він має власний різновид OAuth2.
-### OpenID (не "OpenID Connect")
+### OpenID (не «OpenID Connect») { #openid-not-openid-connect }
-Існувала також специфікація "OpenID", яка намагалася розвʼязати ті самі задачі, що й **OpenID Connect**, але не базувалась на OAuth2.
+Існувала також специфікація «OpenID». Вона намагалася розвʼязати те саме, що й **OpenID Connect**, але не базувалась на OAuth2.
-Це була зовсім інша система, і сьогодні вона майже не використовується.
+Тож це була повністю додаткова система.
-## OpenAPI
+Зараз вона не дуже популярна або використовується.
-OpenAPI (раніше Swagger) — це специфікація для побудови API (тепер під егідою Linux Foundation).
+## OpenAPI { #openapi }
+
+OpenAPI (раніше відомий як Swagger) — це відкрита специфікація для побудови API (тепер частина Linux Foundation).
**FastAPI** базується на **OpenAPI**.
-Завдяки цьому Ви отримуєте автоматичну інтерактивну документацію, генерацію коду та багато іншого.
+Саме це робить можливими кілька автоматичних інтерактивних інтерфейсів документації, генерацію коду тощо.
-OpenAPI дозволяє описувати різні "схеми" безпеки.
+OpenAPI має спосіб визначати різні «схеми» безпеки.
-Використовуючи їх, Ви можете скористатися всіма цими інструментами, що базуються на стандартах, зокрема інтерактивними системами документації.
+Використовуючи їх, ви можете скористатися всіма цими інструментами, що базуються на стандартах, зокрема цими інтерактивними системами документації.
OpenAPI визначає такі схеми безпеки:
@@ -72,33 +74,33 @@ OpenAPI визначає такі схеми безпеки:
* Параметр запиту.
* Заголовок.
* Cookie.
-* `http`: стандартні методи HTTP-автентифікації, включаючи:
- * `bearer`: заголовок `Authorization` зі значенням `Bearer` та токеном. Це успадковано з OAuth2.
- * HTTP Basic автентифікація
- * HTTP Digest, тощо.
+* `http`: стандартні системи HTTP-автентифікації, включаючи:
+ * `bearer`: заголовок `Authorization` зі значенням `Bearer ` та токеном. Це успадковано з OAuth2.
+ * HTTP Basic автентифікацію.
+ * HTTP Digest тощо.
* `oauth2`: усі способи обробки безпеки за допомогою OAuth2 (так звані «потоки»).
- * Деякі з цих потоків підходять для створення власного провайдера автентифікації OAuth 2.0 (наприклад, Google, Facebook, X (Twitter), GitHub тощо):
- * `implicit`— неявний
- * `clientCredentials`— облікові дані клієнта
- * `authorizationCode` — код авторизації
- * Але є один окремий «потік», який ідеально підходить для реалізації автентифікації всередині одного додатку:
- * `password`: у наступних розділах буде приклад використання цього потоку.
-* `openIdConnect`: дозволяє автоматично виявляти параметри автентифікації OAuth2.
- * Це автоматичне виявлення визначається у специфікації OpenID Connect.
+ * Декілька з цих потоків підходять для створення провайдера автентифікації OAuth 2.0 (наприклад, Google, Facebook, X (Twitter), GitHub тощо):
+ * `implicit`
+ * `clientCredentials`
+ * `authorizationCode`
+ * Але є один окремий «потік», який можна ідеально використати для обробки автентифікації напряму в цьому ж застосунку:
+ * `password`: у кількох наступних розділах будуть приклади цього.
+* `openIdConnect`: має спосіб визначити, як автоматично виявляти дані автентифікації OAuth2.
+ * Саме це автоматичне виявлення визначено у специфікації OpenID Connect.
/// tip | Порада
-Інтеграція інших провайдерів автентифікації/авторизації, таких як Google, Facebook, X (Twitter), GitHub тощо — також можлива і відносно проста.
+Інтеграція інших провайдерів автентифікації/авторизації, таких як Google, Facebook, X (Twitter), GitHub тощо, також можлива і відносно проста.
-Найскладніше — це створити власного провайдера автентифікації/авторизації, як Google чи Facebook. Але **FastAPI** надає Вам інструменти, щоб зробити це легко, беручи на себе важку частину роботи.
+Найскладніше — це створити провайдера автентифікації/авторизації на кшталт таких, але **FastAPI** надає вам інструменти, щоб зробити це легко, виконуючи важку частину роботи за вас.
///
-## Інструменти **FastAPI**
+## Утиліти **FastAPI** { #fastapi-utilities }
-FastAPI надає кілька інструментів для кожної з описаних схем безпеки в модулі `fastapi.security`, які спрощують використання цих механізмів захисту.
+FastAPI надає кілька інструментів для кожної з описаних схем безпеки в модулі `fastapi.security`, які спрощують використання цих механізмів безпеки.
-У наступних розділах Ви побачите, як додати безпеку до свого API за допомогою цих інструментів **FastAPI**.
+У наступних розділах ви побачите, як додати безпеку до свого API за допомогою цих інструментів, які надає **FastAPI**.
-А також побачите, як вона автоматично інтегрується в інтерактивну документацію вашого API.
+А також побачите, як це автоматично інтегрується в інтерактивну систему документації.
diff --git a/docs/uk/docs/tutorial/static-files.md b/docs/uk/docs/tutorial/static-files.md
index 3427f23765..32ca1311d2 100644
--- a/docs/uk/docs/tutorial/static-files.md
+++ b/docs/uk/docs/tutorial/static-files.md
@@ -1,13 +1,13 @@
-# Статичні файли
+# Статичні файли { #static-files }
Ви можете автоматично надавати статичні файли з каталогу, використовуючи `StaticFiles`.
-## Використання `StaticFiles`
+## Використання `StaticFiles` { #use-staticfiles }
* Імпортуйте `StaticFiles`.
-* "Під'єднати" екземпляр `StaticFiles()` з вказанням необхідного шляху.
+* «Під'єднати» екземпляр `StaticFiles()` з вказанням необхідного шляху.
-{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
+{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *}
/// note | Технічні деталі
@@ -17,24 +17,24 @@
///
-### Що таке "Під'єднання"
+### Що таке «Під'єднання» { #what-is-mounting }
-"Під'єднання" означає додавання повноцінного "незалежного" застосунку за певним шляхом, який потім обробляє всі під шляхи.
+«Під'єднання» означає додавання повноцінного «незалежного» застосунку за певним шляхом, який потім обробляє всі під шляхи.
-Це відрізняється від використання `APIRouter`, оскільки під'єднаний застосунок є повністю незалежним. OpenAPI та документація вашого основного застосунку не будуть знати нічого про ваш під'єднаний застосунок.
+Це відрізняється від використання `APIRouter`, оскільки під'єднаний застосунок є повністю незалежним. OpenAPI та документація вашого основного застосунку не будуть знати нічого про ваш під'єднаний застосунок тощо.
Ви можете дізнатися більше про це в [Посібнику для просунутих користувачів](../advanced/index.md){.internal-link target=_blank}.
-## Деталі
+## Деталі { #details }
-Перше `"/static"` вказує на під шлях, за яким буде "під'єднано" цей новий "застосунок". Тому будь-який шлях, який починається з `"/static"`, буде оброблятися ним.
+Перше `"/static"` вказує на під шлях, за яким буде «під'єднано» цей новий «підзастосунок». Тому будь-який шлях, який починається з `"/static"`, буде оброблятися ним.
-`directory="static"` визначає каталог, що містить ваші статичні файли.
+`directory="static"` визначає назву каталогу, що містить ваші статичні файли.
`name="static"` це ім'я, яке можна використовувати всередині **FastAPI**.
-Усі ці параметри можуть бути змінені відповідно до потреб і особливостей вашого застосунку.
+Усі ці параметри можуть бути іншими за "`static`", налаштуйте їх відповідно до потреб і особливостей вашого застосунку.
-## Додаткова інформація
+## Додаткова інформація { #more-info }
Детальніше про налаштування та можливості можна дізнатися в документації Starlette про статичні файли.
diff --git a/docs/uk/docs/tutorial/testing.md b/docs/uk/docs/tutorial/testing.md
index 1105c6b0a8..462592829f 100644
--- a/docs/uk/docs/tutorial/testing.md
+++ b/docs/uk/docs/tutorial/testing.md
@@ -1,17 +1,18 @@
-# Тестування
+# Тестування { #testing }
-Тестування **FastAPI** додатків є простим та ефективним завдяки бібліотеці Starlette, яка базується на HTTPX.
-Оскільки HTTPX розроблений на основі Requests, його API є інтуїтивно зрозумілим для тих, хто вже знайомий з Requests.
+Завдяки Starlette тестувати застосунки **FastAPI** просто й приємно.
-З його допомогою Ви можете використовувати pytest безпосередньо з **FastAPI**.
+Воно базується на HTTPX, який, своєю чергою, спроєктований на основі Requests, тож він дуже знайомий та інтуїтивно зрозумілий.
-## Використання `TestClient`
+З його допомогою ви можете використовувати pytest безпосередньо з **FastAPI**.
+
+## Використання `TestClient` { #using-testclient }
/// info | Інформація
Щоб використовувати `TestClient`, спочатку встановіть `httpx`.
-Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили саму бібліотеку, наприклад:
+Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили `httpx`, наприклад:
```console
$ pip install httpx
@@ -21,7 +22,7 @@ $ pip install httpx
Імпортуйте `TestClient`.
-Створіть `TestClient`, передавши йому Ваш застосунок **FastAPI**.
+Створіть `TestClient`, передавши йому ваш застосунок **FastAPI**.
Створюйте функції з іменами, що починаються з `test_` (це стандартна угода для `pytest`).
@@ -29,8 +30,7 @@ $ pip install httpx
Записуйте прості `assert`-вирази зі стандартними виразами Python, які потрібно перевірити (це також стандарт для `pytest`).
-{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
-
+{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *}
/// tip | Порада
@@ -46,25 +46,25 @@ $ pip install httpx
Ви також можете використовувати `from starlette.testclient import TestClient`.
-**FastAPI** надає той самий `starlette.testclient` під назвою `fastapi.testclient` для зручності розробників, але він безпосередньо походить із Starlette.
+**FastAPI** надає той самий `starlette.testclient` під назвою `fastapi.testclient` просто для зручності для вас, розробника. Але він безпосередньо походить із Starlette.
///
/// tip | Порада
-Якщо Вам потрібно викликати `async`-функції у ваших тестах, окрім відправлення запитів до FastAPI-застосунку (наприклад, асинхронні функції роботи з базою даних), перегляньте [Асинхронні тести](../advanced/async-tests.md){.internal-link target=_blank} у розширеному керівництві.
+Якщо ви хочете викликати `async`-функції у ваших тестах, окрім відправлення запитів до вашого застосунку FastAPI (наприклад, асинхронні функції роботи з базою даних), перегляньте [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} у розширеному керівництві.
///
-## Розділення тестів
+## Розділення тестів { #separating-tests }
-У реальному застосунку Ваші тести, ймовірно, будуть в окремому файлі.
+У реальному застосунку ваші тести, ймовірно, будуть в окремому файлі.
-Також Ваш **FastAPI**-застосунок може складатися з кількох файлів або модулів тощо.
+Також ваш застосунок **FastAPI** може складатися з кількох файлів/модулів тощо.
-### Файл застосунку **FastAPI**
+### Файл застосунку **FastAPI** { #fastapi-app-file }
-Припустимо, у Вас є структура файлів, описана в розділі [Більші застосунки](bigger-applications.md){.internal-link target=_blank}:
+Припустимо, у вас є структура файлів, описана в розділі [Bigger Applications](bigger-applications.md){.internal-link target=_blank}:
```
.
@@ -72,14 +72,15 @@ $ pip install httpx
│ ├── __init__.py
│ └── main.py
```
-У файлі `main.py` знаходиться Ваш застосунок **FastAPI** :
-{* ../../docs_src/app_testing/main.py *}
+У файлі `main.py` знаходиться ваш застосунок **FastAPI**:
-### Файл тестування
-Ви можете створити файл `test_main.py` з Вашими тестами. Він може знаходитися в тому ж пакеті Python (у тій самій директорії з файлом `__init__.py`):
+{* ../../docs_src/app_testing/app_a_py39/main.py *}
+### Файл тестування { #testing-file }
+
+Ви можете створити файл `test_main.py` з вашими тестами. Він може знаходитися в тому ж пакеті Python (у тій самій директорії з файлом `__init__.py`):
``` hl_lines="5"
.
@@ -89,18 +90,18 @@ $ pip install httpx
│ └── test_main.py
```
-Оскільки цей файл знаходиться в тому ж пакеті, Ви можете використовувати відносний імпорт, щоб імпортувати об'єкт `app` із модуля `main` (`main.py`):
+Оскільки цей файл знаходиться в тому ж пакеті, ви можете використовувати відносний імпорт, щоб імпортувати об'єкт `app` із модуля `main` (`main.py`):
-{* ../../docs_src/app_testing/test_main.py hl[3] *}
+{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *}
...і написати код для тестів так само як і раніше.
-## Тестування: розширений приклад
+## Тестування: розширений приклад { #testing-extended-example }
Тепер розширимо цей приклад і додамо більше деталей, щоб побачити, як тестувати різні частини.
-### Розширений файл застосунку **FastAPI**
+### Розширений файл застосунку **FastAPI** { #extended-fastapi-app-file }
Залишимо ту саму структуру файлів:
@@ -112,75 +113,26 @@ $ pip install httpx
│ └── test_main.py
```
-Припустимо, що тепер файл `main.py` із Вашим **FastAPI**-застосунком містить додаткові операції шляху (**path operations**).
+Припустимо, що тепер файл `main.py` із вашим застосунком **FastAPI** містить інші **операції шляху**.
Він має `GET`-операцію, яка може повертати помилку.
Він має `POST`-операцію, яка може повертати кілька помилок.
-Обидві операції шляху вимагають заголовок `X-Token`.
+Обидві *операції шляху* вимагають заголовок `X-Token`.
-//// tab | Python 3.10+
+{* ../../docs_src/app_testing/app_b_an_py310/main.py *}
-```Python
-{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
-```
+### Розширений тестовий файл { #extended-testing-file }
-////
+Потім ви можете оновити `test_main.py`, додавши розширені тести:
-//// tab | Python 3.9+
+{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *}
-```Python
-{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
-```
-////
+Коли вам потрібно передати клієнту інформацію в запиті, але ви не знаєте, як це зробити, ви можете пошукати (Google), як це зробити в `httpx`, або навіть як це зробити з `requests`, оскільки дизайн HTTPX базується на дизайні Requests.
-//// tab | Python 3.8+
-
-```Python
-{!> ../../docs_src/app_testing/app_b_an/main.py!}
-```
-
-////
-
-//// tab | Python 3.10+ non-Annotated
-
-/// tip | Порада
-
-Бажано використовувати версію з `Annotated`, якщо це можливо
-
-///
-
-```Python
-{!> ../../docs_src/app_testing/app_b_py310/main.py!}
-```
-
-////
-
-//// tab | Python 3.8+ non-Annotated
-
-/// tip | Порада
-
-Бажано використовувати версію з `Annotated`, якщо це можливо
-
-///
-
-```Python
-{!> ../../docs_src/app_testing/app_b/main.py!}
-```
-
-////
-
-### Розширений тестовий файл
-
-Потім Ви можете оновити `test_main.py`, додавши розширені тести:
-
-{* ../../docs_src/app_testing/app_b/test_main.py *}
-
-Коли Вам потрібно передати клієнту інформацію в запиті, але Ви не знаєте, як це зробити, Ви можете пошукати (наприклад, у Google) спосіб реалізації в `httpx`, або навіть у `requests`, оскільки HTTPX розроблений на основі дизайну Requests.
-
-Далі Ви просто повторюєте ці ж дії у ваших тестах.
+Далі ви просто повторюєте ці ж дії у ваших тестах.
Наприклад:
@@ -195,15 +147,16 @@ $ pip install httpx
/// info | Інформація
Зверніть увагу, що `TestClient` отримує дані, які можна конвертувати в JSON, а не Pydantic-моделі.
-Якщо у Вас є Pydantic-модель у тесті, і Ви хочете передати її дані в додаток під час тестування, Ви можете використати `jsonable_encoder`, описаний у розділі [JSON Compatible Encoder](encoder.md){.internal-link target=_blank}.
+
+Якщо у вас є Pydantic-модель у тесті, і ви хочете передати її дані в застосунок під час тестування, ви можете використати `jsonable_encoder`, описаний у розділі [JSON Compatible Encoder](encoder.md){.internal-link target=_blank}.
///
-## Запуск тестів
+## Запуск { #run-it }
Після цього вам потрібно встановити `pytest`.
-Переконайтеся, що Ви створили [віртуальне середовище]{.internal-link target=_blank}, активували його і встановили необхідні пакети, наприклад:
+Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його і встановили необхідні пакети, наприклад:
- FastAPI framework, hiệu năng cao, dễ học, dễ code, sẵn sàng để tạo ra sản phẩm -
- - ---- - -**Tài liệu**: https://fastapi.tiangolo.com - -**Mã nguồn**: https://github.com/fastapi/fastapi - ---- - -FastAPI là một web framework hiện đại, hiệu năng cao để xây dựng web APIs với Python dựa trên tiêu chuẩn Python type hints. - -Những tính năng như: - -* **Nhanh**: Hiệu năng rất cao khi so sánh với **NodeJS** và **Go** (cảm ơn Starlette và Pydantic). [Một trong những Python framework nhanh nhất](#hieu-nang). -* **Code nhanh**: Tăng tốc độ phát triển tính năng từ 200% tới 300%. * -* **Ít lỗi hơn**: Giảm khoảng 40% những lỗi phát sinh bởi con người (nhà phát triển). * -* **Trực giác tốt hơn**: Được các trình soạn thảo hỗ tuyệt vời. Completion mọi nơi. Ít thời gian gỡ lỗi. -* **Dễ dàng**: Được thiết kế để dễ dàng học và sử dụng. Ít thời gian đọc tài liệu. -* **Ngắn**: Tối thiểu code bị trùng lặp. Nhiều tính năng được tích hợp khi định nghĩa tham số. Ít lỗi hơn. -* **Tăng tốc**: Có được sản phẩm cùng với tài liệu (được tự động tạo) có thể tương tác. -* **Được dựa trên các tiêu chuẩn**: Dựa trên (và hoàn toàn tương thích với) các tiêu chuẩn mở cho APIs : OpenAPI (trước đó được biết đến là Swagger) và JSON Schema. - -* ước tính được dựa trên những kiểm chứng trong nhóm phát triển nội bộ, xây dựng các ứng dụng sản phẩm. - -## Nhà tài trợ - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} -async def...uvicorn main:app --reload...email-validator - cho email validation.
-
-Sử dụng Starlette:
-
-* httpx - Bắt buộc nếu bạn muốn sử dụng `TestClient`.
-* jinja2 - Bắt buộc nếu bạn muốn sử dụng cấu hình template engine mặc định.
-* python-multipart - Bắt buộc nếu bạn muốn hỗ trợ "parsing", form với `request.form()`.
-* itsdangerous - Bắt buộc để hỗ trợ `SessionMiddleware`.
-* pyyaml - Bắt buộc để hỗ trợ `SchemaGenerator` cho Starlette (bạn có thể không cần nó trong FastAPI).
-
-Sử dụng bởi FastAPI / Starlette:
-
-* uvicorn - Server để chạy ứng dụng của bạn.
-* orjson - Bắt buộc nếu bạn muốn sử dụng `ORJSONResponse`.
-* ujson - Bắt buộc nếu bạn muốn sử dụng `UJSONResponse`.
-
-Bạn có thể cài đặt tất cả những dependency trên với `pip install "fastapi[all]"`.
-
-## Giấy phép
-
-Dự án này được cấp phép dưới những điều lệ của giấy phép MIT.
diff --git a/docs/vi/docs/python-types.md b/docs/vi/docs/python-types.md
deleted file mode 100644
index 403e899300..0000000000
--- a/docs/vi/docs/python-types.md
+++ /dev/null
@@ -1,593 +0,0 @@
-# Giới thiệu kiểu dữ liệu Python
-
-Python hỗ trợ tùy chọn "type hints" (còn được gọi là "type annotations").
-
-Những **"type hints"** hay chú thích là một cú pháp đặc biệt cho phép khai báo kiểu dữ liệu của một biến.
-
-Bằng việc khai báo kiểu dữ liệu cho các biến của bạn, các trình soạn thảo và các công cụ có thể hỗ trợ bạn tốt hơn.
-
-Đây chỉ là một **hướng dẫn nhanh** về gợi ý kiểu dữ liệu trong Python. Nó chỉ bao gồm những điều cần thiết tối thiểu để sử dụng chúng với **FastAPI**... đó thực sự là rất ít.
-
-**FastAPI** hoàn toàn được dựa trên những gợi ý kiểu dữ liệu, chúng mang đến nhiều ưu điểm và lợi ích.
-
-Nhưng thậm chí nếu bạn không bao giờ sử dụng **FastAPI**, bạn sẽ được lợi từ việc học một ít về chúng.
-
-/// note
-
-Nếu bạn là một chuyên gia về Python, và bạn đã biết mọi thứ về gợi ý kiểu dữ liệu, bỏ qua và đi tới chương tiếp theo.
-
-///
-
-## Động lực
-
-Hãy bắt đầu với một ví dụ đơn giản:
-
-{* ../../docs_src/python_types/tutorial001.py *}
-
-
-Kết quả khi gọi chương trình này:
-
-```
-John Doe
-```
-
-Hàm thực hiện như sau:
-
-* Lấy một `first_name` và `last_name`.
-* Chuyển đổi kí tự đầu tiên của mỗi biến sang kiểu chữ hoa với `title()`.
-* Nối chúng lại với nhau bằng một kí tự trắng ở giữa.
-
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
-
-
-### Sửa đổi
-
-Nó là một chương trình rất đơn giản.
-
-Nhưng bây giờ hình dung rằng bạn đang viết nó từ đầu.
-
-Tại một vài thời điểm, bạn sẽ bắt đầu định nghĩa hàm, bạn có các tham số...
-
-Nhưng sau đó bạn phải gọi "phương thức chuyển đổi kí tự đầu tiên sang kiểu chữ hoa".
-
-Có phải là `upper`? Có phải là `uppercase`? `first_uppercase`? `capitalize`?
-
-Sau đó, bạn thử hỏi người bạn cũ của mình, autocompletion của trình soạn thảo.
-
-Bạn gõ tham số đầu tiên của hàm, `first_name`, sau đó một dấu chấm (`.`) và sau đó ấn `Ctrl+Space` để kích hoạt bộ hoàn thành.
-
-Nhưng đáng buồn, bạn không nhận được điều gì hữu ích cả:
-
-
-
-### Thêm kiểu dữ liệu
-
-Hãy sửa một dòng từ phiên bản trước.
-
-Chúng ta sẽ thay đổi chính xác đoạn này, tham số của hàm, từ:
-
-```Python
- first_name, last_name
-```
-
-sang:
-
-```Python
- first_name: str, last_name: str
-```
-
-Chính là nó.
-
-Những thứ đó là "type hints":
-
-{* ../../docs_src/python_types/tutorial002.py hl[1] *}
-
-
-Đó không giống như khai báo những giá trị mặc định giống như:
-
-```Python
- first_name="john", last_name="doe"
-```
-
-Nó là một thứ khác.
-
-Chúng ta sử dụng dấu hai chấm (`:`), không phải dấu bằng (`=`).
-
-Và việc thêm gợi ý kiểu dữ liệu không làm thay đổi những gì xảy ra so với khi chưa thêm chúng.
-
-But now, imagine you are again in the middle of creating that function, but with type hints.
-
-Tại cùng một điểm, bạn thử kích hoạt autocomplete với `Ctrl+Space` và bạn thấy:
-
-
-
-Với cái đó, bạn có thể cuộn, nhìn thấy các lựa chọn, cho đến khi bạn tìm thấy một "tiếng chuông":
-
-
-
-## Động lực nhiều hơn
-
-Kiểm tra hàm này, nó đã có gợi ý kiểu dữ liệu:
-
-{* ../../docs_src/python_types/tutorial003.py hl[1] *}
-
-
-Bởi vì trình soạn thảo biết kiểu dữ liệu của các biến, bạn không chỉ có được completion, bạn cũng được kiểm tra lỗi:
-
-
-
-Bây giờ bạn biết rằng bạn phải sửa nó, chuyển `age` sang một xâu với `str(age)`:
-
-{* ../../docs_src/python_types/tutorial004.py hl[2] *}
-
-
-## Khai báo các kiểu dữ liệu
-
-Bạn mới chỉ nhìn thấy những nơi chủ yếu để đặt khai báo kiểu dữ liệu. Như là các tham số của hàm.
-
-Đây cũng là nơi chủ yếu để bạn sử dụng chúng với **FastAPI**.
-
-### Kiểu dữ liệu đơn giản
-
-Bạn có thể khai báo tất cả các kiểu dữ liệu chuẩn của Python, không chỉ là `str`.
-
-Bạn có thể sử dụng, ví dụ:
-
-* `int`
-* `float`
-* `bool`
-* `bytes`
-
-{* ../../docs_src/python_types/tutorial005.py hl[1] *}
-
-
-### Các kiểu dữ liệu tổng quát với tham số kiểu dữ liệu
-
-Có một vài cấu trúc dữ liệu có thể chứa các giá trị khác nhau như `dict`, `list`, `set` và `tuple`. Và những giá trị nội tại cũng có thể có kiểu dữ liệu của chúng.
-
-Những kiểu dữ liệu nội bộ này được gọi là những kiểu dữ liệu "**tổng quát**". Và có khả năng khai báo chúng, thậm chí với các kiểu dữ liệu nội bộ của chúng.
-
-Để khai báo những kiểu dữ liệu và những kiểu dữ liệu nội bộ đó, bạn có thể sử dụng mô đun chuẩn của Python là `typing`. Nó có hỗ trợ những gợi ý kiểu dữ liệu này.
-
-#### Những phiên bản mới hơn của Python
-
-Cú pháp sử dụng `typing` **tương thích** với tất cả các phiên bản, từ Python 3.6 tới những phiên bản cuối cùng, bao gồm Python 3.9, Python 3.10,...
-
-As Python advances, **những phiên bản mới** mang tới sự hỗ trợ được cải tiến cho những chú thích kiểu dữ liệu và trong nhiều trường hợp bạn thậm chí sẽ không cần import và sử dụng mô đun `typing` để khai báo chú thích kiểu dữ liệu.
-
-Nếu bạn có thể chọn một phiên bản Python gần đây hơn cho dự án của bạn, ban sẽ có được những ưu điểm của những cải tiến đơn giản đó.
-
-Trong tất cả các tài liệu tồn tại những ví dụ tương thích với mỗi phiên bản Python (khi có một sự khác nhau).
-
-Cho ví dụ "**Python 3.6+**" có nghĩa là nó tương thích với Python 3.7 hoặc lớn hơn (bao gồm 3.7, 3.8, 3.9, 3.10,...). và "**Python 3.9+**" nghĩa là nó tương thích với Python 3.9 trở lên (bao gồm 3.10,...).
-
-Nếu bạn có thể sử dụng **phiên bản cuối cùng của Python**, sử dụng những ví dụ cho phiên bản cuối, những cái đó sẽ có **cú pháp đơn giản và tốt nhât**, ví dụ, "**Python 3.10+**".
-
-#### List
-
-Ví dụ, hãy định nghĩa một biến là `list` các `str`.
-
-//// tab | Python 3.9+
-
-Khai báo biến với cùng dấu hai chấm (`:`).
-
-Tương tự kiểu dữ liệu `list`.
-
-Như danh sách là một kiểu dữ liệu chứa một vài kiểu dữ liệu có sẵn, bạn đặt chúng trong các dấu ngoặc vuông:
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-Từ `typing`, import `List` (với chữ cái `L` viết hoa):
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-Khai báo biến với cùng dấu hai chấm (`:`).
-
-Tương tự như kiểu dữ liệu, `List` bạn import từ `typing`.
-
-Như danh sách là một kiểu dữ liệu chứa các kiểu dữ liệu có sẵn, bạn đặt chúng bên trong dấu ngoặc vuông:
-
-```Python hl_lines="4"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-////
-
-/// info
-
-Các kiểu dữ liệu có sẵn bên trong dấu ngoặc vuông được gọi là "tham số kiểu dữ liệu".
-
-Trong trường hợp này, `str` là tham số kiểu dữ liệu được truyền tới `List` (hoặc `list` trong Python 3.9 trở lên).
-
-///
-
-Có nghĩa là: "biến `items` là một `list`, và mỗi phần tử trong danh sách này là một `str`".
-
-/// tip
-
-Nếu bạn sử dụng Python 3.9 hoặc lớn hơn, bạn không phải import `List` từ `typing`, bạn có thể sử dụng `list` để thay thế.
-
-///
-
-Bằng cách này, trình soạn thảo của bạn có thể hỗ trợ trong khi xử lí các phần tử trong danh sách:
-
-
-
-Đa phần đều không thể đạt được nếu không có các kiểu dữ liệu.
-
-Chú ý rằng, biến `item` là một trong các phần tử trong danh sách `items`.
-
-Và do vậy, trình soạn thảo biết nó là một `str`, và cung cấp sự hỗ trợ cho nó.
-
-#### Tuple and Set
-
-Bạn sẽ làm điều tương tự để khai báo các `tuple` và các `set`:
-
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial007_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial007.py!}
-```
-
-////
-
-Điều này có nghĩa là:
-
-* Biến `items_t` là một `tuple` với 3 phần tử, một `int`, một `int` nữa, và một `str`.
-* Biến `items_s` là một `set`, và mỗi phần tử của nó có kiểu `bytes`.
-
-#### Dict
-
-Để định nghĩa một `dict`, bạn truyền 2 tham số kiểu dữ liệu, phân cách bởi dấu phẩy.
-
-Tham số kiểu dữ liệu đầu tiên dành cho khóa của `dict`.
-
-Tham số kiểu dữ liệu thứ hai dành cho giá trị của `dict`.
-
-//// tab | Python 3.9+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008.py!}
-```
-
-////
-
-Điều này có nghĩa là:
-
-* Biến `prices` là một `dict`:
- * Khóa của `dict` này là kiểu `str` (đó là tên của mỗi vật phẩm).
- * Giá trị của `dict` này là kiểu `float` (đó là giá của mỗi vật phẩm).
-
-#### Union
-
-Bạn có thể khai báo rằng một biến có thể là **một vài kiểu dữ liệu" bất kì, ví dụ, một `int` hoặc một `str`.
-
-Trong Python 3.6 hoặc lớn hơn (bao gồm Python 3.10) bạn có thể sử dụng kiểu `Union` từ `typing` và đặt trong dấu ngoặc vuông những giá trị được chấp nhận.
-
-In Python 3.10 there's also a **new syntax** where you can put the possible types separated by a vertical bar (`|`).
-
-Trong Python 3.10 cũng có một **cú pháp mới** mà bạn có thể đặt những kiểu giá trị khả thi phân cách bởi một dấu sổ dọc (`|`).
-
-
-//// tab | Python 3.10+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008b_py310.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008b.py!}
-```
-
-////
-
-Trong cả hai trường hợp có nghĩa là `item` có thể là một `int` hoặc `str`.
-
-#### Khả năng `None`
-
-Bạn có thể khai báo một giá trị có thể có một kiểu dữ liệu, giống như `str`, nhưng nó cũng có thể là `None`.
-
-Trong Python 3.6 hoặc lớn hơn (bao gồm Python 3.10) bạn có thể khai báo nó bằng các import và sử dụng `Optional` từ mô đun `typing`.
-
-```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
-```
-
-Sử dụng `Optional[str]` thay cho `str` sẽ cho phép trình soạn thảo giúp bạn phát hiện các lỗi mà bạn có thể gặp như một giá trị luôn là một `str`, trong khi thực tế nó rất có thể là `None`.
-
-`Optional[Something]` là một cách viết ngắn gọn của `Union[Something, None]`, chúng là tương đương nhau.
-
-Điều này cũng có nghĩa là trong Python 3.10, bạn có thể sử dụng `Something | None`:
-
-//// tab | Python 3.10+
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial009_py310.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009.py!}
-```
-
-////
-
-//// tab | Python 3.8+ alternative
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009b.py!}
-```
-
-////
-
-#### Sử dụng `Union` hay `Optional`
-
-If you are using a Python version below 3.10, here's a tip from my very **subjective** point of view:
-
-Nếu bạn đang sử dụng phiên bản Python dưới 3.10, đây là một mẹo từ ý kiến rất "chủ quan" của tôi:
-
-* 🚨 Tránh sử dụng `Optional[SomeType]`
-* Thay vào đó ✨ **sử dụng `Union[SomeType, None]`** ✨.
-
-Cả hai là tương đương và bên dưới chúng giống nhau, nhưng tôi sẽ đễ xuất `Union` thay cho `Optional` vì từ "**tùy chọn**" có vẻ ngầm định giá trị là tùy chọn, và nó thực sự có nghĩa rằng "nó có thể là `None`", do đó nó không phải là tùy chọn và nó vẫn được yêu cầu.
-
-Tôi nghĩ `Union[SomeType, None]` là rõ ràng hơn về ý nghĩa của nó.
-
-Nó chỉ là về các từ và tên. Nhưng những từ đó có thể ảnh hưởng cách bạn và những đồng đội của bạn suy nghĩ về code.
-
-Cho một ví dụ, hãy để ý hàm này:
-
-{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *}
-
-
-Tham số `name` được định nghĩa là `Optional[str]`, nhưng nó **không phải là tùy chọn**, bạn không thể gọi hàm mà không có tham số:
-
-```Python
-say_hi() # Oh, no, this throws an error! 😱
-```
-
-Tham số `name` **vẫn được yêu cầu** (không phải là *tùy chọn*) vì nó không có giá trị mặc định. Trong khi đó, `name` chấp nhận `None` như là giá trị:
-
-```Python
-say_hi(name=None) # This works, None is valid 🎉
-```
-
-Tin tốt là, khi bạn sử dụng Python 3.10, bạn sẽ không phải lo lắng về điều đó, bạn sẽ có thể sử dụng `|` để định nghĩa hợp của các kiểu dữ liệu một cách đơn giản:
-
-{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
-
-
-Và sau đó, bạn sẽ không phải lo rằng những cái tên như `Optional` và `Union`. 😎
-
-
-#### Những kiểu dữ liệu tổng quát
-
-Những kiểu dữ liệu này lấy tham số kiểu dữ liệu trong dấu ngoặc vuông được gọi là **Kiểu dữ liệu tổng quát**, cho ví dụ:
-
-//// tab | Python 3.10+
-
-Bạn có thể sử dụng các kiểu dữ liệu có sẵn như là kiểu dữ liệu tổng quát (với ngoặc vuông và kiểu dữ liệu bên trong):
-
-* `list`
-* `tuple`
-* `set`
-* `dict`
-
-Và tương tự với Python 3.6, từ mô đun `typing`:
-
-* `Union`
-* `Optional` (tương tự như Python 3.6)
-* ...và các kiểu dữ liệu khác.
-
-Trong Python 3.10, thay vì sử dụng `Union` và `Optional`, bạn có thể sử dụng sổ dọc ('|') để khai báo hợp của các kiểu dữ liệu, điều đó tốt hơn và đơn giản hơn nhiều.
-
-////
-
-//// tab | Python 3.9+
-
-Bạn có thể sử dụng các kiểu dữ liệu có sẵn tương tự như (với ngoặc vuông và kiểu dữ liệu bên trong):
-
-* `list`
-* `tuple`
-* `set`
-* `dict`
-
-Và tương tự với Python 3.6, từ mô đun `typing`:
-
-* `Union`
-* `Optional`
-* ...and others.
-
-////
-
-//// tab | Python 3.8+
-
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Union`
-* `Optional`
-* ...và các kiểu khác.
-
-////
-
-### Lớp như kiểu dữ liệu
-
-Bạn cũng có thể khai báo một lớp như là kiểu dữ liệu của một biến.
-
-Hãy nói rằng bạn muốn có một lớp `Person` với một tên:
-
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
-
-
-Sau đó bạn có thể khai báo một biến có kiểu là `Person`:
-
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
-
-
-Và lại một lần nữa, bạn có được tất cả sự hỗ trợ từ trình soạn thảo:
-
-
-
-Lưu ý rằng, điều này có nghĩa rằng "`one_person`" là một **thực thể** của lớp `Person`.
-
-Nó không có nghĩa "`one_person`" là một **lớp** gọi là `Person`.
-
-## Pydantic models
-
-Pydantic là một thư viện Python để validate dữ liệu hiệu năng cao.
-
-Bạn có thể khai báo "hình dạng" của dữa liệu như là các lớp với các thuộc tính.
-
-Và mỗi thuộc tính có một kiểu dữ liệu.
-
-Sau đó bạn tạo một thực thể của lớp đó với một vài giá trị và nó sẽ validate các giá trị, chuyển đổi chúng sang kiểu dữ liệu phù hợp (nếu đó là trường hợp) và cho bạn một object với toàn bộ dữ liệu.
-
-Và bạn nhận được tất cả sự hỗ trợ của trình soạn thảo với object kết quả đó.
-
-Một ví dụ từ tài liệu chính thức của Pydantic:
-
-//// tab | Python 3.10+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py310.py!}
-```
-
-////
-
-//// tab | Python 3.9+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-```Python
-{!> ../../docs_src/python_types/tutorial011.py!}
-```
-
-////
-
-/// info
-
-Để học nhiều hơn về Pydantic, tham khảo tài liệu của nó.
-
-///
-
-**FastAPI** được dựa hoàn toàn trên Pydantic.
-
-Bạn sẽ thấy nhiều ví dụ thực tế hơn trong [Hướng dẫn sử dụng](tutorial/index.md){.internal-link target=_blank}.
-
-/// tip
-
-Pydantic có một hành vi đặc biệt khi bạn sử dụng `Optional` hoặc `Union[Something, None]` mà không có giá trị mặc dịnh, bạn có thể đọc nhiều hơn về nó trong tài liệu của Pydantic về Required Optional fields.
-
-///
-
-## Type Hints với Metadata Annotations
-
-Python cũng có một tính năng cho phép đặt **metadata bổ sung** trong những gợi ý kiểu dữ liệu này bằng cách sử dụng `Annotated`.
-
-//// tab | Python 3.9+
-
-Trong Python 3.9, `Annotated` là một phần của thư viện chuẩn, do đó bạn có thể import nó từ `typing`.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-Ở phiên bản dưới Python 3.9, bạn import `Annotated` từ `typing_extensions`.
-
-Nó đã được cài đặt sẵng cùng với **FastAPI**.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013.py!}
-```
-
-////
-
-Python bản thân nó không làm bất kì điều gì với `Annotated`. Với các trình soạn thảo và các công cụ khác, kiểu dữ liệu vẫn là `str`.
-
-Nhưng bạn có thể sử dụng `Annotated` để cung cấp cho **FastAPI** metadata bổ sung về cách mà bạn muốn ứng dụng của bạn xử lí.
-
-Điều quan trọng cần nhớ là ***tham số kiểu dữ liệu* đầu tiên** bạn truyền tới `Annotated` là **kiểu giá trị thực sự**. Phần còn lại chỉ là metadata cho các công cụ khác.
-
-Bây giờ, bạn chỉ cần biết rằng `Annotated` tồn tại, và nó là tiêu chuẩn của Python. 😎
-
-
-Sau đó, bạn sẽ thấy sự **mạnh mẽ** mà nó có thể làm.
-
-/// tip
-
-Thực tế, cái này là **tiêu chuẩn của Python**, nghĩa là bạn vẫn sẽ có được **trải nghiệm phát triển tốt nhất có thể** với trình soạn thảo của bạn, với các công cụ bạn sử dụng để phân tích và tái cấu trúc code của bạn, etc. ✨
-
-Và code của bạn sẽ tương thích với nhiều công cụ và thư viện khác của Python. 🚀
-
-///
-
-## Các gợi ý kiểu dữ liệu trong **FastAPI**
-
-**FastAPI** lấy các ưu điểm của các gợi ý kiểu dữ liệu để thực hiện một số thứ.
-
-Với **FastAPI**, bạn khai báo các tham số với gợi ý kiểu và bạn có được:
-
-* **Sự hỗ trợ từ các trình soạn thảo**.
-* **Kiểm tra kiểu dữ liệu (type checking)**.
-
-...và **FastAPI** sử dụng các khia báo để:
-
-* **Định nghĩa các yêu cầu**: từ tham số đường dẫn của request, tham số query, headers, bodies, các phụ thuộc (dependencies),...
-* **Chuyển dổi dữ liệu*: từ request sang kiểu dữ liệu được yêu cầu.
-* **Kiểm tra tính đúng đắn của dữ liệu**: tới từ mỗi request:
- * Sinh **lỗi tự động** để trả về máy khác khi dữ liệu không hợp lệ.
-* **Tài liệu hóa** API sử dụng OpenAPI:
- * cái mà sau được được sử dụng bởi tài liệu tương tác người dùng.
-
-Điều này có thể nghe trừu tượng. Đừng lo lắng. Bạn sẽ thấy tất cả chúng trong [Hướng dẫn sử dụng](tutorial/index.md){.internal-link target=_blank}.
-
-Điều quan trọng là bằng việc sử dụng các kiểu dữ liệu chuẩn của Python (thay vì thêm các lớp, decorators,...), **FastAPI** sẽ thực hiện nhiều công việc cho bạn.
-
-/// info
-
-Nếu bạn đã đi qua toàn bộ các hướng dẫn và quay trở lại để tìm hiểu nhiều hơn về các kiểu dữ liệu, một tài nguyên tốt như "cheat sheet" từ `mypy`.
-
-///
diff --git a/docs/vi/docs/tutorial/first-steps.md b/docs/vi/docs/tutorial/first-steps.md
deleted file mode 100644
index d1650539c1..0000000000
--- a/docs/vi/docs/tutorial/first-steps.md
+++ /dev/null
@@ -1,335 +0,0 @@
-# Những bước đầu tiên
-
-Tệp tin FastAPI đơn giản nhất có thể trông như này:
-
-{* ../../docs_src/first_steps/tutorial001.py *}
-
-Sao chép sang một tệp tin `main.py`.
-
-Chạy live server:
-
-get
-
-/// info | Thông tin về "`@decorator`"
-
-Cú pháp `@something` trong Python được gọi là một "decorator".
-
-Bạn đặt nó trên một hàm. Giống như một chiếc mũ xinh xắn (Tôi ddonas đó là lí do mà thuật ngữ này ra đời).
-
-Một "decorator" lấy một hàm bên dưới và thực hiện một vài thứ với nó.
-
-Trong trường hợp của chúng ta, decorator này nói **FastAPI** rằng hàm bên dưới ứng với **đường dẫn** `/` và một **toán tử** `get`.
-
-Nó là một "**decorator đường dẫn toán tử**".
-
-///
-
-Bạn cũng có thể sử dụng với các toán tử khác:
-
-* `@app.post()`
-* `@app.put()`
-* `@app.delete()`
-
-Và nhiều hơn với các toán tử còn lại:
-
-* `@app.options()`
-* `@app.head()`
-* `@app.patch()`
-* `@app.trace()`
-
-/// tip
-
-Bạn thoải mái sử dụng mỗi toán tử (phương thức HTTP) như bạn mơ ước.
-
-**FastAPI** không bắt buộc bất kì ý nghĩa cụ thể nào.
-
-Thông tin ở đây được biểu thị như là một chỉ dẫn, không phải là một yêu cầu bắt buộc.
-
-Ví dụ, khi sử dụng GraphQL bạn thông thường thực hiện tất cả các hành động chỉ bằng việc sử dụng các toán tử `POST`.
-
-///
-
-### Step 4: Định nghĩa **hàm cho đường dẫn toán tử**
-
-Đây là "**hàm cho đường dẫn toán tử**":
-
-* **đường dẫn**: là `/`.
-* **toán tử**: là `get`.
-* **hàm**: là hàm bên dưới "decorator" (bên dưới `@app.get("/")`).
-
-{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
-
-Đây là một hàm Python.
-
-Nó sẽ được gọi bởi **FastAPI** bất cứ khi nào nó nhận một request tới URL "`/`" sử dụng một toán tử `GET`.
-
-Trong trường hợp này, nó là một hàm `async`.
-
----
-
-Bạn cũng có thể định nghĩa nó như là một hàm thông thường thay cho `async def`:
-
-{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
-
-/// note
-
-Nếu bạn không biết sự khác nhau, kiểm tra [Async: *"Trong khi vội vàng?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
-
-///
-
-### Bước 5: Nội dung trả về
-
-{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
-
-Bạn có thể trả về một `dict`, `list`, một trong những giá trị đơn như `str`, `int`,...
-
-Bạn cũng có thể trả về Pydantic model (bạn sẽ thấy nhiều hơn về nó sau).
-
-Có nhiều object và model khác nhau sẽ được tự động chuyển đổi sang JSON (bao gồm cả ORM,...). Thử sử dụng loại ưa thích của bạn, nó có khả năng cao đã được hỗ trợ.
-
-## Tóm lại
-
-* Import `FastAPI`.
-* Tạo một `app` instance.
-* Viết một **decorator cho đường dẫn toán tử** (giống như `@app.get("/")`).
-* Viết một **hàm cho đường dẫn toán tử** (giống như `def root(): ...` ở trên).
-* Chạy server trong môi trường phát triển (giống như `uvicorn main:app --reload`).
diff --git a/docs/vi/docs/tutorial/index.md b/docs/vi/docs/tutorial/index.md
deleted file mode 100644
index dfeeed8c56..0000000000
--- a/docs/vi/docs/tutorial/index.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# Hướng dẫn sử dụng
-
-Hướng dẫn này cho bạn thấy từng bước cách sử dụng **FastAPI** đa số các tính năng của nó.
-
-Mỗi phần được xây dựng từ những phần trước đó, nhưng nó được cấu trúc thành các chủ đề riêng biệt, do đó bạn có thể xem trực tiếp từng phần cụ thể bất kì để giải quyết những API cụ thể mà bạn cần.
-
-Nó cũng được xây dựng để làm việc như một tham chiếu trong tương lai.
-
-Do đó bạn có thể quay lại và tìm chính xác những gì bạn cần.
-
-## Chạy mã
-
-Tất cả các code block có thể được sao chép và sử dụng trực tiếp (chúng thực chất là các tệp tin Python đã được kiểm thử).
-
-Để chạy bất kì ví dụ nào, sao chép code tới tệp tin `main.py`, và bắt đầu `uvicorn` với:
-
-.*?)(?P(?:(?<= )// .*)|(?:^// .*))?$"
+)
+
+HASH_COMMENT_RE = re.compile(r"^(?P.*?)(?P(?:(?<= )# .*)|(?:^# .*))?$")
+
+
+class CodeIncludeInfo(TypedDict):
+ line_no: int
+ line: str
+
+
+class HeaderPermalinkInfo(TypedDict):
+ line_no: int
+ hashes: str
+ title: str
+ permalink: str
+
+
+class MarkdownLinkInfo(TypedDict):
+ line_no: int
+ url: str
+ text: str
+ title: Union[str, None]
+ attributes: Union[str, None]
+ full_match: str
+
+
+class HTMLLinkAttribute(TypedDict):
+ name: str
+ quote: str
+ value: str
+
+
+class HtmlLinkInfo(TypedDict):
+ line_no: int
+ full_tag: str
+ attributes: list[HTMLLinkAttribute]
+ text: str
+
+
+class MultilineCodeBlockInfo(TypedDict):
+ lang: str
+ start_line_no: int
+ content: list[str]
+
+
+# Code includes
+# --------------------------------------------------------------------------------------
+
+
+def extract_code_includes(lines: list[str]) -> list[CodeIncludeInfo]:
+ """
+ Extract lines that contain code includes.
+
+ Return list of CodeIncludeInfo, where each dict contains:
+ - `line_no` - line number (1-based)
+ - `line` - text of the line
+ """
+
+ includes: list[CodeIncludeInfo] = []
+ for line_no, line in enumerate(lines, start=1):
+ if CODE_INCLUDE_RE.match(line):
+ includes.append(CodeIncludeInfo(line_no=line_no, line=line))
+ return includes
+
+
+def replace_code_includes_with_placeholders(text: list[str]) -> list[str]:
+ """
+ Replace code includes with placeholders.
+ """
+
+ modified_text = text.copy()
+ includes = extract_code_includes(text)
+ for include in includes:
+ modified_text[include["line_no"] - 1] = CODE_INCLUDE_PLACEHOLDER
+ return modified_text
+
+
+def replace_placeholders_with_code_includes(
+ text: list[str], original_includes: list[CodeIncludeInfo]
+) -> list[str]:
+ """
+ Replace code includes placeholders with actual code includes from the original (English) document.
+ Fail if the number of placeholders does not match the number of original includes.
+ """
+
+ code_include_lines = [
+ line_no
+ for line_no, line in enumerate(text)
+ if line.strip() == CODE_INCLUDE_PLACEHOLDER
+ ]
+
+ if len(code_include_lines) != len(original_includes):
+ raise ValueError(
+ "Number of code include placeholders does not match the number of code includes "
+ "in the original document "
+ f"({len(code_include_lines)} vs {len(original_includes)})"
+ )
+
+ modified_text = text.copy()
+ for i, line_no in enumerate(code_include_lines):
+ modified_text[line_no] = original_includes[i]["line"]
+
+ return modified_text
+
+
+# Header permalinks
+# --------------------------------------------------------------------------------------
+
+
+def extract_header_permalinks(lines: list[str]) -> list[HeaderPermalinkInfo]:
+ """
+ Extract list of header permalinks from the given lines.
+
+ Return list of HeaderPermalinkInfo, where each dict contains:
+ - `line_no` - line number (1-based)
+ - `hashes` - string of hashes representing header level (e.g., "###")
+ - `permalink` - permalink string (e.g., "{#permalink}")
+ """
+
+ headers: list[HeaderPermalinkInfo] = []
+ in_code_block3 = False
+ in_code_block4 = False
+
+ for line_no, line in enumerate(lines, start=1):
+ if not (in_code_block3 or in_code_block4):
+ if line.startswith("```"):
+ count = len(line) - len(line.lstrip("`"))
+ if count == 3:
+ in_code_block3 = True
+ continue
+ elif count >= 4:
+ in_code_block4 = True
+ continue
+
+ header_match = HEADER_WITH_PERMALINK_RE.match(line)
+ if header_match:
+ hashes, title, permalink = header_match.groups()
+ headers.append(
+ HeaderPermalinkInfo(
+ hashes=hashes, line_no=line_no, permalink=permalink, title=title
+ )
+ )
+
+ elif in_code_block3:
+ if line.startswith("```"):
+ count = len(line) - len(line.lstrip("`"))
+ if count == 3:
+ in_code_block3 = False
+ continue
+
+ elif in_code_block4:
+ if line.startswith("````"):
+ count = len(line) - len(line.lstrip("`"))
+ if count >= 4:
+ in_code_block4 = False
+ continue
+
+ return headers
+
+
+def remove_header_permalinks(lines: list[str]) -> list[str]:
+ """
+ Remove permalinks from headers in the given lines.
+ """
+
+ modified_lines: list[str] = []
+ for line in lines:
+ header_match = HEADER_WITH_PERMALINK_RE.match(line)
+ if header_match:
+ hashes, title, _permalink = header_match.groups()
+ modified_line = f"{hashes} {title}"
+ modified_lines.append(modified_line)
+ else:
+ modified_lines.append(line)
+ return modified_lines
+
+
+def replace_header_permalinks(
+ text: list[str],
+ header_permalinks: list[HeaderPermalinkInfo],
+ original_header_permalinks: list[HeaderPermalinkInfo],
+) -> list[str]:
+ """
+ Replace permalinks in the given text with the permalinks from the original document.
+
+ Fail if the number or level of headers does not match the original.
+ """
+
+ modified_text: list[str] = text.copy()
+
+ if len(header_permalinks) != len(original_header_permalinks):
+ raise ValueError(
+ "Number of headers with permalinks does not match the number in the "
+ "original document "
+ f"({len(header_permalinks)} vs {len(original_header_permalinks)})"
+ )
+
+ for header_no in range(len(header_permalinks)):
+ header_info = header_permalinks[header_no]
+ original_header_info = original_header_permalinks[header_no]
+
+ if header_info["hashes"] != original_header_info["hashes"]:
+ raise ValueError(
+ "Header levels do not match between document and original document"
+ f" (found {header_info['hashes']}, expected {original_header_info['hashes']})"
+ f" for header №{header_no + 1} in line {header_info['line_no']}"
+ )
+ line_no = header_info["line_no"] - 1
+ hashes = header_info["hashes"]
+ title = header_info["title"]
+ permalink = original_header_info["permalink"]
+ modified_text[line_no] = f"{hashes} {title}{permalink}"
+
+ return modified_text
+
+
+# Markdown links
+# --------------------------------------------------------------------------------------
+
+
+def extract_markdown_links(lines: list[str]) -> list[MarkdownLinkInfo]:
+ """
+ Extract all markdown links from the given lines.
+
+ Return list of MarkdownLinkInfo, where each dict contains:
+ - `line_no` - line number (1-based)
+ - `url` - link URL
+ - `text` - link text
+ - `title` - link title (if any)
+ """
+
+ links: list[MarkdownLinkInfo] = []
+ for line_no, line in enumerate(lines, start=1):
+ for m in MARKDOWN_LINK_RE.finditer(line):
+ links.append(
+ MarkdownLinkInfo(
+ line_no=line_no,
+ url=m.group("url"),
+ text=m.group("text"),
+ title=m.group("title"),
+ attributes=m.group("attrs"),
+ full_match=m.group(0),
+ )
+ )
+ return links
+
+
+def _add_lang_code_to_url(url: str, lang_code: str) -> str:
+ if url.startswith(TIANGOLO_COM):
+ rel_url = url[len(TIANGOLO_COM) :]
+ if not rel_url.startswith(ASSETS_URL_PREFIXES):
+ url = url.replace(TIANGOLO_COM, f"{TIANGOLO_COM}/{lang_code}")
+ return url
+
+
+def _construct_markdown_link(
+ url: str,
+ text: str,
+ title: Union[str, None],
+ attributes: Union[str, None],
+ lang_code: str,
+) -> str:
+ """
+ Construct a markdown link, adjusting the URL for the given language code if needed.
+ """
+ url = _add_lang_code_to_url(url, lang_code)
+
+ if title:
+ link = f'[{text}]({url} "{title}")'
+ else:
+ link = f"[{text}]({url})"
+
+ if attributes:
+ link += f"{{{attributes}}}"
+
+ return link
+
+
+def replace_markdown_links(
+ text: list[str],
+ links: list[MarkdownLinkInfo],
+ original_links: list[MarkdownLinkInfo],
+ lang_code: str,
+) -> list[str]:
+ """
+ Replace markdown links in the given text with the original links.
+
+ Fail if the number of links does not match the original.
+ """
+
+ if len(links) != len(original_links):
+ raise ValueError(
+ "Number of markdown links does not match the number in the "
+ "original document "
+ f"({len(links)} vs {len(original_links)})"
+ )
+
+ modified_text = text.copy()
+ for i, link_info in enumerate(links):
+ link_text = link_info["text"]
+ link_title = link_info["title"]
+ original_link_info = original_links[i]
+
+ # Replace
+ replacement_link = _construct_markdown_link(
+ url=original_link_info["url"],
+ text=link_text,
+ title=link_title,
+ attributes=original_link_info["attributes"],
+ lang_code=lang_code,
+ )
+ line_no = link_info["line_no"] - 1
+ modified_line = modified_text[line_no]
+ modified_line = modified_line.replace(
+ link_info["full_match"], replacement_link, 1
+ )
+ modified_text[line_no] = modified_line
+
+ return modified_text
+
+
+# HTML links
+# --------------------------------------------------------------------------------------
+
+
+def extract_html_links(lines: list[str]) -> list[HtmlLinkInfo]:
+ """
+ Extract all HTML links from the given lines.
+
+ Return list of HtmlLinkInfo, where each dict contains:
+ - `line_no` - line number (1-based)
+ - `full_tag` - full HTML link tag
+ - `attributes` - list of HTMLLinkAttribute (name, quote, value)
+ - `text` - link text
+ """
+
+ links = []
+ for line_no, line in enumerate(lines, start=1):
+ for html_link in HTML_LINK_RE.finditer(line):
+ link_str = html_link.group(0)
+
+ link_text_match = HTML_LINK_TEXT_RE.match(link_str)
+ assert link_text_match is not None
+ link_text = link_text_match.group(2)
+ assert isinstance(link_text, str)
+
+ link_open_tag_match = HTML_LINK_OPEN_TAG_RE.match(link_str)
+ assert link_open_tag_match is not None
+ link_open_tag = link_open_tag_match.group(1)
+ assert isinstance(link_open_tag, str)
+
+ attributes: list[HTMLLinkAttribute] = []
+ for attr_name, attr_quote, attr_value in re.findall(
+ HTML_ATTR_RE, link_open_tag
+ ):
+ assert isinstance(attr_name, str)
+ assert isinstance(attr_quote, str)
+ assert isinstance(attr_value, str)
+ attributes.append(
+ HTMLLinkAttribute(
+ name=attr_name, quote=attr_quote, value=attr_value
+ )
+ )
+ links.append(
+ HtmlLinkInfo(
+ line_no=line_no,
+ full_tag=link_str,
+ attributes=attributes,
+ text=link_text,
+ )
+ )
+ return links
+
+
+def _construct_html_link(
+ link_text: str,
+ attributes: list[HTMLLinkAttribute],
+ lang_code: str,
+) -> str:
+ """
+ Reconstruct HTML link, adjusting the URL for the given language code if needed.
+ """
+
+ attributes_upd: list[HTMLLinkAttribute] = []
+ for attribute in attributes:
+ if attribute["name"] == "href":
+ original_url = attribute["value"]
+ url = _add_lang_code_to_url(original_url, lang_code)
+ attributes_upd.append(
+ HTMLLinkAttribute(name="href", quote=attribute["quote"], value=url)
+ )
+ else:
+ attributes_upd.append(attribute)
+
+ attrs_str = " ".join(
+ f"{attribute['name']}={attribute['quote']}{attribute['value']}{attribute['quote']}"
+ for attribute in attributes_upd
+ )
+ return f"{link_text}"
+
+
+def replace_html_links(
+ text: list[str],
+ links: list[HtmlLinkInfo],
+ original_links: list[HtmlLinkInfo],
+ lang_code: str,
+) -> list[str]:
+ """
+ Replace HTML links in the given text with the links from the original document.
+
+ Adjust URLs for the given language code.
+ Fail if the number of links does not match the original.
+ """
+
+ if len(links) != len(original_links):
+ raise ValueError(
+ "Number of HTML links does not match the number in the "
+ "original document "
+ f"({len(links)} vs {len(original_links)})"
+ )
+
+ modified_text = text.copy()
+ for link_index, link in enumerate(links):
+ original_link_info = original_links[link_index]
+
+ # Replace in the document text
+ replacement_link = _construct_html_link(
+ link_text=link["text"],
+ attributes=original_link_info["attributes"],
+ lang_code=lang_code,
+ )
+ line_no = link["line_no"] - 1
+ modified_text[line_no] = modified_text[line_no].replace(
+ link["full_tag"], replacement_link, 1
+ )
+
+ return modified_text
+
+
+# Multiline code blocks
+# --------------------------------------------------------------------------------------
+
+
+def get_code_block_lang(line: str) -> str:
+ match = CODE_BLOCK_LANG_RE.match(line)
+ if match:
+ return match.group(1)
+ return ""
+
+
+def extract_multiline_code_blocks(text: list[str]) -> list[MultilineCodeBlockInfo]:
+ blocks: list[MultilineCodeBlockInfo] = []
+
+ in_code_block3 = False
+ in_code_block4 = False
+ current_block_lang = ""
+ current_block_start_line = -1
+ current_block_lines = []
+
+ for line_no, line in enumerate(text, start=1):
+ stripped = line.lstrip()
+
+ # --- Detect opening fence ---
+ if not (in_code_block3 or in_code_block4):
+ if stripped.startswith("```"):
+ current_block_start_line = line_no
+ count = len(stripped) - len(stripped.lstrip("`"))
+ if count == 3:
+ in_code_block3 = True
+ current_block_lang = get_code_block_lang(stripped)
+ current_block_lines = [line]
+ continue
+ elif count >= 4:
+ in_code_block4 = True
+ current_block_lang = get_code_block_lang(stripped)
+ current_block_lines = [line]
+ continue
+
+ # --- Detect closing fence ---
+ elif in_code_block3:
+ if stripped.startswith("```"):
+ count = len(stripped) - len(stripped.lstrip("`"))
+ if count == 3:
+ current_block_lines.append(line)
+ blocks.append(
+ MultilineCodeBlockInfo(
+ lang=current_block_lang,
+ start_line_no=current_block_start_line,
+ content=current_block_lines,
+ )
+ )
+ in_code_block3 = False
+ current_block_lang = ""
+ current_block_start_line = -1
+ current_block_lines = []
+ continue
+ current_block_lines.append(line)
+
+ elif in_code_block4:
+ if stripped.startswith("````"):
+ count = len(stripped) - len(stripped.lstrip("`"))
+ if count >= 4:
+ current_block_lines.append(line)
+ blocks.append(
+ MultilineCodeBlockInfo(
+ lang=current_block_lang,
+ start_line_no=current_block_start_line,
+ content=current_block_lines,
+ )
+ )
+ in_code_block4 = False
+ current_block_lang = ""
+ current_block_start_line = -1
+ current_block_lines = []
+ continue
+ current_block_lines.append(line)
+
+ return blocks
+
+
+def _split_hash_comment(line: str) -> tuple[str, Union[str, None]]:
+ match = HASH_COMMENT_RE.match(line)
+ if match:
+ code = match.group("code").rstrip()
+ comment = match.group("comment")
+ return code, comment
+ return line.rstrip(), None
+
+
+def _split_slashes_comment(line: str) -> tuple[str, Union[str, None]]:
+ match = SLASHES_COMMENT_RE.match(line)
+ if match:
+ code = match.group("code").rstrip()
+ comment = match.group("comment")
+ return code, comment
+ return line, None
+
+
+def replace_multiline_code_block(
+ block_a: MultilineCodeBlockInfo, block_b: MultilineCodeBlockInfo
+) -> list[str]:
+ """
+ Replace multiline code block `a` with block `b` leaving comments intact.
+
+ Syntax of comments depends on the language of the code block.
+ Raises ValueError if the blocks are not compatible (different languages or different number of lines).
+ """
+
+ start_line = block_a["start_line_no"]
+ end_line_no = start_line + len(block_a["content"]) - 1
+
+ if block_a["lang"] != block_b["lang"]:
+ raise ValueError(
+ f"Code block (lines {start_line}-{end_line_no}) "
+ "has different language than the original block "
+ f"('{block_a['lang']}' vs '{block_b['lang']}')"
+ )
+ if len(block_a["content"]) != len(block_b["content"]):
+ raise ValueError(
+ f"Code block (lines {start_line}-{end_line_no}) "
+ "has different number of lines than the original block "
+ f"({len(block_a['content'])} vs {len(block_b['content'])})"
+ )
+
+ block_language = block_a["lang"].lower()
+ if block_language in {"mermaid"}:
+ if block_a != block_b:
+ print(
+ f"Skipping mermaid code block replacement (lines {start_line}-{end_line_no}). "
+ "This should be checked manually."
+ )
+ return block_a["content"].copy() # We don't handle mermaid code blocks for now
+
+ code_block: list[str] = []
+ for line_a, line_b in zip(block_a["content"], block_b["content"]):
+ line_a_comment: Union[str, None] = None
+ line_b_comment: Union[str, None] = None
+
+ # Handle comments based on language
+ if block_language in {
+ "python",
+ "py",
+ "sh",
+ "bash",
+ "dockerfile",
+ "requirements",
+ "gitignore",
+ "toml",
+ "yaml",
+ "yml",
+ "hash-style-comments",
+ }:
+ _line_a_code, line_a_comment = _split_hash_comment(line_a)
+ _line_b_code, line_b_comment = _split_hash_comment(line_b)
+ res_line = line_b
+ if line_b_comment:
+ res_line = res_line.replace(line_b_comment, line_a_comment, 1)
+ code_block.append(res_line)
+ elif block_language in {"console", "json", "slash-style-comments"}:
+ _line_a_code, line_a_comment = _split_slashes_comment(line_a)
+ _line_b_code, line_b_comment = _split_slashes_comment(line_b)
+ res_line = line_b
+ if line_b_comment:
+ res_line = res_line.replace(line_b_comment, line_a_comment, 1)
+ code_block.append(res_line)
+ else:
+ code_block.append(line_b)
+
+ return code_block
+
+
+def replace_multiline_code_blocks_in_text(
+ text: list[str],
+ code_blocks: list[MultilineCodeBlockInfo],
+ original_code_blocks: list[MultilineCodeBlockInfo],
+) -> list[str]:
+ """
+ Update each code block in `text` with the corresponding code block from
+ `original_code_blocks` with comments taken from `code_blocks`.
+
+ Raises ValueError if the number, language, or shape of code blocks do not match.
+ """
+
+ if len(code_blocks) != len(original_code_blocks):
+ raise ValueError(
+ "Number of code blocks does not match the number in the original document "
+ f"({len(code_blocks)} vs {len(original_code_blocks)})"
+ )
+
+ modified_text = text.copy()
+ for block, original_block in zip(code_blocks, original_code_blocks):
+ updated_content = replace_multiline_code_block(block, original_block)
+
+ start_line_index = block["start_line_no"] - 1
+ for i, updated_line in enumerate(updated_content):
+ modified_text[start_line_index + i] = updated_line
+
+ return modified_text
+
+
+# All checks
+# --------------------------------------------------------------------------------------
+
+
+def check_translation(
+ doc_lines: list[str],
+ en_doc_lines: list[str],
+ lang_code: str,
+ auto_fix: bool,
+ path: str,
+) -> list[str]:
+ # Fix code includes
+ en_code_includes = extract_code_includes(en_doc_lines)
+ doc_lines_with_placeholders = replace_code_includes_with_placeholders(doc_lines)
+ fixed_doc_lines = replace_placeholders_with_code_includes(
+ doc_lines_with_placeholders, en_code_includes
+ )
+ if auto_fix and (fixed_doc_lines != doc_lines):
+ print(f"Fixing code includes in: {path}")
+ doc_lines = fixed_doc_lines
+
+ # Fix permalinks
+ en_permalinks = extract_header_permalinks(en_doc_lines)
+ doc_permalinks = extract_header_permalinks(doc_lines)
+ fixed_doc_lines = replace_header_permalinks(
+ doc_lines, doc_permalinks, en_permalinks
+ )
+ if auto_fix and (fixed_doc_lines != doc_lines):
+ print(f"Fixing header permalinks in: {path}")
+ doc_lines = fixed_doc_lines
+
+ # Fix markdown links
+ en_markdown_links = extract_markdown_links(en_doc_lines)
+ doc_markdown_links = extract_markdown_links(doc_lines)
+ fixed_doc_lines = replace_markdown_links(
+ doc_lines, doc_markdown_links, en_markdown_links, lang_code
+ )
+ if auto_fix and (fixed_doc_lines != doc_lines):
+ print(f"Fixing markdown links in: {path}")
+ doc_lines = fixed_doc_lines
+
+ # Fix HTML links
+ en_html_links = extract_html_links(en_doc_lines)
+ doc_html_links = extract_html_links(doc_lines)
+ fixed_doc_lines = replace_html_links(
+ doc_lines, doc_html_links, en_html_links, lang_code
+ )
+ if auto_fix and (fixed_doc_lines != doc_lines):
+ print(f"Fixing HTML links in: {path}")
+ doc_lines = fixed_doc_lines
+
+ # Fix multiline code blocks
+ en_code_blocks = extract_multiline_code_blocks(en_doc_lines)
+ doc_code_blocks = extract_multiline_code_blocks(doc_lines)
+ fixed_doc_lines = replace_multiline_code_blocks_in_text(
+ doc_lines, doc_code_blocks, en_code_blocks
+ )
+ if auto_fix and (fixed_doc_lines != doc_lines):
+ print(f"Fixing multiline code blocks in: {path}")
+ doc_lines = fixed_doc_lines
+
+ return doc_lines
diff --git a/scripts/docs.py b/scripts/docs.py
index 56ffb9d364..20bf1c1688 100644
--- a/scripts/docs.py
+++ b/scripts/docs.py
@@ -4,21 +4,32 @@ import os
import re
import shutil
import subprocess
-from functools import lru_cache
+from html.parser import HTMLParser
from http.server import HTTPServer, SimpleHTTPRequestHandler
-from importlib import metadata
from multiprocessing import Pool
from pathlib import Path
-from typing import Any, Dict, List, Optional, Union
+from typing import Any, Optional, Union
import mkdocs.utils
import typer
import yaml
from jinja2 import Template
from ruff.__main__ import find_ruff_bin
+from slugify import slugify as py_slugify
logging.basicConfig(level=logging.INFO)
+SUPPORTED_LANGS = {
+ "en",
+ "de",
+ "es",
+ "ko",
+ "pt",
+ "ru",
+ "uk",
+}
+
+
app = typer.Typer()
mkdocs_name = "mkdocs.yml"
@@ -27,8 +38,8 @@ missing_translation_snippet = """
{!../../docs/missing-translation.md!}
"""
-non_translated_sections = [
- "reference/",
+non_translated_sections = (
+ f"reference{os.sep}",
"release-notes.md",
"fastapi-people.md",
"external-links.md",
@@ -36,7 +47,7 @@ non_translated_sections = [
"management-tasks.md",
"management.md",
"contributing.md",
-]
+)
docs_path = Path("docs")
en_docs_path = Path("docs/en")
@@ -44,20 +55,46 @@ en_config_path: Path = en_docs_path / mkdocs_name
site_path = Path("site").absolute()
build_site_path = Path("site_build").absolute()
+header_pattern = re.compile(r"^(#{1,6}) (.+?)(?:\s*\{\s*(#.*)\s*\})?\s*$")
header_with_permalink_pattern = re.compile(r"^(#{1,6}) (.+?)(\s*\{\s*#.*\s*\})\s*$")
+code_block3_pattern = re.compile(r"^\s*```")
+code_block4_pattern = re.compile(r"^\s*````")
-@lru_cache
-def is_mkdocs_insiders() -> bool:
- version = metadata.version("mkdocs-material")
- return "insiders" in version
+class VisibleTextExtractor(HTMLParser):
+ """Extract visible text from a string with HTML tags."""
+
+ def __init__(self):
+ super().__init__()
+ self.text_parts = []
+
+ def handle_data(self, data):
+ self.text_parts.append(data)
+
+ def extract_visible_text(self, html: str) -> str:
+ self.reset()
+ self.text_parts = []
+ self.feed(html)
+ return "".join(self.text_parts).strip()
-def get_en_config() -> Dict[str, Any]:
+def slugify(text: str) -> str:
+ return py_slugify(
+ text,
+ replacements=[
+ ("`", ""), # `dict`s -> dicts
+ ("'s", "s"), # it's -> its
+ ("'t", "t"), # don't -> dont
+ ("**", ""), # **FastAPI**s -> FastAPIs
+ ],
+ )
+
+
+def get_en_config() -> dict[str, Any]:
return mkdocs.utils.yaml_load(en_config_path.read_text(encoding="utf-8"))
-def get_lang_paths() -> List[Path]:
+def get_lang_paths() -> list[Path]:
return sorted(docs_path.iterdir())
@@ -77,9 +114,7 @@ def complete_existing_lang(incomplete: str):
@app.callback()
def callback() -> None:
- if is_mkdocs_insiders():
- os.environ["INSIDERS_FILE"] = "../en/mkdocs.insiders.yml"
- # For MacOS with insiders and Cairo
+ # For MacOS with Cairo
os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = "/opt/homebrew/lib"
@@ -95,14 +130,9 @@ def new_lang(lang: str = typer.Argument(..., callback=lang_callback)):
new_path.mkdir()
new_config_path: Path = Path(new_path) / mkdocs_name
new_config_path.write_text("INHERIT: ../en/mkdocs.yml\n", encoding="utf-8")
- new_config_docs_path: Path = new_path / "docs"
- new_config_docs_path.mkdir()
- en_index_path: Path = en_docs_path / "docs" / "index.md"
- new_index_path: Path = new_config_docs_path / "index.md"
- en_index_content = en_index_path.read_text(encoding="utf-8")
- new_index_content = f"{missing_translation_snippet}\n\n{en_index_content}"
- new_index_path.write_text(new_index_content, encoding="utf-8")
- typer.secho(f"Successfully initialized: {new_path}", color=typer.colors.GREEN)
+ new_llm_prompt_path: Path = new_path / "llm-prompt.md"
+ new_llm_prompt_path.write_text("", encoding="utf-8")
+ print(f"Successfully initialized: {new_path}")
update_languages()
@@ -115,10 +145,6 @@ def build_lang(
"""
Build the docs for a language.
"""
- insiders_env_file = os.environ.get("INSIDERS_FILE")
- print(f"Insiders file {insiders_env_file}")
- if is_mkdocs_insiders():
- print("Using insiders")
lang_path: Path = Path("docs") / lang
if not lang_path.is_dir():
typer.echo(f"The language translation doesn't seem to exist yet: {lang}")
@@ -145,14 +171,20 @@ def build_lang(
index_sponsors_template = """
-{% if sponsors %}
+### Keystone Sponsor
+
+{% for sponsor in sponsors.keystone -%}
+
+{% endfor %}
+### Gold and Silver Sponsors
+
{% for sponsor in sponsors.gold -%}
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
{% endfor %}
-{% endif %}
+
"""
@@ -203,27 +235,15 @@ def generate_readme() -> None:
"""
Generate README.md content from main index.md
"""
- typer.echo("Generating README")
readme_path = Path("README.md")
+ old_content = readme_path.read_text("utf-8")
new_content = generate_readme_content()
- readme_path.write_text(new_content, encoding="utf-8")
-
-
-@app.command()
-def verify_readme() -> None:
- """
- Verify README.md content from main index.md
- """
- typer.echo("Verifying README")
- readme_path = Path("README.md")
- generated_content = generate_readme_content()
- readme_content = readme_path.read_text("utf-8")
- if generated_content != readme_content:
- typer.secho(
- "README.md outdated from the latest index.md", color=typer.colors.RED
- )
- raise typer.Abort()
- typer.echo("Valid README ✅")
+ if new_content != old_content:
+ print("README.md outdated from the latest index.md")
+ print("Updating README.md")
+ readme_path.write_text(new_content, encoding="utf-8")
+ raise typer.Exit(1)
+ print("README.md is up to date ✅")
@app.command()
@@ -234,7 +254,11 @@ def build_all() -> None:
"""
update_languages()
shutil.rmtree(site_path, ignore_errors=True)
- langs = [lang.name for lang in get_lang_paths() if lang.is_dir()]
+ langs = [
+ lang.name
+ for lang in get_lang_paths()
+ if (lang.is_dir() and lang.name in SUPPORTED_LANGS)
+ ]
cpu_count = os.cpu_count() or 1
process_pool_size = cpu_count * 4
typer.echo(f"Using process pool size: {process_pool_size}")
@@ -247,7 +271,17 @@ def update_languages() -> None:
"""
Update the mkdocs.yml file Languages section including all the available languages.
"""
- update_config()
+ old_config = get_en_config()
+ updated_config = get_updated_config_content()
+ if old_config != updated_config:
+ print("docs/en/mkdocs.yml outdated")
+ print("Updating docs/en/mkdocs.yml")
+ en_config_path.write_text(
+ yaml.dump(updated_config, sort_keys=False, width=200, allow_unicode=True),
+ encoding="utf-8",
+ )
+ raise typer.Exit(1)
+ print("docs/en/mkdocs.yml is up to date ✅")
@app.command()
@@ -301,19 +335,22 @@ def live(
)
-def get_updated_config_content() -> Dict[str, Any]:
+def get_updated_config_content() -> dict[str, Any]:
config = get_en_config()
languages = [{"en": "/"}]
- new_alternate: List[Dict[str, str]] = []
+ new_alternate: list[dict[str, str]] = []
# Language names sourced from https://quickref.me/iso-639-1
# Contributors may wish to update or change these, e.g. to fix capitalization.
language_names_path = Path(__file__).parent / "../docs/language_names.yml"
- local_language_names: Dict[str, str] = mkdocs.utils.yaml_load(
+ local_language_names: dict[str, str] = mkdocs.utils.yaml_load(
language_names_path.read_text(encoding="utf-8")
)
for lang_path in get_lang_paths():
if lang_path.name in {"en", "em"} or not lang_path.is_dir():
continue
+ if lang_path.name not in SUPPORTED_LANGS:
+ # Skip languages that are not yet ready
+ continue
code = lang_path.name
languages.append({code: f"/{code}/"})
for lang_dict in languages:
@@ -327,44 +364,16 @@ def get_updated_config_content() -> Dict[str, Any]:
raise typer.Abort()
use_name = f"{code} - {local_language_names[code]}"
new_alternate.append({"link": url, "name": use_name})
- new_alternate.append({"link": "/em/", "name": "😉"})
config["extra"]["alternate"] = new_alternate
return config
-def update_config() -> None:
- config = get_updated_config_content()
- en_config_path.write_text(
- yaml.dump(config, sort_keys=False, width=200, allow_unicode=True),
- encoding="utf-8",
- )
-
-
@app.command()
-def verify_config() -> None:
+def ensure_non_translated() -> None:
"""
- Verify main mkdocs.yml content to make sure it uses the latest language names.
+ Ensure there are no files in the non translatable pages.
"""
- typer.echo("Verifying mkdocs.yml")
- config = get_en_config()
- updated_config = get_updated_config_content()
- if config != updated_config:
- typer.secho(
- "docs/en/mkdocs.yml outdated from docs/language_names.yml, "
- "update language_names.yml and run "
- "python ./scripts/docs.py update-languages",
- color=typer.colors.RED,
- )
- raise typer.Abort()
- typer.echo("Valid mkdocs.yml ✅")
-
-
-@app.command()
-def verify_non_translated() -> None:
- """
- Verify there are no files in the non translatable pages.
- """
- print("Verifying non translated pages")
+ print("Ensuring no non translated pages")
lang_paths = get_lang_paths()
error_paths = []
for lang in lang_paths:
@@ -375,25 +384,22 @@ def verify_non_translated() -> None:
if non_translatable_path.exists():
error_paths.append(non_translatable_path)
if error_paths:
- print("Non-translated pages found, remove them:")
+ print("Non-translated pages found, removing them:")
for error_path in error_paths:
print(error_path)
- raise typer.Abort()
+ if error_path.is_file():
+ error_path.unlink()
+ else:
+ shutil.rmtree(error_path)
+ raise typer.Exit(1)
print("No non-translated pages found ✅")
-@app.command()
-def verify_docs():
- verify_readme()
- verify_config()
- verify_non_translated()
-
-
@app.command()
def langs_json():
langs = []
for lang_path in get_lang_paths():
- if lang_path.is_dir():
+ if lang_path.is_dir() and lang_path.name in SUPPORTED_LANGS:
langs.append(lang_path.name)
print(json.dumps(langs))
@@ -434,5 +440,83 @@ def generate_docs_src_versions_for_file(file_path: Path) -> None:
version_file.write_text(content_format, encoding="utf-8")
+@app.command()
+def add_permalinks_page(path: Path, update_existing: bool = False):
+ """
+ Add or update header permalinks in specific page of En docs.
+ """
+
+ if not path.is_relative_to(en_docs_path / "docs"):
+ raise RuntimeError(f"Path must be inside {en_docs_path}")
+ rel_path = path.relative_to(en_docs_path / "docs")
+
+ # Skip excluded sections
+ if str(rel_path).startswith(non_translated_sections):
+ return
+
+ visible_text_extractor = VisibleTextExtractor()
+ updated_lines = []
+ in_code_block3 = False
+ in_code_block4 = False
+ permalinks = set()
+
+ with path.open("r", encoding="utf-8") as f:
+ lines = f.readlines()
+
+ for line in lines:
+ # Handle codeblocks start and end
+ if not (in_code_block3 or in_code_block4):
+ if code_block4_pattern.match(line):
+ in_code_block4 = True
+ elif code_block3_pattern.match(line):
+ in_code_block3 = True
+ else:
+ if in_code_block4 and code_block4_pattern.match(line):
+ in_code_block4 = False
+ elif in_code_block3 and code_block3_pattern.match(line):
+ in_code_block3 = False
+
+ # Process Headers only outside codeblocks
+ if not (in_code_block3 or in_code_block4):
+ match = header_pattern.match(line)
+ if match:
+ hashes, title, _permalink = match.groups()
+ if (not _permalink) or update_existing:
+ slug = slugify(visible_text_extractor.extract_visible_text(title))
+ if slug in permalinks:
+ # If the slug is already used, append a number to make it unique
+ count = 1
+ original_slug = slug
+ while slug in permalinks:
+ slug = f"{original_slug}_{count}"
+ count += 1
+ permalinks.add(slug)
+
+ line = f"{hashes} {title} {{ #{slug} }}\n"
+
+ updated_lines.append(line)
+
+ with path.open("w", encoding="utf-8") as f:
+ f.writelines(updated_lines)
+
+
+@app.command()
+def add_permalinks_pages(pages: list[Path], update_existing: bool = False) -> None:
+ """
+ Add or update header permalinks in specific pages of En docs.
+ """
+ for md_file in pages:
+ add_permalinks_page(md_file, update_existing=update_existing)
+
+
+@app.command()
+def add_permalinks(update_existing: bool = False) -> None:
+ """
+ Add or update header permalinks in all pages of En docs.
+ """
+ for md_file in en_docs_path.rglob("*.md"):
+ add_permalinks_page(md_file, update_existing=update_existing)
+
+
if __name__ == "__main__":
app()
diff --git a/scripts/general-llm-prompt.md b/scripts/general-llm-prompt.md
new file mode 100644
index 0000000000..d45ab8eb07
--- /dev/null
+++ b/scripts/general-llm-prompt.md
@@ -0,0 +1,528 @@
+### Your task
+
+Translate an English original content to a target language.
+
+The original content is written in Markdown, write the translation in Markdown as well.
+
+The original content will be surrounded by triple percentage signs (%%%). Do not include the triple percentage signs in the translation.
+
+### Technical terms in English
+
+For technical terms in English that don't have a common translation term, use the original term in English.
+
+### Content of code snippets
+
+Do not translate the content of code snippets, keep the original in English. For example, `list`, `dict`, keep them as is.
+
+### Content of code blocks
+
+Do not translate the content of code blocks, except for comments in the language which the code block uses.
+
+Examples:
+
+Source (English) - The code block is a bash code example with one comment:
+
+```bash
+# Print greeting
+echo "Hello, World!"
+```
+
+Result (German):
+
+```bash
+# Gruß ausgeben
+echo "Hello, World!"
+```
+
+Source (English) - The code block is a console example containing HTML tags. No comments, so nothing to change here:
+
+```console
+$ fastapi run main.py
+ FastAPI Starting server
+ Searching for package file structure
+```
+
+Result (German):
+
+```console
+$ fastapi run main.py
+ FastAPI Starting server
+ Searching for package file structure
+```
+
+Source (English) - The code block is a console example containing 5 comments:
+
+
+```console
+// Go to the home directory
+$ cd
+// Create a directory for all your code projects
+$ mkdir code
+// Enter into that code directory
+$ cd code
+// Create a directory for this project
+$ mkdir awesome-project
+// Enter into that project directory
+$ cd awesome-project
+```
+
+Result (German):
+
+```console
+// Gehe zum Home-Verzeichnis
+$ cd
+// Erstelle ein Verzeichnis für alle Ihre Code-Projekte
+$ mkdir code
+// Gehe in dieses Code-Verzeichnis
+$ cd code
+// Erstelle ein Verzeichnis für dieses Projekt
+$ mkdir awesome-project
+// Gehe in dieses Projektverzeichnis
+$ cd awesome-project
+```
+
+If there is an existing translation and its Mermaid diagram is in sync with the Mermaid diagram in the English source, except a few translated words, then use the Mermaid diagram of the existing translation. The human editor of the translation translated these words in the Mermaid diagram. Keep these translations, do not revert them back to the English source.
+
+Example:
+
+Source (English):
+
+```mermaid
+flowchart LR
+ subgraph global[global env]
+ harry-1[harry v1]
+ end
+ subgraph stone-project[philosophers-stone project]
+ stone(philosophers-stone) -->|requires| harry-1
+ end
+```
+
+Existing translation (German) - has three translations:
+
+```mermaid
+flowchart LR
+ subgraph global[globale Umgebung]
+ harry-1[harry v1]
+ end
+ subgraph stone-project[philosophers-stone-Projekt]
+ stone(philosophers-stone) -->|benötigt| harry-1
+ end
+```
+
+Result (German) - you change nothing:
+
+```mermaid
+flowchart LR
+ subgraph global[globale Umgebung]
+ harry-1[harry v1]
+ end
+ subgraph stone-project[philosophers-stone-Projekt]
+ stone(philosophers-stone) -->|benötigt| harry-1
+ end
+```
+
+### Special blocks
+
+There are special blocks of notes, tips and others that look like:
+
+/// note
+Here goes a note
+///
+
+To translate it, keep the same line and add the translation after a vertical bar.
+
+For example, if you were translating to Spanish, you would write:
+
+/// note | Nota
+
+Some examples in Spanish:
+
+Source (English):
+
+/// tip
+
+Result (Spanish):
+
+/// tip | Consejo
+
+Source (English):
+
+/// details | Preview
+
+Result (Spanish):
+
+/// details | Vista previa
+
+### Tab blocks
+
+There are special blocks surrounded by four slashes (////). They mark text, which will be rendered as part of a tab in the final document. The scheme is:
+
+//// tab | {tab title}
+{tab content, may span many lines}
+////
+
+Keep everything before the vertical bar (|) as is, including the vertical bar. Translate the tab title. Translate the tab content, applying the rules you know. Keep the four block closing slashes as is.
+
+Examples:
+
+Source (English):
+
+//// tab | Python 3.8+ non-Annotated
+Hello
+////
+
+Result (German):
+
+//// tab | Python 3.8+ nicht annotiert
+Hallo
+////
+
+Source (English) - Here there is nothing to translate in the tab title:
+
+//// tab | Linux, macOS, Windows Bash
+Hello again
+////
+
+Result (German):
+
+//// tab | Linux, macOS, Windows Bash
+Hallo wieder
+////
+
+### Headings
+
+Every Markdown heading in the English text (all levels) ends with a part inside curly brackets. This part denotes the hash of this heading, which is used in links to this heading. In translations, translate the heading, but do not translate this hash part, so that links do not break.
+
+Examples of how to translate a heading:
+
+Source (English):
+
+```
+## Alternative API docs { #alternative-api-docs }
+```
+
+Result (Spanish):
+
+```
+## Documentación de la API alternativa { #alternative-api-docs }
+```
+
+Source (English):
+
+```
+### Example { #example }
+```
+
+Result (German):
+
+```
+### Beispiel { #example }
+```
+
+### Links
+
+Use the following rules for links (apply both to Markdown-style links ([text](url)) and to HTML-style text tags):
+
+- For relative URLs, only translate the link text. Do not translate the URL or its parts.
+
+Example:
+
+Source (English):
+
+```
+[One of the fastest Python frameworks available](#performance)
+```
+
+Result (German):
+
+```
+[Eines der schnellsten verfügbaren Python-Frameworks](#performance)
+```
+
+- For absolute URLs which DO NOT start EXACTLY with https://fastapi.tiangolo.com, only translate the link text and leave the URL unchanged.
+
+Example:
+
+Source (English):
+
+```
+SQLModel docs
+```
+
+Result (German):
+
+```
+SQLModel-Dokumentation
+```
+
+- For absolute URLs which DO start EXACTLY with https://fastapi.tiangolo.com, only translate the link text and change the URL by adding the language code (https://fastapi.tiangolo.com/{language_code}[rest part of the url]).
+
+Example:
+
+Source (English):
+
+```
+Documentation
+```
+
+Result (Spanish):
+
+```
+Documentación
+```
+
+- Do not add language codes for URLs that point to static assets (e.g., images, CSS, JavaScript).
+
+Example:
+
+Source (English):
+
+```
+Something
+```
+
+Result (Spanish):
+
+```
+Algo
+```
+
+- For internal links, only translate link text.
+
+Example:
+
+Source (English):
+
+```
+[Create Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}
+```
+
+Result (German):
+
+```
+[Pull Requests erzeugen](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}
+```
+
+- Do not translate anchor fragments in links (the part after `#`), as they must remain the same to work correctly.
+
+- If an existing translation has a link with an anchor fragment different to the anchor fragment in the English source, then this is an error. Fix this by using the anchor fragment of the English source.
+
+Example:
+
+Source (English):
+
+```
+[Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}
+```
+
+Existing wrong translation (German) - notice the wrongly translated anchor fragment:
+
+```
+[Body - Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#einzelne-werte-im-body){.internal-link target=_blank}.
+```
+
+Result (German) - you fix the anchor fragment:
+
+```
+[Body - Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
+```
+
+- Do not add anchor fragments at will, even if this makes sense. If the English source has no anchor, don't add one.
+
+Example:
+
+Source (English):
+
+```
+Create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}
+```
+
+Wrong translation in German - Anchor added to the URL.
+
+```
+Erstelle eine [virtuelle Umgebung](../virtual-environments.md#create-a-virtual-environment){.internal-link target=_blank}
+```
+
+Good translation (German) - URL stays like in the English source.
+
+```
+Erstelle eine [Virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank}
+```
+
+### HTML abbr elements
+
+Translate HTML abbr elements (`text`) as follows:
+
+- If the text surrounded by the abbr element is an abbreviation (the text may be surrounded by further HTML or Markdown markup or quotes, for example text or `text` or "text", ignore that further markup when deciding if the text is an abbreviation), and if the description (the text inside the title attribute) contains the full phrase for this abbreviation, then append a dash (-) to the full phrase, followed by the translation of the full phrase.
+
+Conversion scheme:
+
+Source (English):
+
+```
+{abbreviation}
+```
+
+Result:
+
+```
+{abbreviation}
+```
+
+Examples:
+
+Source (English):
+
+```
+IoT
+CPU
+TL;DR:
+```
+
+Result (German):
+
+```
+IoT
+CPU
+TL;DR:
+```
+
+- If the language to which you translate mostly uses the letters of the ASCII char set (for example Spanish, French, German, but not Russian, Chinese) and if the translation of the full phrase is identical to, or starts with the same letters as the original full phrase, then only give the translation of the full phrase.
+
+Conversion scheme:
+
+Source (English):
+
+```
+{abbreviation}
+```
+
+Result:
+
+```
+{abbreviation}
+```
+
+Examples:
+
+Source (English):
+
+```
+JWT
+Enum
+ASGI
+```
+
+Result (German):
+
+```
+JWT
+Enum
+ASGI
+```
+
+- If the description is not a full phrase for an abbreviation which the abbr element surrounds, but some other information, then just translate the description.
+
+Conversion scheme:
+
+Source (English):
+
+```
+{text}
+```
+
+Result:
+
+```
+{translation of text}
+```
+
+Examples:
+
+ Source (English):
+
+```
+path
+linter
+parsing
+0.95.0
+at the time of writing this
+```
+
+Result (German):
+
+```
+Pfad
+Linter
+Parsen
+0.95.0
+zum Zeitpunkt als das hier geschrieben wurde
+```
+
+- If the text surrounded by the abbr element is an abbreviation and the description contains both the full phrase for that abbreviation, and other information, separated by a colon (`:`), then append a dash (`-`) and the translation of the full phrase to the original full phrase and translate the other information.
+
+Conversion scheme:
+
+Source (English):
+
+```
+{abbreviation}
+```
+
+Result:
+
+```
+{abbreviation}
+```
+
+Examples:
+
+Source (English):
+
+```
+I/O
+CDN
+IDE
+```
+
+Result (German):
+
+```
+I/O
+CDN
+IDE
+```
+
+- You can leave the original full phrase away, if the translated full phrase is identical or starts with the same letters as the original full phrase.
+
+Conversion scheme:
+
+Source (English):
+
+```
+{abbreviation}
+```
+
+Result:
+
+```
+{abbreviation}
+```
+
+Example:
+
+Source (English):
+
+```
+ORM
+```
+
+Result (German):
+
+```
+ORM
+```
+
+- If there is an existing translation, and it has ADDITIONAL abbr elements in a sentence, and these additional abbr elements do not exist in the related sentence in the English text, then KEEP those additional abbr elements in the translation. Do not remove them. Except when you remove the whole sentence from the translation, because the whole sentence was removed from the English text, then also remove the abbr element. The reasoning for this rule is, that such additional abbr elements are manually added by the human editor of the translation, in order to translate or explain an English word to the human readers of the translation. These additional abbr elements would not make sense in the English text, but they do make sense in the translation. So keep them in the translation, even though they are not part of the English text. This rule only applies to abbr elements.
+
+- Apply above rules also when there is an existing translation! Make sure that all title attributes in abbr elements get properly translated or updated, using the schemes given above. However, leave the ADDITIONAL abbr's described above alone. Do not change their formatting or content.
diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py
index b9e4ff59ed..4b781270a2 100644
--- a/scripts/mkdocs_hooks.py
+++ b/scripts/mkdocs_hooks.py
@@ -1,6 +1,6 @@
from functools import lru_cache
from pathlib import Path
-from typing import Any, List, Union
+from typing import Any, Union
import material
from mkdocs.config.defaults import MkDocsConfig
@@ -27,7 +27,7 @@ def get_missing_translation_content(docs_dir: str) -> str:
@lru_cache
-def get_mkdocs_material_langs() -> List[str]:
+def get_mkdocs_material_langs() -> list[str]:
material_path = Path(material.__file__).parent
material_langs_path = material_path / "templates" / "partials" / "languages"
langs = [file.stem for file in material_langs_path.glob("*.html")]
@@ -65,7 +65,7 @@ def resolve_file(*, item: str, files: Files, config: MkDocsConfig) -> None:
)
-def resolve_files(*, items: List[Any], files: Files, config: MkDocsConfig) -> None:
+def resolve_files(*, items: list[Any], files: Files, config: MkDocsConfig) -> None:
for item in items:
if isinstance(item, str):
resolve_file(item=item, files=files, config=config)
@@ -94,9 +94,9 @@ def on_files(files: Files, *, config: MkDocsConfig) -> Files:
def generate_renamed_section_items(
- items: List[Union[Page, Section, Link]], *, config: MkDocsConfig
-) -> List[Union[Page, Section, Link]]:
- new_items: List[Union[Page, Section, Link]] = []
+ items: list[Union[Page, Section, Link]], *, config: MkDocsConfig
+) -> list[Union[Page, Section, Link]]:
+ new_items: list[Union[Page, Section, Link]] = []
for item in items:
if isinstance(item, Section):
new_title = item.title
@@ -132,7 +132,7 @@ def on_pre_page(page: Page, *, config: MkDocsConfig, files: Files) -> Page:
def on_page_markdown(
markdown: str, *, page: Page, config: MkDocsConfig, files: Files
) -> str:
- # Set matadata["social"]["cards_layout_options"]["title"] to clean title (without
+ # Set metadata["social"]["cards_layout_options"]["title"] to clean title (without
# permalink)
title = page.title
clean_title = title.split("{ #")[0]
diff --git a/scripts/notify_translations.py b/scripts/notify_translations.py
index c300624db2..74cdf0dffe 100644
--- a/scripts/notify_translations.py
+++ b/scripts/notify_translations.py
@@ -3,7 +3,7 @@ import random
import sys
import time
from pathlib import Path
-from typing import Any, Dict, List, Union, cast
+from typing import Any, Union, cast
import httpx
from github import Github
@@ -120,7 +120,7 @@ class CommentsEdge(BaseModel):
class Comments(BaseModel):
- edges: List[CommentsEdge]
+ edges: list[CommentsEdge]
class CommentsDiscussion(BaseModel):
@@ -149,7 +149,7 @@ class AllDiscussionsLabelsEdge(BaseModel):
class AllDiscussionsDiscussionLabels(BaseModel):
- edges: List[AllDiscussionsLabelsEdge]
+ edges: list[AllDiscussionsLabelsEdge]
class AllDiscussionsDiscussionNode(BaseModel):
@@ -160,7 +160,7 @@ class AllDiscussionsDiscussionNode(BaseModel):
class AllDiscussionsDiscussions(BaseModel):
- nodes: List[AllDiscussionsDiscussionNode]
+ nodes: list[AllDiscussionsDiscussionNode]
class AllDiscussionsRepository(BaseModel):
@@ -205,7 +205,7 @@ def get_graphql_response(
discussion_id: Union[str, None] = None,
comment_id: Union[str, None] = None,
body: Union[str, None] = None,
-) -> Dict[str, Any]:
+) -> dict[str, Any]:
headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"}
variables = {
"after": after,
@@ -233,12 +233,12 @@ def get_graphql_response(
logging.error(data["errors"])
logging.error(response.text)
raise RuntimeError(response.text)
- return cast(Dict[str, Any], data)
+ return cast(dict[str, Any], data)
def get_graphql_translation_discussions(
*, settings: Settings
-) -> List[AllDiscussionsDiscussionNode]:
+) -> list[AllDiscussionsDiscussionNode]:
data = get_graphql_response(
settings=settings,
query=all_discussions_query,
@@ -250,7 +250,7 @@ def get_graphql_translation_discussions(
def get_graphql_translation_discussion_comments_edges(
*, settings: Settings, discussion_number: int, after: Union[str, None] = None
-) -> List[CommentsEdge]:
+) -> list[CommentsEdge]:
data = get_graphql_response(
settings=settings,
query=translation_discussion_query,
@@ -264,7 +264,7 @@ def get_graphql_translation_discussion_comments_edges(
def get_graphql_translation_discussion_comments(
*, settings: Settings, discussion_number: int
) -> list[Comment]:
- comment_nodes: List[Comment] = []
+ comment_nodes: list[Comment] = []
discussion_edges = get_graphql_translation_discussion_comments_edges(
settings=settings, discussion_number=discussion_number
)
@@ -316,7 +316,7 @@ def main() -> None:
raise RuntimeError(
f"No github event file available at: {settings.github_event_path}"
)
- contents = settings.github_event_path.read_text()
+ contents = settings.github_event_path.read_text("utf-8")
github_event = PartialGitHubEvent.model_validate_json(contents)
logging.info(f"Using GitHub event: {github_event}")
number = (
@@ -348,7 +348,7 @@ def main() -> None:
# Generate translation map, lang ID to discussion
discussions = get_graphql_translation_discussions(settings=settings)
- lang_to_discussion_map: Dict[str, AllDiscussionsDiscussionNode] = {}
+ lang_to_discussion_map: dict[str, AllDiscussionsDiscussionNode] = {}
for discussion in discussions:
for edge in discussion.labels.edges:
label = edge.node.name
diff --git a/scripts/people.py b/scripts/people.py
index 7418b45956..207ab46493 100644
--- a/scripts/people.py
+++ b/scripts/people.py
@@ -3,9 +3,10 @@ import secrets
import subprocess
import time
from collections import Counter
+from collections.abc import Container
from datetime import datetime, timedelta, timezone
from pathlib import Path
-from typing import Any, Container, Union
+from typing import Any, Union
import httpx
import yaml
@@ -378,9 +379,10 @@ def main() -> None:
return
logging.info("Setting up GitHub Actions git user")
- subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
+ subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True)
subprocess.run(
- ["git", "config", "user.email", "github-actions@github.com"], check=True
+ ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"],
+ check=True,
)
branch_name = f"fastapi-people-experts-{secrets.token_hex(4)}"
logging.info(f"Creating a new branch {branch_name}")
diff --git a/scripts/sponsors.py b/scripts/sponsors.py
index 45e02bd621..fdcabc737b 100644
--- a/scripts/sponsors.py
+++ b/scripts/sponsors.py
@@ -190,9 +190,10 @@ def main() -> None:
return
logging.info("Setting up GitHub Actions git user")
- subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
+ subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True)
subprocess.run(
- ["git", "config", "user.email", "github-actions@github.com"], check=True
+ ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"],
+ check=True,
)
branch_name = f"fastapi-people-sponsors-{secrets.token_hex(4)}"
logging.info(f"Creating a new branch {branch_name}")
diff --git a/scripts/test.sh b/scripts/test.sh
index 7d17add8fa..0bffcd1cdd 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -4,4 +4,4 @@ set -e
set -x
export PYTHONPATH=./docs_src
-coverage run -m pytest tests ${@}
+coverage run -m pytest tests scripts/tests/ ${@}
diff --git a/scripts/tests/test_translation_fixer/conftest.py b/scripts/tests/test_translation_fixer/conftest.py
new file mode 100644
index 0000000000..006f519f46
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/conftest.py
@@ -0,0 +1,42 @@
+import shutil
+import sys
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+skip_on_windows = pytest.mark.skipif(
+ sys.platform == "win32", reason="Skipping on Windows"
+)
+
+
+def pytest_collection_modifyitems(items: list[pytest.Item]) -> None:
+ for item in items:
+ item.add_marker(skip_on_windows)
+
+
+@pytest.fixture(name="runner")
+def get_runner():
+ runner = CliRunner()
+ with runner.isolated_filesystem():
+ yield runner
+
+
+@pytest.fixture(name="root_dir")
+def prepare_paths(runner):
+ docs_dir = Path("docs")
+ en_docs_dir = docs_dir / "en" / "docs"
+ lang_docs_dir = docs_dir / "lang" / "docs"
+ en_docs_dir.mkdir(parents=True, exist_ok=True)
+ lang_docs_dir.mkdir(parents=True, exist_ok=True)
+ yield Path.cwd()
+
+
+@pytest.fixture
+def copy_test_files(root_dir: Path, request: pytest.FixtureRequest):
+ en_file_path = Path(request.param[0])
+ translation_file_path = Path(request.param[1])
+ shutil.copy(str(en_file_path), str(root_dir / "docs" / "en" / "docs" / "doc.md"))
+ shutil.copy(
+ str(translation_file_path), str(root_dir / "docs" / "lang" / "docs" / "doc.md")
+ )
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/en_doc.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/en_doc.md
new file mode 100644
index 0000000000..cad20e2c70
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/en_doc.md
@@ -0,0 +1,44 @@
+# Code blocks { #code-blocks }
+
+Some text
+
+```python
+# This is a sample Python code block
+def hello_world():
+ # Comment with indentation
+ print("Hello, world!") # Print greeting
+```
+
+Some more text
+
+```toml
+# This is a sample TOML code block
+title = "TOML Example" # Title of the document
+```
+
+And more text
+
+```console
+// Use the command "live" and pass the language code as a CLI argument
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+And even more text
+
+```json
+{
+ // This is a sample JSON code block
+ "greeting": "Hello, world!" // Greeting
+}
+```
+
+Mermaid diagram
+
+```mermaid
+flowchart LR
+ stone(philosophers-stone) -->|requires| harry-1[harry v1]
+```
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_gt.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_gt.md
new file mode 100644
index 0000000000..f46070156b
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_gt.md
@@ -0,0 +1,45 @@
+# Code blocks { #code-blocks }
+
+Some text
+
+```python
+# This is a sample Python code block
+def hello_world():
+ # Comment with indentation
+ print("Hello, world!") # Print greeting
+```
+
+Some more text
+
+```toml
+# Extra line
+# This is a sample TOML code block
+title = "TOML Example" # Title of the document
+```
+
+And more text
+
+```console
+// Use the command "live" and pass the language code as a CLI argument
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+And even more text
+
+```json
+{
+ // This is a sample JSON code block
+ "greeting": "Hello, world!" // Greeting
+}
+```
+
+Диаграма Mermaid
+
+```mermaid
+flowchart LR
+ stone(philosophers-stone) -->|requires| harry-1[harry v1]
+```
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_lt.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_lt.md
new file mode 100644
index 0000000000..e08baa70b1
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_lines_number_lt.md
@@ -0,0 +1,45 @@
+# Code blocks { #code-blocks }
+
+Some text
+
+```python
+# This is a sample Python code block
+def hello_world():
+ # Comment with indentation
+ print("Hello, world!") # Print greeting
+```
+
+Some more text
+
+The following block is missing first line:
+
+```toml
+title = "TOML Example" # Title of the document
+```
+
+And more text
+
+```console
+// Use the command "live" and pass the language code as a CLI argument
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+And even more text
+
+```json
+{
+ // This is a sample JSON code block
+ "greeting": "Hello, world!" // Greeting
+}
+```
+
+Диаграма Mermaid
+
+```mermaid
+flowchart LR
+ stone(philosophers-stone) -->|requires| harry-1[harry v1]
+```
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_not_translated.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_not_translated.md
new file mode 100644
index 0000000000..cacb9546d0
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_not_translated.md
@@ -0,0 +1,44 @@
+# Code blocks { #code-blocks }
+
+Some text
+
+```python
+# This is a sample Python code block
+def hello_world():
+ # Comment with indentation
+ print("Hello, world!") # Print greeting
+```
+
+Some more text
+
+```toml
+# This is a sample TOML code block
+title = "TOML Example" # Title of the document
+```
+
+And more text
+
+```console
+// Use the command "live" and pass the language code as a CLI argument
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+And even more text
+
+```json
+{
+ // This is a sample JSON code block
+ "greeting": "Hello, world!" // Greeting
+}
+```
+
+Диаграма Mermaid
+
+```mermaid
+flowchart LR
+ stone(philosophers-stone) -->|requires| harry-1[harry v1]
+```
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_translated.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_translated.md
new file mode 100644
index 0000000000..d03dca53eb
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_mermaid_translated.md
@@ -0,0 +1,44 @@
+# Code blocks { #code-blocks }
+
+Some text
+
+```python
+# This is a sample Python code block
+def hello_world():
+ # Comment with indentation
+ print("Hello, world!") # Print greeting
+```
+
+Some more text
+
+```toml
+# This is a sample TOML code block
+title = "TOML Example" # Title of the document
+```
+
+And more text
+
+```console
+// Use the command "live" and pass the language code as a CLI argument
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+And even more text
+
+```json
+{
+ // This is a sample JSON code block
+ "greeting": "Hello, world!" // Greeting
+}
+```
+
+Диаграма Mermaid
+
+```mermaid
+flowchart LR
+ stone(philosophers-stone) -->|требует| harry-1[harry v1]
+```
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_gt.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_gt.md
new file mode 100644
index 0000000000..e77050cc95
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_gt.md
@@ -0,0 +1,50 @@
+# Code blocks { #code-blocks }
+
+Some text
+
+```python
+# This is a sample Python code block
+def hello_world():
+ # Comment with indentation
+ print("Hello, world!") # Print greeting
+```
+
+Some more text
+
+```toml
+# This is a sample TOML code block
+title = "TOML Example" # Title of the document
+```
+
+Extra code block
+
+```
+$ cd my_project
+```
+
+And more text
+
+```console
+// Use the command "live" and pass the language code as a CLI argument
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+And even more text
+
+```json
+{
+ // This is a sample JSON code block
+ "greeting": "Hello, world!" // Greeting
+}
+```
+
+Диаграма Mermaid
+
+```mermaid
+flowchart LR
+ stone(philosophers-stone) -->|requires| harry-1[harry v1]
+```
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_lt.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_lt.md
new file mode 100644
index 0000000000..918cb883f4
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_number_lt.md
@@ -0,0 +1,41 @@
+# Code blocks { #code-blocks }
+
+Some text
+
+```python
+# This is a sample Python code block
+def hello_world():
+ # Comment with indentation
+ print("Hello, world!") # Print greeting
+```
+
+Some more text
+
+Missing code block...
+
+And more text
+
+```console
+// Use the command "live" and pass the language code as a CLI argument
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+And even more text
+
+```json
+{
+ // This is a sample JSON code block
+ "greeting": "Hello, world!" // Greeting
+}
+```
+
+Диаграма Mermaid
+
+```mermaid
+flowchart LR
+ stone(philosophers-stone) -->|requires| harry-1[harry v1]
+```
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code.md
new file mode 100644
index 0000000000..88aed900d3
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code.md
@@ -0,0 +1,46 @@
+# Code blocks { #code-blocks }
+
+Some text
+
+```python
+# This is a sample Python code block
+def hello_world():
+ # Comment with indentation
+ print("Hello, world!") # Print greeting
+```
+
+Some more text
+
+The following block has wrong language code (should be TOML):
+
+```yaml
+# This is a sample TOML code block
+title = "TOML Example" # Title of the document
+```
+
+And more text
+
+```console
+// Use the command "live" and pass the language code as a CLI argument
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+And even more text
+
+```json
+{
+ // This is a sample JSON code block
+ "greeting": "Hello, world!" // Greeting
+}
+```
+
+Диаграма Mermaid
+
+```mermaid
+flowchart LR
+ stone(philosophers-stone) -->|requires| harry-1[harry v1]
+```
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code_2.md b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code_2.md
new file mode 100644
index 0000000000..a7fbb39f54
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/data/translated_doc_wrong_lang_code_2.md
@@ -0,0 +1,46 @@
+# Code blocks { #code-blocks }
+
+Some text
+
+```python
+# This is a sample Python code block
+def hello_world():
+ # Comment with indentation
+ print("Hello, world!") # Print greeting
+```
+
+Some more text
+
+The following block has wrong language code (should be TOML):
+
+```
+# This is a sample TOML code block
+title = "TOML Example" # Title of the document
+```
+
+And more text
+
+```console
+// Use the command "live" and pass the language code as a CLI argument
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+And even more text
+
+```json
+{
+ // This is a sample JSON code block
+ "greeting": "Hello, world!" // Greeting
+}
+```
+
+Диаграма Mermaid
+
+```mermaid
+flowchart LR
+ stone(philosophers-stone) -->|requires| harry-1[harry v1]
+```
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_lines_number_mismatch.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_lines_number_mismatch.py
new file mode 100644
index 0000000000..9cdbe8323a
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_lines_number_mismatch.py
@@ -0,0 +1,58 @@
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+from scripts.translation_fixer import cli
+
+data_path = Path(
+ "scripts/tests/test_translation_fixer/test_code_blocks/data"
+).absolute()
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_lines_number_gt.md")],
+ indirect=True,
+)
+def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_lines_number_gt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Code block (lines 14-18) has different number of lines than the original block (5 vs 4)"
+ ) in result.output
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_lines_number_lt.md")],
+ indirect=True,
+)
+def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ # assert result.exit_code == 1, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_lines_number_lt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Code block (lines 16-18) has different number of lines than the original block (3 vs 4)"
+ ) in result.output
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_mermaid.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_mermaid.py
new file mode 100644
index 0000000000..8b80c70f34
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_mermaid.py
@@ -0,0 +1,59 @@
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+from scripts.translation_fixer import cli
+
+data_path = Path(
+ "scripts/tests/test_translation_fixer/test_code_blocks/data"
+).absolute()
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_mermaid_translated.md")],
+ indirect=True,
+)
+def test_translated(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 0, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(
+ f"{data_path}/translated_doc_mermaid_translated.md"
+ ).read_text("utf-8")
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert (
+ "Skipping mermaid code block replacement (lines 41-44). This should be checked manually."
+ ) in result.output
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [
+ (
+ f"{data_path}/en_doc.md",
+ f"{data_path}/translated_doc_mermaid_not_translated.md",
+ )
+ ],
+ indirect=True,
+)
+def test_not_translated(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 0, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(
+ f"{data_path}/translated_doc_mermaid_not_translated.md"
+ ).read_text("utf-8")
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert ("Skipping mermaid code block replacement") not in result.output
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_number_mismatch.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_number_mismatch.py
new file mode 100644
index 0000000000..ad5767c1cb
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_number_mismatch.py
@@ -0,0 +1,60 @@
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+from scripts.translation_fixer import cli
+
+data_path = Path(
+ "scripts/tests/test_translation_fixer/test_code_blocks/data"
+).absolute()
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")],
+ indirect=True,
+)
+def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Number of code blocks does not match the number "
+ "in the original document (6 vs 5)"
+ ) in result.output
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")],
+ indirect=True,
+)
+def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ # assert result.exit_code == 1, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Number of code blocks does not match the number "
+ "in the original document (4 vs 5)"
+ ) in result.output
diff --git a/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_wrong_lang_code.py b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_wrong_lang_code.py
new file mode 100644
index 0000000000..85d73e3b4a
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_blocks/test_code_blocks_wrong_lang_code.py
@@ -0,0 +1,58 @@
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+from scripts.translation_fixer import cli
+
+data_path = Path(
+ "scripts/tests/test_translation_fixer/test_code_blocks/data"
+).absolute()
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_wrong_lang_code.md")],
+ indirect=True,
+)
+def test_wrong_lang_code_1(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_wrong_lang_code.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Code block (lines 16-19) has different language than the original block ('yaml' vs 'toml')"
+ ) in result.output
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_wrong_lang_code_2.md")],
+ indirect=True,
+)
+def test_wrong_lang_code_2(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(
+ f"{data_path}/translated_doc_wrong_lang_code_2.md"
+ ).read_text("utf-8")
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Code block (lines 16-19) has different language than the original block ('' vs 'toml')"
+ ) in result.output
diff --git a/scripts/tests/test_translation_fixer/test_code_includes/data/en_doc.md b/scripts/tests/test_translation_fixer/test_code_includes/data/en_doc.md
new file mode 100644
index 0000000000..593da0b327
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_includes/data/en_doc.md
@@ -0,0 +1,13 @@
+# Header
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+Some text
+
+{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
+
+Some more text
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
+
+And even more text
diff --git a/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_gt.md b/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_gt.md
new file mode 100644
index 0000000000..c1ad94d276
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_gt.md
@@ -0,0 +1,15 @@
+# Header
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+Some text
+
+{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
+
+Some more text
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
+
+And even more text
+
+{* ../../docs_src/python_types/tutorial001_py39.py *}
diff --git a/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_lt.md b/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_lt.md
new file mode 100644
index 0000000000..07eaf2c23d
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_lt.md
@@ -0,0 +1,13 @@
+# Header
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+Some text
+
+{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
+
+Some more text
+
+...
+
+And even more text
diff --git a/scripts/tests/test_translation_fixer/test_code_includes/test_number_mismatch.py b/scripts/tests/test_translation_fixer/test_code_includes/test_number_mismatch.py
new file mode 100644
index 0000000000..1020b890cf
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_code_includes/test_number_mismatch.py
@@ -0,0 +1,60 @@
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+from scripts.translation_fixer import cli
+
+data_path = Path(
+ "scripts/tests/test_translation_fixer/test_code_includes/data"
+).absolute()
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")],
+ indirect=True,
+)
+def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Number of code include placeholders does not match the number of code includes "
+ "in the original document (4 vs 3)"
+ ) in result.output
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")],
+ indirect=True,
+)
+def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Number of code include placeholders does not match the number of code includes "
+ "in the original document (2 vs 3)"
+ ) in result.output
diff --git a/scripts/tests/test_translation_fixer/test_complex_doc/data/en_doc.md b/scripts/tests/test_translation_fixer/test_complex_doc/data/en_doc.md
new file mode 100644
index 0000000000..69cd3f3fd7
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_complex_doc/data/en_doc.md
@@ -0,0 +1,244 @@
+# Test translation fixer tool { #test-translation-fixer }
+
+## Code blocks with and without comments { #code-blocks-with-and-without-comments }
+
+This is a test page for the translation fixer tool.
+
+### Code blocks with comments { #code-blocks-with-comments }
+
+The following code blocks include comments in different styles.
+Fixer tool should fix content, but preserve comments correctly.
+
+```python
+# This is a sample Python code block
+def hello_world():
+ # Comment with indentation
+ print("Hello, world!") # Print greeting
+```
+
+```toml
+# This is a sample TOML code block
+title = "TOML Example" # Title of the document
+```
+
+```console
+// Use the command "live" and pass the language code as a CLI argument
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+```json
+{
+ // This is a sample JSON code block
+ "greeting": "Hello, world!" // Greeting
+}
+```
+
+
+### Code blocks with comments where language uses different comment styles { #code-blocks-with-different-comment-styles }
+
+The following code blocks include comments in different styles based on the language.
+Fixer tool will not preserve comments in these blocks.
+
+```json
+{
+ # This is a sample JSON code block
+ "greeting": "Hello, world!" # Print greeting
+}
+```
+
+```console
+# This is a sample console code block
+$ echo "Hello, world!" # Print greeting
+```
+
+```toml
+// This is a sample TOML code block
+title = "TOML Example" // Title of the document
+```
+
+
+### Code blocks with comments with unsupported languages or without language specified { #code-blocks-with-unsupported-languages }
+
+The following code blocks use unsupported languages for comment preservation.
+Fixer tool will not preserve comments in these blocks.
+
+```javascript
+// This is a sample JavaScript code block
+console.log("Hello, world!"); // Print greeting
+```
+
+```
+# This is a sample console code block
+$ echo "Hello, world!" # Print greeting
+```
+
+```
+// This is a sample console code block
+$ echo "Hello, world!" // Print greeting
+```
+
+
+### Code blocks with comments that don't follow pattern { #code-blocks-with-comments-without-pattern }
+
+Fixer tool expects comments that follow specific pattern:
+
+- For hash-style comments: comment starts with `# ` (hash following by whitespace) in the beginning of the string or after a whitespace.
+- For slash-style comments: comment starts with `// ` (two slashes following by whitespace) in the beginning of the string or after a whitespace.
+
+If comment doesn't follow this pattern, fixer tool will not preserve it.
+
+```python
+#Function declaration
+def hello_world():# Print greeting
+ print("Hello, world!") #Print greeting without space after hash
+```
+
+```console
+//Function declaration
+def hello_world():// Print greeting
+ print("Hello, world!") //Print greeting without space after slashes
+```
+
+## Code blocks with quadruple backticks { #code-blocks-with-quadruple-backticks }
+
+The following code block uses quadruple backticks.
+
+````python
+# Hello world function
+def hello_world():
+ print("Hello, world!") # Print greeting
+````
+
+### Backticks number mismatch is fixable { #backticks-number-mismatch-is-fixable }
+
+The following code block has triple backticks in the original document, but quadruple backticks in the translated document.
+It will be fixed by the fixer tool (will convert to triple backticks).
+
+```Python
+# Some Python code
+```
+
+### Triple backticks inside quadruple backticks { #triple-backticks-inside-quadruple-backticks }
+
+Comments inside nested code block will NOT be preserved.
+
+````
+Here is a code block with quadruple backticks that contains triple backticks inside:
+
+```python
+# This is a sample Python code block
+def hello_world():
+ print("Hello, world!") # Print greeting
+```
+
+````
+
+# Code includes { #code-includes }
+
+## Simple code includes { #simple-code-includes }
+
+{* ../../docs_src/python_types/tutorial001_py39.py *}
+
+{* ../../docs_src/python_types/tutorial002_py39.py *}
+
+
+## Code includes with highlighting { #code-includes-with-highlighting }
+
+{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
+
+{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *}
+
+
+## Code includes with line ranges { #code-includes-with-line-ranges }
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] *}
+
+
+## Code includes with line ranges and highlighting { #code-includes-with-line-ranges-and-highlighting }
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
+
+{* ../../docs_src/dependencies/tutorial015_an_py310.py ln[10:15] hl[12:14] *}
+
+
+## Code includes qith title { #code-includes-with-title }
+
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
+
+{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
+
+## Code includes with unknown attributes { #code-includes-with-unknown-attributes }
+
+{* ../../docs_src/python_types/tutorial001_py39.py unknown[123] *}
+
+## Some more code includes to test fixing { #some-more-code-includes-to-test-fixing }
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
+
+
+
+# Links { #links }
+
+## Markdown-style links { #markdown-style-links }
+
+This is a [Markdown link](https://example.com) to an external site.
+
+This is a link with attributes: [**FastAPI** Project Generators](project-generation.md){.internal-link target=_blank}
+
+This is a link to the main FastAPI site: [FastAPI](https://fastapi.tiangolo.com) - tool should add language code to the URL.
+
+This is a link to one of the pages on FastAPI site: [How to](https://fastapi.tiangolo.com/how-to/) - tool should add language code to the URL.
+
+Link to test wrong attribute: [**FastAPI** Project Generators](project-generation.md){.internal-link} - tool should fix the attribute.
+
+Link with a title: [Example](https://example.com "Example site") - URL will be fixed, title preserved.
+
+### Markdown link to static assets { #markdown-link-to-static-assets }
+
+These are links to static assets:
+
+* [FastAPI Logo](https://fastapi.tiangolo.com/img/fastapi-logo.png)
+* [FastAPI CSS](https://fastapi.tiangolo.com/css/fastapi.css)
+* [FastAPI JS](https://fastapi.tiangolo.com/js/fastapi.js)
+
+Tool should NOT add language code to their URLs.
+
+## HTML-style links { #html-style-links }
+
+This is an HTML link to an external site.
+
+This is an link to the main FastAPI site - tool should add language code to the URL.
+
+This is an link to one of the pages on FastAPI site - tool should add language code to the URL.
+
+Link to test wrong attribute: **FastAPI** Project Generators - tool should fix the attribute.
+
+### HTML links to static assets { #html-links-to-static-assets }
+
+These are links to static assets:
+
+* FastAPI Logo
+* FastAPI CSS
+* FastAPI JS
+
+Tool should NOT add language code to their URLs.
+
+# Header (with HTML link to tiangolo.com) { #header-with-html-link-to-tiangolo-com }
+
+#Not a header
+
+```Python
+# Also not a header
+```
+
+Some text
diff --git a/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc.md b/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc.md
new file mode 100644
index 0000000000..c922d7b133
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc.md
@@ -0,0 +1,240 @@
+# Тестовый инструмент исправления переводов { #test-translation-fixer }
+
+## Блоки кода с комментариями и без комментариев { #code-blocks-with-and-without-comments }
+
+Это тестовая страница для инструмента исправления переводов.
+
+### Блоки кода с комментариями { #code-blocks-with-comments }
+
+Следующие блоки кода содержат комментарии в разных стилях.
+Инструмент исправления должен исправлять содержимое, но корректно сохранять комментарии.
+
+```python
+# Это пример блока кода на Python
+def hello_world():
+ # Комментарий с отступом
+ print("Hello, world!") # Печать приветствия
+```
+
+```toml
+# Это пример блока кода на TOML
+title = "TOML Example" # Заголовок документа
+```
+
+```console
+// Используйте команду "live" и передайте код языка в качестве аргумента CLI
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+```json
+{
+ // Это пример блока кода на JSON
+ "greeting": "Hello, world!" // Печать приветствия
+}
+```
+
+
+### Блоки кода с комментариями, где язык использует другие стили комментариев { #code-blocks-with-different-comment-styles }
+
+Следующие блоки кода содержат комментарии в разных стилях в зависимости от языка.
+Инструмент исправления не будет сохранять комментарии в этих блоках.
+
+```json
+{
+ # Это пример блока кода на JSON
+ "greeting": "Hello, world!" # Печать приветствия
+}
+```
+
+```console
+# Это пример блока кода консоли
+$ echo "Hello, world!" # Печать приветствия
+```
+
+```toml
+// Это пример блока кода на TOML
+title = "TOML Example" // Заголовок документа
+```
+
+### Блоки кода с комментариями на неподдерживаемых языках или без указания языка { #code-blocks-with-unsupported-languages }
+
+Следующие блоки кода используют неподдерживаемые языки для сохранения комментариев.
+Инструмент исправления не будет сохранять комментарии в этих блоках.
+
+```javascript
+// Это пример блока кода на JavaScript
+console.log("Hello, world!"); // Печать приветствия
+```
+
+```
+# Это пример блока кода консоли
+$ echo "Hello, world!" # Печать приветствия
+```
+
+```
+// Это пример блока кода консоли
+$ echo "Hello, world!" // Печать приветствия
+```
+
+### Блоки кода с комментариями, которые не соответствуют шаблону { #code-blocks-with-comments-without-pattern }
+
+Инструмент исправления ожидает комментарии, которые соответствуют определённому шаблону:
+
+- Для комментариев в стиле с решёткой: комментарий начинается с `# ` (решётка, затем пробел) в начале строки или после пробела.
+- Для комментариев в стиле со слешами: комментарий начинается с `// ` (два слеша, затем пробел) в начале строки или после пробела.
+
+Если комментарий не соответствует этому шаблону, инструмент исправления не будет его сохранять.
+
+```python
+#Объявление функции
+def hello_world():# Печать приветствия
+ print("Hello, world!") #Печать приветствия без пробела после решётки
+```
+
+```console
+//Объявление функции
+def hello_world():// Печать приветствия
+ print("Hello, world!") //Печать приветствия без пробела после слешей
+```
+
+## Блок кода с четырёхкратными обратными кавычками { #code-blocks-with-quadruple-backticks }
+
+Следующий блок кода содержит четырёхкратные обратные кавычки.
+
+````python
+# Функция приветствия
+def hello_world():
+ print("Hello, world") # Печать приветствия
+````
+
+### Несоответствие обратных кавычек фиксится { #backticks-number-mismatch-is-fixable }
+
+Следующий блок кода имеет тройные обратные кавычки в оригинальном документе, но четырёхкратные обратные кавычки в переведённом документе.
+Это будет исправлено инструментом исправления (будет преобразовано в тройные обратные кавычки).
+
+````Python
+# Немного кода на Python
+````
+
+### Блок кода в тройных обратных кавычка внутри блока кода в четырёхкратных обратных кавычках { #triple-backticks-inside-quadruple-backticks }
+
+Комментарии внутри вложенного блока кода в тройных обратных кавычках НЕ БУДУТ сохранены.
+
+````
+Here is a code block with quadruple backticks that contains triple backticks inside:
+
+```python
+# Этот комментарий НЕ будет сохранён
+def hello_world():
+ print("Hello, world") # Как и этот комментарий
+```
+
+````
+
+# Включения кода { #code-includes }
+
+## Простые включения кода { #simple-code-includes }
+
+{* ../../docs_src/python_types/tutorial001_py39.py *}
+
+{* ../../docs_src/python_types/tutorial002_py39.py *}
+
+
+## Включения кода с подсветкой { #code-includes-with-highlighting }
+
+{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
+
+{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *}
+
+
+## Включения кода с диапазонами строк { #code-includes-with-line-ranges }
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] *}
+
+
+## Включения кода с диапазонами строк и подсветкой { #code-includes-with-line-ranges-and-highlighting }
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
+
+{* ../../docs_src/dependencies/tutorial015_an_py310.py ln[10:15] hl[12:14] *}
+
+
+## Включения кода с заголовком { #code-includes-with-title }
+
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
+
+{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
+
+## Включения кода с неизвестными атрибутами { #code-includes-with-unknown-attributes }
+
+{* ../../docs_src/python_types/tutorial001_py39.py unknown[123] *}
+
+## Ещё включения кода для тестирования исправления { #some-more-code-includes-to-test-fixing }
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19 : 21] *}
+
+{* ../../docs_src/bigger_applications/app_an_py39/wrong.py hl[3] title["app/internal/admin.py"] *}
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[1:30] hl[1:10] *}
+
+# Ссылки { #links }
+
+## Ссылки в стиле Markdown { #markdown-style-links }
+
+Это [Markdown-ссылка](https://example.com) на внешний сайт.
+
+Это ссылка с атрибутами: [**FastAPI** генераторы проектов](project-generation.md){.internal-link target=_blank}
+
+Это ссылка на основной сайт FastAPI: [FastAPI](https://fastapi.tiangolo.com) — инструмент должен добавить код языка в URL.
+
+Это ссылка на одну из страниц на сайте FastAPI: [How to](https://fastapi.tiangolo.com/how-to) — инструмент должен добавить код языка в URL.
+
+Ссылка для тестирования неправильного атрибута: [**FastAPI** генераторы проектов](project-generation.md){.external-link} - инструмент должен исправить атрибут.
+
+Ссылка с заголовком: [Пример](http://example.com/ "Сайт для примера") - URL будет исправлен инструментом, заголовок сохранится.
+
+### Markdown ссылки на статические ресурсы { #markdown-link-to-static-assets }
+
+Это ссылки на статические ресурсы:
+
+* [FastAPI Logo](https://fastapi.tiangolo.com/img/fastapi-logo.png)
+* [FastAPI CSS](https://fastapi.tiangolo.com/css/fastapi.css)
+* [FastAPI JS](https://fastapi.tiangolo.com/js/fastapi.js)
+
+Инструмент НЕ должен добавлять код языка в их URL.
+
+## Ссылки в стиле HTML { #html-style-links }
+
+Это HTML-ссылка на внешний сайт.
+
+Это ссылка на основной сайт FastAPI — инструмент должен добавить код языка в URL.
+
+Это ссылка на одну из страниц на сайте FastAPI — инструмент должен добавить код языка в URL.
+
+Ссылка для тестирования неправильного атрибута: **FastAPI** генераторы проектов - инструмент должен исправить атрибут.
+
+### HTML ссылки на статические ресурсы { #html-links-to-static-assets }
+
+Это ссылки на статические ресурсы:
+
+* FastAPI Logo
+* FastAPI CSS
+* FastAPI JS
+
+Инструмент НЕ должен добавлять код языка в их URL.
+
+# Заголовок (с HTML ссылкой на tiangolo.com) { #header-5 }
+
+#Не заголовок
+
+```Python
+# Также не заголовок
+```
+
+Немного текста
diff --git a/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc_expected.md b/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc_expected.md
new file mode 100644
index 0000000000..b33f36e772
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc_expected.md
@@ -0,0 +1,240 @@
+# Тестовый инструмент исправления переводов { #test-translation-fixer }
+
+## Блоки кода с комментариями и без комментариев { #code-blocks-with-and-without-comments }
+
+Это тестовая страница для инструмента исправления переводов.
+
+### Блоки кода с комментариями { #code-blocks-with-comments }
+
+Следующие блоки кода содержат комментарии в разных стилях.
+Инструмент исправления должен исправлять содержимое, но корректно сохранять комментарии.
+
+```python
+# Это пример блока кода на Python
+def hello_world():
+ # Комментарий с отступом
+ print("Hello, world!") # Печать приветствия
+```
+
+```toml
+# Это пример блока кода на TOML
+title = "TOML Example" # Заголовок документа
+```
+
+```console
+// Используйте команду "live" и передайте код языка в качестве аргумента CLI
+$ python ./scripts/docs.py live es
+
+[INFO] Serving on http://127.0.0.1:8008
+[INFO] Start watching changes
+[INFO] Start detecting changes
+```
+
+```json
+{
+ // Это пример блока кода на JSON
+ "greeting": "Hello, world!" // Печать приветствия
+}
+```
+
+
+### Блоки кода с комментариями, где язык использует другие стили комментариев { #code-blocks-with-different-comment-styles }
+
+Следующие блоки кода содержат комментарии в разных стилях в зависимости от языка.
+Инструмент исправления не будет сохранять комментарии в этих блоках.
+
+```json
+{
+ # This is a sample JSON code block
+ "greeting": "Hello, world!" # Print greeting
+}
+```
+
+```console
+# This is a sample console code block
+$ echo "Hello, world!" # Print greeting
+```
+
+```toml
+// This is a sample TOML code block
+title = "TOML Example" // Title of the document
+```
+
+### Блоки кода с комментариями на неподдерживаемых языках или без указания языка { #code-blocks-with-unsupported-languages }
+
+Следующие блоки кода используют неподдерживаемые языки для сохранения комментариев.
+Инструмент исправления не будет сохранять комментарии в этих блоках.
+
+```javascript
+// This is a sample JavaScript code block
+console.log("Hello, world!"); // Print greeting
+```
+
+```
+# This is a sample console code block
+$ echo "Hello, world!" # Print greeting
+```
+
+```
+// This is a sample console code block
+$ echo "Hello, world!" // Print greeting
+```
+
+### Блоки кода с комментариями, которые не соответствуют шаблону { #code-blocks-with-comments-without-pattern }
+
+Инструмент исправления ожидает комментарии, которые соответствуют определённому шаблону:
+
+- Для комментариев в стиле с решёткой: комментарий начинается с `# ` (решётка, затем пробел) в начале строки или после пробела.
+- Для комментариев в стиле со слешами: комментарий начинается с `// ` (два слеша, затем пробел) в начале строки или после пробела.
+
+Если комментарий не соответствует этому шаблону, инструмент исправления не будет его сохранять.
+
+```python
+#Function declaration
+def hello_world():# Print greeting
+ print("Hello, world!") #Print greeting without space after hash
+```
+
+```console
+//Function declaration
+def hello_world():// Print greeting
+ print("Hello, world!") //Print greeting without space after slashes
+```
+
+## Блок кода с четырёхкратными обратными кавычками { #code-blocks-with-quadruple-backticks }
+
+Следующий блок кода содержит четырёхкратные обратные кавычки.
+
+````python
+# Функция приветствия
+def hello_world():
+ print("Hello, world!") # Печать приветствия
+````
+
+### Несоответствие обратных кавычек фиксится { #backticks-number-mismatch-is-fixable }
+
+Следующий блок кода имеет тройные обратные кавычки в оригинальном документе, но четырёхкратные обратные кавычки в переведённом документе.
+Это будет исправлено инструментом исправления (будет преобразовано в тройные обратные кавычки).
+
+```Python
+# Немного кода на Python
+```
+
+### Блок кода в тройных обратных кавычка внутри блока кода в четырёхкратных обратных кавычках { #triple-backticks-inside-quadruple-backticks }
+
+Комментарии внутри вложенного блока кода в тройных обратных кавычках НЕ БУДУТ сохранены.
+
+````
+Here is a code block with quadruple backticks that contains triple backticks inside:
+
+```python
+# This is a sample Python code block
+def hello_world():
+ print("Hello, world!") # Print greeting
+```
+
+````
+
+# Включения кода { #code-includes }
+
+## Простые включения кода { #simple-code-includes }
+
+{* ../../docs_src/python_types/tutorial001_py39.py *}
+
+{* ../../docs_src/python_types/tutorial002_py39.py *}
+
+
+## Включения кода с подсветкой { #code-includes-with-highlighting }
+
+{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
+
+{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *}
+
+
+## Включения кода с диапазонами строк { #code-includes-with-line-ranges }
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] *}
+
+
+## Включения кода с диапазонами строк и подсветкой { #code-includes-with-line-ranges-and-highlighting }
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
+
+{* ../../docs_src/dependencies/tutorial015_an_py310.py ln[10:15] hl[12:14] *}
+
+
+## Включения кода с заголовком { #code-includes-with-title }
+
+{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
+
+{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
+
+## Включения кода с неизвестными атрибутами { #code-includes-with-unknown-attributes }
+
+{* ../../docs_src/python_types/tutorial001_py39.py unknown[123] *}
+
+## Ещё включения кода для тестирования исправления { #some-more-code-includes-to-test-fixing }
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
+
+# Ссылки { #links }
+
+## Ссылки в стиле Markdown { #markdown-style-links }
+
+Это [Markdown-ссылка](https://example.com) на внешний сайт.
+
+Это ссылка с атрибутами: [**FastAPI** генераторы проектов](project-generation.md){.internal-link target=_blank}
+
+Это ссылка на основной сайт FastAPI: [FastAPI](https://fastapi.tiangolo.com/lang) — инструмент должен добавить код языка в URL.
+
+Это ссылка на одну из страниц на сайте FastAPI: [How to](https://fastapi.tiangolo.com/lang/how-to/) — инструмент должен добавить код языка в URL.
+
+Ссылка для тестирования неправильного атрибута: [**FastAPI** генераторы проектов](project-generation.md){.internal-link} - инструмент должен исправить атрибут.
+
+Ссылка с заголовком: [Пример](https://example.com "Сайт для примера") - URL будет исправлен инструментом, заголовок сохранится.
+
+### Markdown ссылки на статические ресурсы { #markdown-link-to-static-assets }
+
+Это ссылки на статические ресурсы:
+
+* [FastAPI Logo](https://fastapi.tiangolo.com/img/fastapi-logo.png)
+* [FastAPI CSS](https://fastapi.tiangolo.com/css/fastapi.css)
+* [FastAPI JS](https://fastapi.tiangolo.com/js/fastapi.js)
+
+Инструмент НЕ должен добавлять код языка в их URL.
+
+## Ссылки в стиле HTML { #html-style-links }
+
+Это HTML-ссылка на внешний сайт.
+
+Это ссылка на основной сайт FastAPI — инструмент должен добавить код языка в URL.
+
+Это ссылка на одну из страниц на сайте FastAPI — инструмент должен добавить код языка в URL.
+
+Ссылка для тестирования неправильного атрибута: **FastAPI** генераторы проектов - инструмент должен исправить атрибут.
+
+### HTML ссылки на статические ресурсы { #html-links-to-static-assets }
+
+Это ссылки на статические ресурсы:
+
+* FastAPI Logo
+* FastAPI CSS
+* FastAPI JS
+
+Инструмент НЕ должен добавлять код языка в их URL.
+
+# Заголовок (с HTML ссылкой на tiangolo.com) { #header-with-html-link-to-tiangolo-com }
+
+#Не заголовок
+
+```Python
+# Также не заголовок
+```
+
+Немного текста
diff --git a/scripts/tests/test_translation_fixer/test_complex_doc/test_compex_doc.py b/scripts/tests/test_translation_fixer/test_complex_doc/test_compex_doc.py
new file mode 100644
index 0000000000..cc7bcadda8
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_complex_doc/test_compex_doc.py
@@ -0,0 +1,30 @@
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+from scripts.translation_fixer import cli
+
+data_path = Path(
+ "scripts/tests/test_translation_fixer/test_complex_doc/data"
+).absolute()
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc.md")],
+ indirect=True,
+)
+def test_fix(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 0, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = (data_path / "translated_doc_expected.md").read_text("utf-8")
+ assert fixed_content == expected_content
+
+ assert "Fixing multiline code blocks in" in result.output
+ assert "Fixing markdown links in" in result.output
diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/data/en_doc.md b/scripts/tests/test_translation_fixer/test_header_permalinks/data/en_doc.md
new file mode 100644
index 0000000000..878e16fb5d
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_header_permalinks/data/en_doc.md
@@ -0,0 +1,19 @@
+# Header 1 { #header-1 }
+
+Some text
+
+## Header 2 { #header-2 }
+
+Some more text
+
+### Header 3 { #header-3 }
+
+Even more text
+
+# Header 4 { #header-4 }
+
+A bit more text
+
+#Not a header
+
+Final portion of text
diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_1.md b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_1.md
new file mode 100644
index 0000000000..feefd77705
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_1.md
@@ -0,0 +1,19 @@
+# Header 1 { #header-1 }
+
+Some text
+
+# Header 2 { #header-2 }
+
+Some more text
+
+### Header 3 { #header-3 }
+
+Even more text
+
+# Header 4 { #header-4 }
+
+A bit more text
+
+#Not a header
+
+Final portion of text
diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_2.md b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_2.md
new file mode 100644
index 0000000000..ad53a660cd
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_level_mismatch_2.md
@@ -0,0 +1,19 @@
+# Header 1 { #header-1 }
+
+Some text
+
+## Header 2 { #header-2 }
+
+Some more text
+
+### Header 3 { #header-3 }
+
+Even more text
+
+## Header 4 { #header-4 }
+
+A bit more text
+
+#Not a header
+
+Final portion of text
diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_gt.md b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_gt.md
new file mode 100644
index 0000000000..9c517c9bb0
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_gt.md
@@ -0,0 +1,19 @@
+# Header 1 { #header-1 }
+
+Some text
+
+## Header 2 { #header-2 }
+
+Some more text
+
+### Header 3 { #header-3 }
+
+Even more text
+
+# Header 4 { #header-4 }
+
+A bit more text
+
+# Extra header
+
+Final portion of text
diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_lt.md b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_lt.md
new file mode 100644
index 0000000000..8a308728b2
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_header_permalinks/data/translated_doc_number_lt.md
@@ -0,0 +1,19 @@
+# Header 1 { #header-1 }
+
+Some text
+
+## Header 2 { #header-2 }
+
+Some more text
+
+### Header 3 { #header-3 }
+
+Even more text
+
+Header 4 is missing
+
+A bit more text
+
+#Not a header
+
+Final portion of text
diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_level_mismatch.py b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_level_mismatch.py
new file mode 100644
index 0000000000..99d27db953
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_level_mismatch.py
@@ -0,0 +1,60 @@
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+from scripts.translation_fixer import cli
+
+data_path = Path(
+ "scripts/tests/test_translation_fixer/test_header_permalinks/data"
+).absolute()
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_level_mismatch_1.md")],
+ indirect=True,
+)
+def test_level_mismatch_1(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(
+ f"{data_path}/translated_doc_level_mismatch_1.md"
+ ).read_text("utf-8")
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Header levels do not match between document and original document"
+ " (found #, expected ##) for header №2 in line 5"
+ ) in result.output
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_level_mismatch_2.md")],
+ indirect=True,
+)
+def test_level_mismatch_2(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(
+ f"{data_path}/translated_doc_level_mismatch_2.md"
+ ).read_text("utf-8")
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Header levels do not match between document and original document"
+ " (found ##, expected #) for header №4 in line 13"
+ ) in result.output
diff --git a/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_number_mismatch.py b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_number_mismatch.py
new file mode 100644
index 0000000000..c4d0346171
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_header_permalinks/test_header_number_mismatch.py
@@ -0,0 +1,60 @@
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+from scripts.translation_fixer import cli
+
+data_path = Path(
+ "scripts/tests/test_translation_fixer/test_header_permalinks/data"
+).absolute()
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")],
+ indirect=True,
+)
+def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Number of headers with permalinks does not match the number "
+ "in the original document (5 vs 4)"
+ ) in result.output
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")],
+ indirect=True,
+)
+def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Number of headers with permalinks does not match the number "
+ "in the original document (3 vs 4)"
+ ) in result.output
diff --git a/scripts/tests/test_translation_fixer/test_html_links/data/en_doc.md b/scripts/tests/test_translation_fixer/test_html_links/data/en_doc.md
new file mode 100644
index 0000000000..4c4d104cf2
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_html_links/data/en_doc.md
@@ -0,0 +1,19 @@
+# Header 1 { #header-1 }
+
+Some text with a link to FastAPI.
+
+## Header 2 { #header-2 }
+
+Two links here: How to and Project Generators.
+
+### Header 3 { #header-3 }
+
+Another link: **FastAPI** Project Generators with title.
+
+# Header 4 { #header-4 }
+
+Link to anchor: Header 2
+
+# Header with link { #header-with-link }
+
+Some text
diff --git a/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_gt.md b/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_gt.md
new file mode 100644
index 0000000000..bac40242be
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_gt.md
@@ -0,0 +1,21 @@
+# Заголовок 1 { #header-1 }
+
+Немного текста со ссылкой на FastAPI.
+
+## Заголовок 2 { #header-2 }
+
+Две ссылки здесь: How to и Project Generators.
+
+### Заголовок 3 { #header-3 }
+
+Ещё ссылка: **FastAPI** Генераторы Проектов с тайтлом.
+
+И ещё одна экстра ссылка.
+
+# Заголовок 4 { #header-4 }
+
+Ссылка на якорь: Заголовок 2
+
+# Заголовок со ссылкой { #header-with-link }
+
+Немного текста
diff --git a/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_lt.md b/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_lt.md
new file mode 100644
index 0000000000..e2b36b6887
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_html_links/data/translated_doc_number_lt.md
@@ -0,0 +1,19 @@
+# Заголовок 1 { #header-1 }
+
+Немного текста со ссылкой на FastAPI.
+
+## Заголовок 2 { #header-2 }
+
+Две ссылки здесь: How to и Project Generators.
+
+### Заголовок 3 { #header-3 }
+
+Ещё ссылка: **FastAPI** Генераторы Проектов с тайтлом.
+
+# Заголовок 4 { #header-4 }
+
+Ссылка на якорь: Заголовок 2
+
+# Заголовок с потерянной ссылкой { #header-with-link }
+
+Немного текста
diff --git a/scripts/tests/test_translation_fixer/test_html_links/test_html_links_number_mismatch.py b/scripts/tests/test_translation_fixer/test_html_links/test_html_links_number_mismatch.py
new file mode 100644
index 0000000000..11dcea1541
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_html_links/test_html_links_number_mismatch.py
@@ -0,0 +1,58 @@
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+from scripts.translation_fixer import cli
+
+data_path = Path("scripts/tests/test_translation_fixer/test_html_links/data").absolute()
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")],
+ indirect=True,
+)
+def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Number of HTML links does not match the number "
+ "in the original document (7 vs 6)"
+ ) in result.output
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")],
+ indirect=True,
+)
+def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ # assert result.exit_code == 1, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Number of HTML links does not match the number "
+ "in the original document (5 vs 6)"
+ ) in result.output
diff --git a/scripts/tests/test_translation_fixer/test_markdown_links/data/en_doc.md b/scripts/tests/test_translation_fixer/test_markdown_links/data/en_doc.md
new file mode 100644
index 0000000000..9d965b0d68
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_markdown_links/data/en_doc.md
@@ -0,0 +1,19 @@
+# Header 1 { #header-1 }
+
+Some text with a link to [FastAPI](https://fastapi.tiangolo.com).
+
+## Header 2 { #header-2 }
+
+Two links here: [How to](https://fastapi.tiangolo.com/how-to/) and [Project Generators](project-generation.md){.internal-link target=_blank}.
+
+### Header 3 { #header-3 }
+
+Another link: [**FastAPI** Project Generators](project-generation.md "Link title"){.internal-link target=_blank} with title.
+
+# Header 4 { #header-4 }
+
+Link to anchor: [Header 2](#header-2)
+
+# Header with [link](http://example.com) { #header-with-link }
+
+Some text
diff --git a/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc.md b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc.md
new file mode 100644
index 0000000000..804a7e60a6
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc.md
@@ -0,0 +1,19 @@
+# Заголовок 1 { #header-1 }
+
+Немного текста со ссылкой на [FastAPI](https://fastapi.tiangolo.com).
+
+## Заголовок 2 { #header-2 }
+
+Две ссылки здесь: [How to](https://fastapi.tiangolo.com/how-to/) и [Project Generators](project-generation.md){.internal-link target=_blank}.
+
+### Заголовок 3 { #header-3 }
+
+Ещё ссылка: [**FastAPI** Генераторы Проектов](project-generation.md "Тайтл"){.internal-link target=_blank} с тайтлом.
+
+# Заголовок 4 { #header-4 }
+
+Ссылка на якорь: [Заголовок 2](#header-2)
+
+# Заголовок со [ссылкой](http://example.com) { #header-with-link }
+
+Немного текста
diff --git a/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_gt.md b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_gt.md
new file mode 100644
index 0000000000..9cbedb676e
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_gt.md
@@ -0,0 +1,21 @@
+# Заголовок 1 { #header-1 }
+
+Немного текста со ссылкой на [FastAPI](https://fastapi.tiangolo.com).
+
+## Заголовок 2 { #header-2 }
+
+Две ссылки здесь: [How to](https://fastapi.tiangolo.com/how-to/) и [Project Generators](project-generation.md){.internal-link target=_blank}.
+
+### Заголовок 3 { #header-3 }
+
+Ещё ссылка: [**FastAPI** Генераторы Проектов](project-generation.md "Тайтл"){.internal-link target=_blank} с тайтлом.
+
+И ещё одна [экстра ссылка](https://github.com).
+
+# Заголовок 4 { #header-4 }
+
+Ссылка на якорь: [Заголовок 2](#header-2)
+
+# Заголовок со [ссылкой](http://example.com) { #header-with-link }
+
+Немного текста
diff --git a/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_lt.md b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_lt.md
new file mode 100644
index 0000000000..4e9e6ccf78
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_markdown_links/data/translated_doc_number_lt.md
@@ -0,0 +1,19 @@
+# Заголовок 1 { #header-1 }
+
+Немного текста со ссылкой на [FastAPI](https://fastapi.tiangolo.com).
+
+## Заголовок 2 { #header-2 }
+
+Две ссылки здесь: [How to](https://fastapi.tiangolo.com/how-to/) и [Project Generators](project-generation.md){.internal-link target=_blank}.
+
+### Заголовок 3 { #header-3 }
+
+Ещё ссылка: [**FastAPI** Генераторы Проектов](project-generation.md "Тайтл"){.internal-link target=_blank} с тайтлом.
+
+# Заголовок 4 { #header-4 }
+
+Ссылка на якорь: [Заголовок 2](#header-2)
+
+# Заголовок с потерянной ссылкой { #header-with-link }
+
+Немного текста
diff --git a/scripts/tests/test_translation_fixer/test_markdown_links/test_mkd_links_number_mismatch.py b/scripts/tests/test_translation_fixer/test_markdown_links/test_mkd_links_number_mismatch.py
new file mode 100644
index 0000000000..c72e012543
--- /dev/null
+++ b/scripts/tests/test_translation_fixer/test_markdown_links/test_mkd_links_number_mismatch.py
@@ -0,0 +1,60 @@
+from pathlib import Path
+
+import pytest
+from typer.testing import CliRunner
+
+from scripts.translation_fixer import cli
+
+data_path = Path(
+ "scripts/tests/test_translation_fixer/test_markdown_links/data"
+).absolute()
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")],
+ indirect=True,
+)
+def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ assert result.exit_code == 1, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Number of markdown links does not match the number "
+ "in the original document (7 vs 6)"
+ ) in result.output
+
+
+@pytest.mark.parametrize(
+ "copy_test_files",
+ [(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")],
+ indirect=True,
+)
+def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
+ result = runner.invoke(
+ cli,
+ ["fix-pages", "docs/lang/docs/doc.md"],
+ )
+ # assert result.exit_code == 1, result.output
+
+ fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text("utf-8")
+ expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text(
+ "utf-8"
+ )
+
+ assert fixed_content == expected_content # Translated doc remains unchanged
+ assert "Error processing docs/lang/docs/doc.md" in result.output
+ assert (
+ "Number of markdown links does not match the number "
+ "in the original document (5 vs 6)"
+ ) in result.output
diff --git a/scripts/topic_repos.py b/scripts/topic_repos.py
index bc14977513..b7afc0864a 100644
--- a/scripts/topic_repos.py
+++ b/scripts/topic_repos.py
@@ -56,9 +56,10 @@ def main() -> None:
return
repos_path.write_text(new_repos_content, encoding="utf-8")
logging.info("Setting up GitHub Actions git user")
- subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
+ subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True)
subprocess.run(
- ["git", "config", "user.email", "github-actions@github.com"], check=True
+ ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"],
+ check=True,
)
branch_name = f"fastapi-topic-repos-{secrets.token_hex(4)}"
logging.info(f"Creating a new branch {branch_name}")
diff --git a/scripts/translate.py b/scripts/translate.py
index ede101e8fc..eba4ad6a2f 100644
--- a/scripts/translate.py
+++ b/scripts/translate.py
@@ -1,3 +1,4 @@
+import json
import secrets
import subprocess
from collections.abc import Iterable
@@ -24,650 +25,8 @@ non_translated_sections = (
"contributing.md",
)
-
-general_prompt = """
-### About literal text in this prompt
-
-1) In the following instructions (after I say: `The above rules are in effect now`) the two characters `«` and `»` will be used to surround LITERAL TEXT, which is text or characters you shall interpret literally. The `«` and the `»` are not part of the literal text, they are the meta characters denoting it.
-
-2) Furthermore, text surrounded by `«««` and `»»»` is a BLOCK OF LITERAL TEXT which spans multiple lines. To get its content, dedent all lines of the block until the `«««` and `»»»` are at column zero, then remove the newline (`\n`) after the `«««` and the newline before the `»»»`. The `«««` and the `»»»` are not part of the literal text block, they are the meta characters denoting it.
-
-3) If you see backticks or any other quotes inside literal text – inside `«` and `»` – or inside blocks of literal text – inside `«««` and `»»»` – then interpret them as literal characters, do NOT interpret them as meta characters.
-
-The above rules are in effect now.
-
-
-### Definitions of terms used in this prompt
-
-"backtick"
-
- The character «`»
- Unicode U+0060 (GRAVE ACCENT)
-
-"single backtick"
-
- A single backtick – «`»
-
-"triple backticks"
-
- Three backticks in a row – «```»
-
-"neutral double quote"
-
- The character «"»
- Unicode U+0022 (QUOTATION MARK)
-
-"neutral single quote"
-
- The character «'»
- Unicode U+0027 (APOSTROPHE)
-
-"English double typographic quotes"
-
- The characters «“» and «”»
- Unicode U+201C (LEFT DOUBLE QUOTATION MARK) and Unicode U+201D (RIGHT DOUBLE QUOTATION MARK)
-
-"English single typographic quotes"
-
- The characters «‘» and «’»
- Unicode U+2018 (LEFT SINGLE QUOTATION MARK) and Unicode U+2019 (RIGHT SINGLE QUOTATION MARK)
-
-"code snippet"
-
- Also called "inline code". Text in a Markdown document which is surrounded by single backticks. A paragraph in a Markdown document can have a more than one code snippet.
-
- Example:
-
- «««
- `i am a code snippet`
- »»»
-
- Example:
-
- «««
- `first code snippet` `second code snippet` `third code snippet`
- »»»
-
-"code block"
-
- Text in a Markdown document which is surrounded by triple backticks. Spreads multiple lines.
-
- Example:
-
- «««
- ```
- Hello
- World
- ```
- »»»
-
- Example:
-
- «««
- ```python
- print("hello World")
- ```
- »»»
-
-"HTML element"
-
- a HTML opening tag – e.g. «» – and a HTML closing tag – e.g. «» – surrounding text or other HTML elements.
-
-
-### Your task
-
-Translate an English text – the original content – to a target language.
-
-The original content is written in Markdown, write the translation in Markdown as well.
-
-The original content will be surrounded by triple percentage signs («%%%»). Do not include the triple percentage signs in the translation.
-
-
-### Technical terms in English
-
-For technical terms in English that don't have a common translation term, use the original term in English.
-
-
-### Content of code snippets
-
-Do not translate the content of code snippets, keep the original in English. For example, «`list`», «`dict`», keep them as is.
-
-
-### Content of code blocks
-
-Do not translate the content of code blocks, except for comments in the language which the code block uses.
-
-Examples:
-
- Source (English) – The code block is a bash code example with one comment:
-
- «««
- ```bash
- # Print greeting
- echo "Hello, World!"
- ```
- »»»
-
- Result (German):
-
- «««
- ```bash
- # Gruß ausgeben
- echo "Hello, World!"
- ```
- »»»
-
- Source (English) – The code block is a console example containing HTML tags. No comments, so nothing to change here:
-
- «««
- ```console
- $ fastapi run main.py
- FastAPI Starting server
- Searching for package file structure
- ```
- »»»
-
- Result (German):
-
- «««
- ```console
- $ fastapi run main.py
- FastAPI Starting server
- Searching for package file structure
- ```
- »»»
-
- Source (English) – The code block is a console example containing 5 comments:
-
- «««
- ```console
- // Go to the home directory
- $ cd
- // Create a directory for all your code projects
- $ mkdir code
- // Enter into that code directory
- $ cd code
- // Create a directory for this project
- $ mkdir awesome-project
- // Enter into that project directory
- $ cd awesome-project
- ```
- »»»
-
- Result (German):
-
- «««
- ```console
- // Gehe zum Home-Verzeichnis
- $ cd
- // Erstelle ein Verzeichnis für alle Ihre Code-Projekte
- $ mkdir code
- // Gehe in dieses Code-Verzeichnis
- $ cd code
- // Erstelle ein Verzeichnis für dieses Projekt
- $ mkdir awesome-project
- // Gehe in dieses Projektverzeichnis
- $ cd awesome-project
- ```
- »»»
-
-If there is an existing translation and its Mermaid diagram is in sync with the Mermaid diagram in the English source, except a few translated words, then use the Mermaid diagram of the existing translation. The human editor of the translation translated these words in the Mermaid diagram. Keep these translations, do not revert them back to the English source.
-
-Example:
-
- Source (English):
-
- «««
- ```mermaid
- flowchart LR
- subgraph global[global env]
- harry-1[harry v1]
- end
- subgraph stone-project[philosophers-stone project]
- stone(philosophers-stone) -->|requires| harry-1
- end
- ```
- »»»
-
- Existing translation (German) – has three translations:
-
- «««
- ```mermaid
- flowchart LR
- subgraph global[globale Umgebung]
- harry-1[harry v1]
- end
- subgraph stone-project[philosophers-stone-Projekt]
- stone(philosophers-stone) -->|benötigt| harry-1
- end
- ```
- »»»
-
- Result (German) – you change nothing:
-
- «««
- ```mermaid
- flowchart LR
- subgraph global[globale Umgebung]
- harry-1[harry v1]
- end
- subgraph stone-project[philosophers-stone-Projekt]
- stone(philosophers-stone) -->|benötigt| harry-1
- end
- ```
- »»»
-
-
-### Special blocks
-
-There are special blocks of notes, tips and others that look like:
-
- «««
- /// note
- »»»
-
-To translate it, keep the same line and add the translation after a vertical bar.
-
-For example, if you were translating to Spanish, you would write:
-
- «««
- /// note | Nota
- »»»
-
-Some examples in Spanish:
-
- Source:
-
- «««
- /// tip
- »»»
-
- Result:
-
- «««
- /// tip | Consejo
- »»»
-
- Source:
-
- «««
- /// details | Preview
- »»»
-
- Result:
-
- «««
- /// details | Vista previa
- »»»
-
-
-### Tab blocks
-
-There are special blocks surrounded by four slashes («////»). They mark text, which will be rendered as part of a tab in the final document. The scheme is:
-
- //// tab | {tab title}
- {tab content, may span many lines}
- ////
-
-Keep everything before the vertical bar («|») as is, including the vertical bar. Translate the tab title. Translate the tab content, applying the rules you know. Keep the four block closing slashes as is.
-
-Examples:
-
- Source (English):
-
- «««
- //// tab | Python 3.8+ non-Annotated
- Hello
- ////
- »»»
-
- Result (German):
-
- «««
- //// tab | Python 3.8+ nicht annotiert
- Hallo
- ////
- »»»
-
- Source (English) – Here there is nothing to translate in the tab title:
-
- «««
- //// tab | Linux, macOS, Windows Bash
- Hello again
- ////
- »»»
-
- Result (German):
-
- «««
- //// tab | Linux, macOS, Windows Bash
- Hallo wieder
- ////
- »»»
-
-
-### Headings
-
-Every Markdown heading in the English text (all levels) ends with a part inside curly brackets. This part denotes the hash of this heading, which is used in links to this heading. In translations, translate the heading, but do not translate this hash part, so that links do not break.
-
-Examples of how to translate a heading:
-
- Source (English):
-
- «««
- ## Alternative API docs { #alternative-api-docs }
- »»»
-
- Result (Spanish):
-
- «««
- ## Documentación de la API alternativa { #alternative-api-docs }
- »»»
-
- Source (English):
-
- «««
- ### Example { #example }
- »»»
-
- Result (German):
-
- «««
- ### Beispiel { #example }
- »»»
-
-
-### Links
-
-Use the following rules for links (apply both to Markdown-style links ([text](url)) and to HTML-style tags):
-
-1) For relative URLs, only translate link text. Do not translate the URL or its parts
-
-Example:
-
- Source (English):
-
- «««
- [One of the fastest Python frameworks available](#performance)
- »»»
-
- Result (German):
-
- «««
- [Eines der schnellsten verfügbaren Python-Frameworks](#performance)
- »»»
-
-2) For absolute URLs which DO NOT start EXACTLY with «https://fastapi.tiangolo.com», only translate link text and leave the URL unchanged.
-
-Example:
-
- Source (English):
-
- «««
- SQLModel docs
- »»»
-
- Result (German):
-
- «««
- SQLModel-Dokumentation
- »»»
-
-3) For absolute URLs which DO start EXACTLY with «https://fastapi.tiangolo.com», only translate link text and change the URL by adding language code («https://fastapi.tiangolo.com/{language_code}[rest part of the url]»).
-
-Example:
-
- Source (English):
-
- «««
- Documentation
- »»»
-
- Result (Spanish):
-
- «««
- Documentación
- »»»
-
-3.1) Do not add language codes for URLs that point to static assets (e.g., images, CSS, JavaScript).
-
-Example:
-
- Source (English):
-
- «««
- Something
- »»»
-
- Result (Spanish):
-
- «««
- Algo
- »»»
-
-4) For internal links, only translate link text.
-
-Example:
-
- Source (English):
-
- «««
- [Create Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}
- »»»
-
- Result (German):
-
- «««
- [Pull Requests erzeugen](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}
- »»»
-
-5) Do not translate anchor fragments in links (the part after «#»), as they must remain the same to work correctly.
-
-5.1) If an existing translation has a link with an anchor fragment different to the anchor fragment in the English source, then this is an error. Fix this by using the anchor fragment of the English source.
-
-Example:
-
- Source (English):
-
- «««
- [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}
- »»»
-
- Existing wrong translation (German) – notice the wrongly translated anchor fragment:
-
- «««
- [Body – Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#einzelne-werte-im-body){.internal-link target=_blank}.
- »»»
-
- Result (German) – you fix the anchor fragment:
-
- «««
- [Body – Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
- »»»
-
-5.2) Do not add anchor fragments at will, even if this makes sense. If the English source has no anchor, don't add one.
-
-Example:
-
- Source (English):
-
- «««
- Create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}
- »»»
-
- Wrong translation (German) – Anchor added to the URL.
-
- «««
- Erstelle eine [virtuelle Umgebung](../virtual-environments.md#create-a-virtual-environment){.internal-link target=_blank}
- »»»
-
- Good translation (German) – URL stays like in the English source.
-
- «««
- Erstelle eine [Virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank}
- »»»
-
-
-### HTML abbr elements
-
-Translate HTML abbr elements («text») as follows:
-
-1) If the text surrounded by the abbr element is an abbreviation (the text may be surrounded by further HTML or Markdown markup or quotes, for example «text» or «`text`» or «"text"», ignore that further markup when deciding if the text is an abbreviation), and if the description (the text inside the title attribute) contains the full phrase for this abbreviation, then append a dash («–») to the full phrase, followed by the translation of the full phrase.
-
-Conversion scheme:
-
- Source (English):
-
- {abbreviation}
-
- Result:
-
- {abbreviation}
-
-Examples:
-
- Source (English):
-
- «««
- IoT
- CPU
- TL;DR:
- »»»
-
- Result (German):
-
- «««
- IoT
- CPU
- TL;DR:
- »»»
-
-1.1) If the language to which you translate mostly uses the letters of the ASCII char set (for example Spanish, French, German, but not Russian, Chinese) and if the translation of the full phrase is identical to, or starts with the same letters as the original full phrase, then only give the translation of the full phrase.
-
-Conversion scheme:
-
- Source (English):
-
- {abbreviation}
-
- Result:
-
- {abbreviation}
-
-Examples:
-
- Source (English):
-
- «««
- JWT
- Enum
- ASGI
- »»»
-
- Result (German):
-
- «««
- JWT
- Enum
- ASGI
- »»»
-
-2) If the description is not a full phrase for an abbreviation which the abbr element surrounds, but some other information, then just translate the description.
-
-Conversion scheme:
-
- Source (English):
-
- {text}
-
- Result:
-
- {translation of text}
-
-Examples:
-
- Source (English):
-
- «««
- path
- linter
- parsing
- 0.95.0
- at the time of writing this
- »»»
-
- Result (German):
-
- «««
- Pfad
- Linter
- Parsen
- 0.95.0
- zum Zeitpunkt als das hier geschrieben wurde
- »»»
-
-
-3) If the text surrounded by the abbr element is an abbreviation and the description contains both the full phrase for that abbreviation, and other information, separated by a colon («:»), then append a dash («–») and the translation of the full phrase to the original full phrase and translate the other information.
-
-Conversion scheme:
-
- Source (English):
-
- {abbreviation}
-
- Result:
-
- {abbreviation}
-
-Examples:
-
- Source (English):
-
- «««
- I/O
- CDN
- IDE
- »»»
-
- Result (German):
-
- «««
- I/O
- CDN
- IDE
- »»»
-
-3.1) Like in rule 2.1, you can leave the original full phrase away, if the translated full phrase is identical or starts with the same letters as the original full phrase.
-
-Conversion scheme:
-
- Source (English):
-
- {abbreviation}
-
- Result:
-
- {abbreviation}
-
-Example:
-
- Source (English):
-
- «««
- ORM
- »»»
-
- Result (German):
-
- «««
- ORM
- »»»
-
-4) If there is an existing translation, and it has ADDITIONAL abbr elements in a sentence, and these additional abbr elements do not exist in the related sentence in the English text, then KEEP those additional abbr elements in the translation. Do not remove them. Except when you remove the whole sentence from the translation, because the whole sentence was removed from the English text, then also remove the abbr element. The reasoning for this rule is, that such additional abbr elements are manually added by the human editor of the translation, in order to translate or explain an English word to the human readers of the translation. These additional abbr elements would not make sense in the English text, but they do make sense in the translation. So keep them in the translation, even though they are not part of the English text. This rule only applies to abbr elements.
-
-5) Apply above rules also when there is an existing translation! Make sure that all title attributes in abbr elements get properly translated or updated, using the schemes given above. However, leave the ADDITIONAL abbr's from rule 4 alone. Do not change their formatting or content.
-
-"""
+general_prompt_path = Path(__file__).absolute().parent / "general-llm-prompt.md"
+general_prompt = general_prompt_path.read_text(encoding="utf-8")
app = typer.Typer()
@@ -726,7 +85,7 @@ def translate_page(
print(f"Found existing translation: {out_path}")
old_translation = out_path.read_text(encoding="utf-8")
print(f"Translating {en_path} to {language} ({language_name})")
- agent = Agent("openai:gpt-5")
+ agent = Agent("openai:gpt-5.2")
prompt_segments = [
general_prompt,
@@ -828,6 +187,65 @@ def translate_lang(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) ->
print(f"Done translating: {p}")
+def get_llm_translatable() -> list[str]:
+ translatable_langs = []
+ langs = get_langs()
+ for lang in langs:
+ if lang == "en":
+ continue
+ lang_prompt_path = Path(f"docs/{lang}/llm-prompt.md")
+ if lang_prompt_path.exists():
+ translatable_langs.append(lang)
+ return translatable_langs
+
+
+@app.command()
+def list_llm_translatable() -> list[str]:
+ translatable_langs = get_llm_translatable()
+ print("LLM translatable languages:", translatable_langs)
+ return translatable_langs
+
+
+@app.command()
+def llm_translatable_json(
+ language: Annotated[str | None, typer.Option(envvar="LANGUAGE")] = None,
+) -> None:
+ translatable_langs = get_llm_translatable()
+ if language:
+ if language in translatable_langs:
+ print(json.dumps([language]))
+ return
+ else:
+ raise typer.Exit(code=1)
+ print(json.dumps(translatable_langs))
+
+
+@app.command()
+def commands_json(
+ command: Annotated[str | None, typer.Option(envvar="COMMAND")] = None,
+) -> None:
+ available_commands = [
+ "translate-page",
+ "translate-lang",
+ "update-outdated",
+ "add-missing",
+ "update-and-add",
+ "remove-removable",
+ ]
+ default_commands = [
+ "remove-removable",
+ "update-outdated",
+ "add-missing",
+ ]
+ if command:
+ if command in available_commands:
+ print(json.dumps([command]))
+ return
+ else:
+ raise typer.Exit(code=1)
+ print(json.dumps(default_commands))
+
+
@app.command()
def list_removable(language: str) -> list[Path]:
removable_paths: list[Path] = []
@@ -854,7 +272,7 @@ def list_all_removable() -> list[Path]:
@app.command()
-def remove_removable(language: str) -> None:
+def remove_removable(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None:
removable_paths = list_removable(language)
for path in removable_paths:
path.unlink()
@@ -939,24 +357,39 @@ def update_and_add(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) ->
def make_pr(
*,
language: Annotated[str | None, typer.Option(envvar="LANGUAGE")] = None,
+ command: Annotated[str | None, typer.Option(envvar="COMMAND")] = None,
github_token: Annotated[str, typer.Option(envvar="GITHUB_TOKEN")],
github_repository: Annotated[str, typer.Option(envvar="GITHUB_REPOSITORY")],
+ commit_in_place: Annotated[
+ bool, typer.Option(envvar="COMMIT_IN_PLACE", show_default=True)
+ ] = False,
) -> None:
print("Setting up GitHub Actions git user")
repo = git.Repo(Path(__file__).absolute().parent.parent)
if not repo.is_dirty(untracked_files=True):
print("Repository is clean, no changes to commit")
return
- subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
+ subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True)
subprocess.run(
- ["git", "config", "user.email", "github-actions@github.com"], check=True
+ ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"],
+ check=True,
)
- branch_name = "translate"
- if language:
- branch_name += f"-{language}"
- branch_name += f"-{secrets.token_hex(4)}"
- print(f"Creating a new branch {branch_name}")
- subprocess.run(["git", "checkout", "-b", branch_name], check=True)
+ current_branch = repo.active_branch.name
+ if current_branch == "master" and commit_in_place:
+ print("Can't commit directly to master")
+ raise typer.Exit(code=1)
+
+ if not commit_in_place:
+ branch_name = "translate"
+ if language:
+ branch_name += f"-{language}"
+ if command:
+ branch_name += f"-{command}"
+ branch_name += f"-{secrets.token_hex(4)}"
+ print(f"Creating a new branch {branch_name}")
+ subprocess.run(["git", "checkout", "-b", branch_name], check=True)
+ else:
+ print(f"Committing in place on branch {current_branch}")
print("Adding updated files")
git_path = Path("docs")
subprocess.run(["git", "add", str(git_path)], check=True)
@@ -964,16 +397,25 @@ def make_pr(
message = "🌐 Update translations"
if language:
message += f" for {language}"
+ if command:
+ message += f" ({command})"
subprocess.run(["git", "commit", "-m", message], check=True)
print("Pushing branch")
subprocess.run(["git", "push", "origin", branch_name], check=True)
- print("Creating PR")
- g = Github(github_token)
- gh_repo = g.get_repo(github_repository)
- pr = gh_repo.create_pull(
- title=message, body=message, base="master", head=branch_name
- )
- print(f"Created PR: {pr.number}")
+ if not commit_in_place:
+ print("Creating PR")
+ g = Github(github_token)
+ gh_repo = g.get_repo(github_repository)
+ body = (
+ message
+ + "\n\nThis PR was created automatically using LLMs."
+ + f"\n\nIt uses the prompt file https://github.com/fastapi/fastapi/blob/master/docs/{language}/llm-prompt.md."
+ + "\n\nIn most cases, it's better to make PRs updating that file so that the LLM can do a better job generating the translations than suggesting changes in this PR."
+ )
+ pr = gh_repo.create_pull(
+ title=message, body=body, base="master", head=branch_name
+ )
+ print(f"Created PR: {pr.number}")
print("Finished")
diff --git a/scripts/translation_fixer.py b/scripts/translation_fixer.py
new file mode 100644
index 0000000000..3e1f42d514
--- /dev/null
+++ b/scripts/translation_fixer.py
@@ -0,0 +1,132 @@
+import os
+from collections.abc import Iterable
+from pathlib import Path
+from typing import Annotated
+
+import typer
+
+from scripts.doc_parsing_utils import check_translation
+
+non_translated_sections = (
+ f"reference{os.sep}",
+ "release-notes.md",
+ "fastapi-people.md",
+ "external-links.md",
+ "newsletter.md",
+ "management-tasks.md",
+ "management.md",
+ "contributing.md",
+)
+
+
+cli = typer.Typer()
+
+
+@cli.callback()
+def callback():
+ pass
+
+
+def iter_all_lang_paths(lang_path_root: Path) -> Iterable[Path]:
+ """
+ Iterate on the markdown files to translate in order of priority.
+ """
+
+ first_dirs = [
+ lang_path_root / "learn",
+ lang_path_root / "tutorial",
+ lang_path_root / "advanced",
+ lang_path_root / "about",
+ lang_path_root / "how-to",
+ ]
+ first_parent = lang_path_root
+ yield from first_parent.glob("*.md")
+ for dir_path in first_dirs:
+ yield from dir_path.rglob("*.md")
+ first_dirs_str = tuple(str(d) for d in first_dirs)
+ for path in lang_path_root.rglob("*.md"):
+ if str(path).startswith(first_dirs_str):
+ continue
+ if path.parent == first_parent:
+ continue
+ yield path
+
+
+def get_all_paths(lang: str):
+ res: list[str] = []
+ lang_docs_root = Path("docs") / lang / "docs"
+ for path in iter_all_lang_paths(lang_docs_root):
+ relpath = path.relative_to(lang_docs_root)
+ if not str(relpath).startswith(non_translated_sections):
+ res.append(str(relpath))
+ return res
+
+
+def process_one_page(path: Path) -> bool:
+ """
+ Fix one translated document by comparing it to the English version.
+
+ Returns True if processed successfully, False otherwise.
+ """
+
+ try:
+ lang_code = path.parts[1]
+ if lang_code == "en":
+ print(f"Skipping English document: {path}")
+ return True
+
+ en_doc_path = Path("docs") / "en" / Path(*path.parts[2:])
+
+ doc_lines = path.read_text(encoding="utf-8").splitlines()
+ en_doc_lines = en_doc_path.read_text(encoding="utf-8").splitlines()
+
+ doc_lines = check_translation(
+ doc_lines=doc_lines,
+ en_doc_lines=en_doc_lines,
+ lang_code=lang_code,
+ auto_fix=True,
+ path=str(path),
+ )
+
+ # Write back the fixed document
+ doc_lines.append("") # Ensure file ends with a newline
+ path.write_text("\n".join(doc_lines), encoding="utf-8")
+
+ except ValueError as e:
+ print(f"Error processing {path}: {e}")
+ return False
+ return True
+
+
+@cli.command()
+def fix_all(ctx: typer.Context, language: str):
+ docs = get_all_paths(language)
+
+ all_good = True
+ for page in docs:
+ doc_path = Path("docs") / language / "docs" / page
+ res = process_one_page(doc_path)
+ all_good = all_good and res
+
+ if not all_good:
+ raise typer.Exit(code=1)
+
+
+@cli.command()
+def fix_pages(
+ doc_paths: Annotated[
+ list[Path],
+ typer.Argument(help="List of paths to documents."),
+ ],
+):
+ all_good = True
+ for path in doc_paths:
+ res = process_one_page(path)
+ all_good = all_good and res
+
+ if not all_good:
+ raise typer.Exit(code=1)
+
+
+if __name__ == "__main__":
+ cli()
diff --git a/tests/benchmarks/__init__.py b/tests/benchmarks/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/benchmarks/test_general_performance.py b/tests/benchmarks/test_general_performance.py
new file mode 100644
index 0000000000..87add6d174
--- /dev/null
+++ b/tests/benchmarks/test_general_performance.py
@@ -0,0 +1,399 @@
+import json
+import sys
+from collections.abc import Iterator
+from typing import Annotated, Any
+
+import pytest
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+if "--codspeed" not in sys.argv:
+ pytest.skip(
+ "Benchmark tests are skipped by default; run with --codspeed.",
+ allow_module_level=True,
+ )
+
+LARGE_ITEMS: list[dict[str, Any]] = [
+ {
+ "id": i,
+ "name": f"item-{i}",
+ "values": list(range(25)),
+ "meta": {
+ "active": True,
+ "group": i % 10,
+ "tag": f"t{i % 5}",
+ },
+ }
+ for i in range(300)
+]
+
+LARGE_METADATA: dict[str, Any] = {
+ "source": "benchmark",
+ "version": 1,
+ "flags": {"a": True, "b": False, "c": True},
+ "notes": ["x" * 50, "y" * 50, "z" * 50],
+}
+
+LARGE_PAYLOAD: dict[str, Any] = {"items": LARGE_ITEMS, "metadata": LARGE_METADATA}
+
+
+def dep_a():
+ return 40
+
+
+def dep_b(a: Annotated[int, Depends(dep_a)]):
+ return a + 2
+
+
+class ItemIn(BaseModel):
+ name: str
+ value: int
+
+
+class ItemOut(BaseModel):
+ name: str
+ value: int
+ dep: int
+
+
+class LargeIn(BaseModel):
+ items: list[dict[str, Any]]
+ metadata: dict[str, Any]
+
+
+class LargeOut(BaseModel):
+ items: list[dict[str, Any]]
+ metadata: dict[str, Any]
+
+
+app = FastAPI()
+
+
+@app.post("/sync/validated", response_model=ItemOut)
+def sync_validated(item: ItemIn, dep: Annotated[int, Depends(dep_b)]):
+ return ItemOut(name=item.name, value=item.value, dep=dep)
+
+
+@app.get("/sync/dict-no-response-model")
+def sync_dict_no_response_model():
+ return {"name": "foo", "value": 123}
+
+
+@app.get("/sync/dict-with-response-model", response_model=ItemOut)
+def sync_dict_with_response_model(
+ dep: Annotated[int, Depends(dep_b)],
+):
+ return {"name": "foo", "value": 123, "dep": dep}
+
+
+@app.get("/sync/model-no-response-model")
+def sync_model_no_response_model(dep: Annotated[int, Depends(dep_b)]):
+ return ItemOut(name="foo", value=123, dep=dep)
+
+
+@app.get("/sync/model-with-response-model", response_model=ItemOut)
+def sync_model_with_response_model(dep: Annotated[int, Depends(dep_b)]):
+ return ItemOut(name="foo", value=123, dep=dep)
+
+
+@app.post("/async/validated", response_model=ItemOut)
+async def async_validated(
+ item: ItemIn,
+ dep: Annotated[int, Depends(dep_b)],
+):
+ return ItemOut(name=item.name, value=item.value, dep=dep)
+
+
+@app.post("/sync/large-receive")
+def sync_large_receive(payload: LargeIn):
+ return {"received": len(payload.items)}
+
+
+@app.post("/async/large-receive")
+async def async_large_receive(payload: LargeIn):
+ return {"received": len(payload.items)}
+
+
+@app.get("/sync/large-dict-no-response-model")
+def sync_large_dict_no_response_model():
+ return LARGE_PAYLOAD
+
+
+@app.get("/sync/large-dict-with-response-model", response_model=LargeOut)
+def sync_large_dict_with_response_model():
+ return LARGE_PAYLOAD
+
+
+@app.get("/sync/large-model-no-response-model")
+def sync_large_model_no_response_model():
+ return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA)
+
+
+@app.get("/sync/large-model-with-response-model", response_model=LargeOut)
+def sync_large_model_with_response_model():
+ return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA)
+
+
+@app.get("/async/large-dict-no-response-model")
+async def async_large_dict_no_response_model():
+ return LARGE_PAYLOAD
+
+
+@app.get("/async/large-dict-with-response-model", response_model=LargeOut)
+async def async_large_dict_with_response_model():
+ return LARGE_PAYLOAD
+
+
+@app.get("/async/large-model-no-response-model")
+async def async_large_model_no_response_model():
+ return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA)
+
+
+@app.get("/async/large-model-with-response-model", response_model=LargeOut)
+async def async_large_model_with_response_model():
+ return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA)
+
+
+@app.get("/async/dict-no-response-model")
+async def async_dict_no_response_model():
+ return {"name": "foo", "value": 123}
+
+
+@app.get("/async/dict-with-response-model", response_model=ItemOut)
+async def async_dict_with_response_model(
+ dep: Annotated[int, Depends(dep_b)],
+):
+ return {"name": "foo", "value": 123, "dep": dep}
+
+
+@app.get("/async/model-no-response-model")
+async def async_model_no_response_model(
+ dep: Annotated[int, Depends(dep_b)],
+):
+ return ItemOut(name="foo", value=123, dep=dep)
+
+
+@app.get("/async/model-with-response-model", response_model=ItemOut)
+async def async_model_with_response_model(
+ dep: Annotated[int, Depends(dep_b)],
+):
+ return ItemOut(name="foo", value=123, dep=dep)
+
+
+@pytest.fixture(scope="module")
+def client() -> Iterator[TestClient]:
+ with TestClient(app) as client:
+ yield client
+
+
+def _bench_get(benchmark, client: TestClient, path: str) -> tuple[int, bytes]:
+ warmup = client.get(path)
+ assert warmup.status_code == 200
+
+ def do_request() -> tuple[int, bytes]:
+ response = client.get(path)
+ return response.status_code, response.content
+
+ return benchmark(do_request)
+
+
+def _bench_post_json(
+ benchmark, client: TestClient, path: str, json: dict[str, Any]
+) -> tuple[int, bytes]:
+ warmup = client.post(path, json=json)
+ assert warmup.status_code == 200
+
+ def do_request() -> tuple[int, bytes]:
+ response = client.post(path, json=json)
+ return response.status_code, response.content
+
+ return benchmark(do_request)
+
+
+def test_sync_receiving_validated_pydantic_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_post_json(
+ benchmark,
+ client,
+ "/sync/validated",
+ json={"name": "foo", "value": 123},
+ )
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_sync_return_dict_without_response_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_get(benchmark, client, "/sync/dict-no-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123}'
+
+
+def test_sync_return_dict_with_response_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_get(benchmark, client, "/sync/dict-with-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_sync_return_model_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(benchmark, client, "/sync/model-no-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_sync_return_model_with_response_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_get(benchmark, client, "/sync/model-with-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_async_receiving_validated_pydantic_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_post_json(
+ benchmark, client, "/async/validated", json={"name": "foo", "value": 123}
+ )
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_async_return_dict_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(benchmark, client, "/async/dict-no-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123}'
+
+
+def test_async_return_dict_with_response_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_get(benchmark, client, "/async/dict-with-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_async_return_model_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(benchmark, client, "/async/model-no-response-model")
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_async_return_model_with_response_model(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/async/model-with-response-model"
+ )
+ assert status_code == 200
+ assert body == b'{"name":"foo","value":123,"dep":42}'
+
+
+def test_sync_receiving_large_payload(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_post_json(
+ benchmark,
+ client,
+ "/sync/large-receive",
+ json=LARGE_PAYLOAD,
+ )
+ assert status_code == 200
+ assert body == b'{"received":300}'
+
+
+def test_async_receiving_large_payload(benchmark, client: TestClient) -> None:
+ status_code, body = _bench_post_json(
+ benchmark,
+ client,
+ "/async/large-receive",
+ json=LARGE_PAYLOAD,
+ )
+ assert status_code == 200
+ assert body == b'{"received":300}'
+
+
+def _expected_large_payload_json_bytes() -> bytes:
+ return json.dumps(
+ LARGE_PAYLOAD,
+ ensure_ascii=False,
+ allow_nan=False,
+ separators=(",", ":"),
+ ).encode("utf-8")
+
+
+def test_sync_return_large_dict_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/sync/large-dict-no-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_sync_return_large_dict_with_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/sync/large-dict-with-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_sync_return_large_model_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/sync/large-model-no-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_sync_return_large_model_with_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/sync/large-model-with-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_async_return_large_dict_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/async/large-dict-no-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_async_return_large_dict_with_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/async/large-dict-with-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_async_return_large_model_without_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/async/large-model-no-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
+
+
+def test_async_return_large_model_with_response_model(
+ benchmark, client: TestClient
+) -> None:
+ status_code, body = _bench_get(
+ benchmark, client, "/async/large-model-with-response-model"
+ )
+ assert status_code == 200
+ assert body == _expected_large_payload_json_bytes()
diff --git a/tests/forward_reference_type.py b/tests/forward_reference_type.py
new file mode 100644
index 0000000000..52a0d4a708
--- /dev/null
+++ b/tests/forward_reference_type.py
@@ -0,0 +1,9 @@
+from pydantic import BaseModel
+
+
+def forwardref_method(input: "ForwardRefModel") -> "ForwardRefModel":
+ return ForwardRefModel(x=input.x + 1)
+
+
+class ForwardRefModel(BaseModel):
+ x: int = 0
diff --git a/tests/main.py b/tests/main.py
index 2f1d617115..7edb16c615 100644
--- a/tests/main.py
+++ b/tests/main.py
@@ -1,5 +1,5 @@
import http
-from typing import FrozenSet, List, Optional
+from typing import Optional
from fastapi import FastAPI, Path, Query
@@ -195,15 +195,15 @@ def get_enum_status_code():
@app.get("/query/frozenset")
-def get_query_type_frozenset(query: FrozenSet[int] = Query(...)):
+def get_query_type_frozenset(query: frozenset[int] = Query(...)):
return ",".join(map(str, sorted(query)))
@app.get("/query/list")
-def get_query_list(device_ids: List[int] = Query()) -> List[int]:
+def get_query_list(device_ids: list[int] = Query()) -> list[int]:
return device_ids
@app.get("/query/list-default")
-def get_query_list_default(device_ids: List[int] = Query(default=[])) -> List[int]:
+def get_query_list_default(device_ids: list[int] = Query(default=[])) -> list[int]:
return device_ids
diff --git a/tests/test_additional_properties.py b/tests/test_additional_properties.py
index be14d10edc..2622366400 100644
--- a/tests/test_additional_properties.py
+++ b/tests/test_additional_properties.py
@@ -1,5 +1,3 @@
-from typing import Dict
-
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -8,7 +6,7 @@ app = FastAPI()
class Items(BaseModel):
- items: Dict[str, int]
+ items: dict[str, int]
@app.post("/foo")
diff --git a/tests/test_additional_properties_bool.py b/tests/test_additional_properties_bool.py
index de59e48ce7..063297a3f2 100644
--- a/tests/test_additional_properties_bool.py
+++ b/tests/test_additional_properties_bool.py
@@ -1,19 +1,12 @@
from typing import Union
-from dirty_equals import IsDict
from fastapi import FastAPI
-from fastapi._compat import PYDANTIC_V2
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
class FooBaseModel(BaseModel):
- if PYDANTIC_V2:
- model_config = ConfigDict(extra="forbid")
- else:
-
- class Config:
- extra = "forbid"
+ model_config = ConfigDict(extra="forbid")
class Foo(FooBaseModel):
@@ -58,19 +51,13 @@ def test_openapi_schema():
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "anyOf": [
- {"$ref": "#/components/schemas/Foo"},
- {"type": "null"},
- ],
- "title": "Foo",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"$ref": "#/components/schemas/Foo"}
- )
+ "schema": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Foo"},
+ {"type": "null"},
+ ],
+ "title": "Foo",
+ }
}
}
},
diff --git a/tests/test_additional_responses_custom_model_in_callback.py b/tests/test_additional_responses_custom_model_in_callback.py
index 2ad5754551..376d7714ed 100644
--- a/tests/test_additional_responses_custom_model_in_callback.py
+++ b/tests/test_additional_responses_custom_model_in_callback.py
@@ -1,6 +1,6 @@
-from dirty_equals import IsDict
from fastapi import APIRouter, FastAPI
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from pydantic import BaseModel, HttpUrl
from starlette.responses import JSONResponse
@@ -32,121 +32,114 @@ client = TestClient(app)
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/": {
- "post": {
- "summary": "Main Route",
- "operationId": "main_route__post",
- "parameters": [
- {
- "required": True,
- "schema": IsDict(
- {
- "title": "Callback Url",
- "minLength": 1,
- "type": "string",
- "format": "uri",
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict(
- {
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Main Route",
+ "operationId": "main_route__post",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
"title": "Callback Url",
"maxLength": 2083,
"minLength": 1,
"type": "string",
"format": "uri",
- }
- ),
- "name": "callback_url",
- "in": "query",
- }
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
+ },
+ "name": "callback_url",
+ "in": "query",
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
},
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
+ "callbacks": {
+ "callback_route": {
+ "{$callback_url}/callback/": {
+ "get": {
+ "summary": "Callback Route",
+ "operationId": "callback_route__callback_url__callback__get",
+ "responses": {
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CustomModel"
+ }
+ }
+ },
+ "description": "Bad Request",
+ },
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {"schema": {}}
+ },
+ },
+ },
}
}
- },
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "CustomModel": {
+ "title": "CustomModel",
+ "required": ["a"],
+ "type": "object",
+ "properties": {"a": {"title": "A", "type": "integer"}},
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
},
},
- "callbacks": {
- "callback_route": {
- "{$callback_url}/callback/": {
- "get": {
- "summary": "Callback Route",
- "operationId": "callback_route__callback_url__callback__get",
- "responses": {
- "400": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/CustomModel"
- }
- }
- },
- "description": "Bad Request",
- },
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {"schema": {}}
- },
- },
- },
- }
- }
- }
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
},
}
- }
- },
- "components": {
- "schemas": {
- "CustomModel": {
- "title": "CustomModel",
- "required": ["a"],
- "type": "object",
- "properties": {"a": {"title": "A", "type": "integer"}},
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_additional_responses_custom_validationerror.py b/tests/test_additional_responses_custom_validationerror.py
index 9fec5c96d4..8724e5ecb8 100644
--- a/tests/test_additional_responses_custom_validationerror.py
+++ b/tests/test_additional_responses_custom_validationerror.py
@@ -1,5 +1,3 @@
-import typing
-
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
@@ -18,7 +16,7 @@ class Error(BaseModel):
class JsonApiError(BaseModel):
- errors: typing.List[Error]
+ errors: list[Error]
@app.get(
diff --git a/tests/test_additional_responses_response_class.py b/tests/test_additional_responses_response_class.py
index 68753561c9..fecc3ee16b 100644
--- a/tests/test_additional_responses_response_class.py
+++ b/tests/test_additional_responses_response_class.py
@@ -1,5 +1,3 @@
-import typing
-
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
@@ -18,7 +16,7 @@ class Error(BaseModel):
class JsonApiError(BaseModel):
- errors: typing.List[Error]
+ errors: list[Error]
@app.get(
diff --git a/tests/test_allow_inf_nan_in_enforcing.py b/tests/test_allow_inf_nan_in_enforcing.py
index 9e855fdf81..083a024af0 100644
--- a/tests/test_allow_inf_nan_in_enforcing.py
+++ b/tests/test_allow_inf_nan_in_enforcing.py
@@ -1,7 +1,8 @@
+from typing import Annotated
+
import pytest
from fastapi import Body, FastAPI, Query
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
app = FastAPI()
diff --git a/tests/test_ambiguous_params.py b/tests/test_ambiguous_params.py
index 8a31442eb0..44d49b7818 100644
--- a/tests/test_ambiguous_params.py
+++ b/tests/test_ambiguous_params.py
@@ -1,9 +1,9 @@
+from typing import Annotated
+
import pytest
from fastapi import Depends, FastAPI, Path
from fastapi.param_functions import Query
from fastapi.testclient import TestClient
-from fastapi.utils import PYDANTIC_V2
-from typing_extensions import Annotated
app = FastAPI()
@@ -70,6 +70,5 @@ def test_multiple_annotations():
response = client.get("/multi-query", params={"foo": "123"})
assert response.status_code == 422
- if PYDANTIC_V2:
- response = client.get("/multi-query", params={"foo": "1"})
- assert response.status_code == 422
+ response = client.get("/multi-query", params={"foo": "1"})
+ assert response.status_code == 422
diff --git a/tests/test_annotated.py b/tests/test_annotated.py
index 473d33e52c..39f6f83b29 100644
--- a/tests/test_annotated.py
+++ b/tests/test_annotated.py
@@ -1,8 +1,8 @@
+from typing import Annotated
+
import pytest
-from dirty_equals import IsDict
from fastapi import APIRouter, FastAPI, Query
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
app = FastAPI()
@@ -31,44 +31,23 @@ client = TestClient(app)
foo_is_missing = {
"detail": [
- IsDict(
- {
- "loc": ["query", "foo"],
- "msg": "Field required",
- "type": "missing",
- "input": None,
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict(
- {
- "loc": ["query", "foo"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- )
+ {
+ "loc": ["query", "foo"],
+ "msg": "Field required",
+ "type": "missing",
+ "input": None,
+ }
]
}
foo_is_short = {
"detail": [
- IsDict(
- {
- "ctx": {"min_length": 1},
- "loc": ["query", "foo"],
- "msg": "String should have at least 1 character",
- "type": "string_too_short",
- "input": "",
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict(
- {
- "ctx": {"limit_value": 1},
- "loc": ["query", "foo"],
- "msg": "ensure this value has at least 1 characters",
- "type": "value_error.any_str.min_length",
- }
- )
+ {
+ "ctx": {"min_length": 1},
+ "loc": ["query", "foo"],
+ "msg": "String should have at least 1 character",
+ "type": "string_too_short",
+ "input": "",
+ }
]
}
diff --git a/tests/test_application.py b/tests/test_application.py
index 8f1b0a18d3..001586ff78 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -1,5 +1,4 @@
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from .main import app
@@ -274,14 +273,10 @@ def test_openapi_schema():
"name": "item_id",
"in": "path",
"required": True,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Item Id",
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict({"title": "Item Id", "type": "string"}),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Item Id",
+ },
}
],
}
@@ -984,14 +979,10 @@ def test_openapi_schema():
"name": "query",
"in": "query",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "integer"}, {"type": "null"}],
- "title": "Query",
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict({"title": "Query", "type": "integer"}),
+ "schema": {
+ "anyOf": [{"type": "integer"}, {"type": "null"}],
+ "title": "Query",
+ },
}
],
}
diff --git a/tests/test_arbitrary_types.py b/tests/test_arbitrary_types.py
new file mode 100644
index 0000000000..481acc3bfc
--- /dev/null
+++ b/tests/test_arbitrary_types.py
@@ -0,0 +1,135 @@
+from typing import Annotated
+
+import pytest
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from pydantic import (
+ BaseModel,
+ ConfigDict,
+ PlainSerializer,
+ TypeAdapter,
+ WithJsonSchema,
+ )
+
+ class FakeNumpyArray:
+ def __init__(self):
+ self.data = [1.0, 2.0, 3.0]
+
+ FakeNumpyArrayPydantic = Annotated[
+ FakeNumpyArray,
+ WithJsonSchema(TypeAdapter(list[float]).json_schema()),
+ PlainSerializer(lambda v: v.data),
+ ]
+
+ class MyModel(BaseModel):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+ custom_field: FakeNumpyArrayPydantic
+
+ app = FastAPI()
+
+ @app.get("/")
+ def test() -> MyModel:
+ return MyModel(custom_field=FakeNumpyArray())
+
+ client = TestClient(app)
+ return client
+
+
+def test_get(client: TestClient):
+ response = client.get("/")
+ assert response.json() == {"custom_field": [1.0, 2.0, 3.0]}
+
+
+def test_typeadapter():
+ # This test is only to confirm that Pydantic alone is working as expected
+ from pydantic import (
+ BaseModel,
+ ConfigDict,
+ PlainSerializer,
+ TypeAdapter,
+ WithJsonSchema,
+ )
+
+ class FakeNumpyArray:
+ def __init__(self):
+ self.data = [1.0, 2.0, 3.0]
+
+ FakeNumpyArrayPydantic = Annotated[
+ FakeNumpyArray,
+ WithJsonSchema(TypeAdapter(list[float]).json_schema()),
+ PlainSerializer(lambda v: v.data),
+ ]
+
+ class MyModel(BaseModel):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+ custom_field: FakeNumpyArrayPydantic
+
+ ta = TypeAdapter(MyModel)
+ assert ta.dump_python(MyModel(custom_field=FakeNumpyArray())) == {
+ "custom_field": [1.0, 2.0, 3.0]
+ }
+ assert ta.json_schema() == snapshot(
+ {
+ "properties": {
+ "custom_field": {
+ "items": {"type": "number"},
+ "title": "Custom Field",
+ "type": "array",
+ }
+ },
+ "required": ["custom_field"],
+ "title": "MyModel",
+ "type": "object",
+ }
+ )
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("openapi.json")
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "get": {
+ "summary": "Test",
+ "operationId": "test__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MyModel"
+ }
+ }
+ },
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "MyModel": {
+ "properties": {
+ "custom_field": {
+ "items": {"type": "number"},
+ "type": "array",
+ "title": "Custom Field",
+ }
+ },
+ "type": "object",
+ "required": ["custom_field"],
+ "title": "MyModel",
+ }
+ }
+ },
+ }
+ )
diff --git a/tests/test_compat.py b/tests/test_compat.py
index 0184c9a2ee..0b5600f8f5 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -1,23 +1,18 @@
-from typing import Any, Dict, List, Union
+from typing import Union
from fastapi import FastAPI, UploadFile
from fastapi._compat import (
Undefined,
- _get_model_config,
- get_cached_model_fields,
- is_scalar_field,
is_uploadfile_sequence_annotation,
- may_v1,
)
from fastapi._compat.shared import is_bytes_sequence_annotation
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
from pydantic.fields import FieldInfo
-from .utils import needs_py_lt_314, needs_pydanticv2
+from .utils import needs_py310
-@needs_pydanticv2
def test_model_field_default_required():
from fastapi._compat import v2
@@ -27,41 +22,11 @@ def test_model_field_default_required():
assert field.default is Undefined
-@needs_py_lt_314
-def test_v1_plain_validator_function():
- from fastapi._compat import v1
-
- # For coverage
- def func(v): # pragma: no cover
- return v
-
- result = v1.with_info_plain_validator_function(func)
- assert result == {}
-
-
-def test_is_model_field():
- # For coverage
- from fastapi._compat import _is_model_field
-
- assert not _is_model_field(str)
-
-
-@needs_pydanticv2
-def test_get_model_config():
- # For coverage in Pydantic v2
- class Foo(BaseModel):
- model_config = ConfigDict(from_attributes=True)
-
- foo = Foo()
- config = _get_model_config(foo)
- assert config == {"from_attributes": True}
-
-
def test_complex():
app = FastAPI()
@app.post("/")
- def foo(foo: Union[str, List[int]]):
+ def foo(foo: Union[str, list[int]]):
return foo
client = TestClient(app)
@@ -75,7 +40,6 @@ def test_complex():
assert response2.json() == [1, 2]
-@needs_pydanticv2
def test_propagates_pydantic2_model_config():
app = FastAPI()
@@ -95,7 +59,7 @@ def test_propagates_pydantic2_model_config():
embedded_model: EmbeddedModel = EmbeddedModel()
@app.post("/")
- def foo(req: Model) -> Dict[str, Union[str, None]]:
+ def foo(req: Model) -> dict[str, Union[str, None]]:
return {
"value": req.value or None,
"embedded_value": req.embedded_model.value or None,
@@ -125,7 +89,7 @@ def test_is_bytes_sequence_annotation_union():
# TODO: in theory this would allow declaring types that could be lists of bytes
# to be read from files and other types, but I'm not even sure it's a good idea
# to support it as a first class "feature"
- assert is_bytes_sequence_annotation(Union[List[str], List[bytes]])
+ assert is_bytes_sequence_annotation(Union[list[str], list[bytes]])
def test_is_uploadfile_sequence_annotation():
@@ -133,34 +97,38 @@ def test_is_uploadfile_sequence_annotation():
# TODO: in theory this would allow declaring types that could be lists of UploadFile
# and other types, but I'm not even sure it's a good idea to support it as a first
# class "feature"
- assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]])
+ assert is_uploadfile_sequence_annotation(Union[list[str], list[UploadFile]])
-@needs_py_lt_314
-def test_is_pv1_scalar_field():
- from fastapi._compat import v1
+def test_serialize_sequence_value_with_optional_list():
+ """Test that serialize_sequence_value handles optional lists correctly."""
+ from fastapi._compat import v2
- # For coverage
- class Model(v1.BaseModel):
- foo: Union[str, Dict[str, Any]]
-
- fields = v1.get_model_fields(Model)
- assert not is_scalar_field(fields[0])
+ field_info = FieldInfo(annotation=Union[list[str], None])
+ field = v2.ModelField(name="items", field_info=field_info)
+ result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"])
+ assert result == ["a", "b", "c"]
+ assert isinstance(result, list)
-@needs_py_lt_314
-def test_get_model_fields_cached():
- from fastapi._compat import v1
+@needs_py310
+def test_serialize_sequence_value_with_optional_list_pipe_union():
+ """Test that serialize_sequence_value handles optional lists correctly (with new syntax)."""
+ from fastapi._compat import v2
- class Model(may_v1.BaseModel):
- foo: str
+ field_info = FieldInfo(annotation=list[str] | None)
+ field = v2.ModelField(name="items", field_info=field_info)
+ result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"])
+ assert result == ["a", "b", "c"]
+ assert isinstance(result, list)
- non_cached_fields = v1.get_model_fields(Model)
- non_cached_fields2 = v1.get_model_fields(Model)
- cached_fields = get_cached_model_fields(Model)
- cached_fields2 = get_cached_model_fields(Model)
- for f1, f2 in zip(cached_fields, cached_fields2):
- assert f1 is f2
- assert non_cached_fields is not non_cached_fields2
- assert cached_fields is cached_fields2
+def test_serialize_sequence_value_with_none_first_in_union():
+ """Test that serialize_sequence_value handles Union[None, List[...]] correctly."""
+ from fastapi._compat import v2
+
+ field_info = FieldInfo(annotation=Union[None, list[str]])
+ field = v2.ModelField(name="items", field_info=field_info)
+ result = v2.serialize_sequence_value(field=field, value=["x", "y"])
+ assert result == ["x", "y"]
+ assert isinstance(result, list)
diff --git a/tests/test_compat_params_v1.py b/tests/test_compat_params_v1.py
deleted file mode 100644
index 7064761cb5..0000000000
--- a/tests/test_compat_params_v1.py
+++ /dev/null
@@ -1,1122 +0,0 @@
-import sys
-from typing import List, Optional
-
-import pytest
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from fastapi import FastAPI
-from fastapi._compat.v1 import BaseModel
-from fastapi.temp_pydantic_v1_params import (
- Body,
- Cookie,
- File,
- Form,
- Header,
- Path,
- Query,
-)
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-from typing_extensions import Annotated
-
-
-class Item(BaseModel):
- name: str
- price: float
- description: Optional[str] = None
-
-
-app = FastAPI()
-
-
-@app.get("/items/{item_id}")
-def get_item_with_path(
- item_id: Annotated[int, Path(title="The ID of the item", ge=1, le=1000)],
-):
- return {"item_id": item_id}
-
-
-@app.get("/items/")
-def get_items_with_query(
- q: Annotated[
- Optional[str], Query(min_length=3, max_length=50, pattern="^[a-zA-Z0-9 ]+$")
- ] = None,
- skip: Annotated[int, Query(ge=0)] = 0,
- limit: Annotated[int, Query(ge=1, le=100, examples=[5])] = 10,
-):
- return {"q": q, "skip": skip, "limit": limit}
-
-
-@app.get("/users/")
-def get_user_with_header(
- x_custom: Annotated[Optional[str], Header()] = None,
- x_token: Annotated[Optional[str], Header(convert_underscores=True)] = None,
-):
- return {"x_custom": x_custom, "x_token": x_token}
-
-
-@app.get("/cookies/")
-def get_cookies(
- session_id: Annotated[Optional[str], Cookie()] = None,
- tracking_id: Annotated[Optional[str], Cookie(min_length=10)] = None,
-):
- return {"session_id": session_id, "tracking_id": tracking_id}
-
-
-@app.post("/items/")
-def create_item(
- item: Annotated[
- Item,
- Body(examples=[{"name": "Foo", "price": 35.4, "description": "The Foo item"}]),
- ],
-):
- return {"item": item}
-
-
-@app.post("/items-embed/")
-def create_item_embed(
- item: Annotated[Item, Body(embed=True)],
-):
- return {"item": item}
-
-
-@app.put("/items/{item_id}")
-def update_item(
- item_id: Annotated[int, Path(ge=1)],
- item: Annotated[Item, Body()],
- importance: Annotated[int, Body(gt=0, le=10)],
-):
- return {"item": item, "importance": importance}
-
-
-@app.post("/form-data/")
-def submit_form(
- username: Annotated[str, Form(min_length=3, max_length=50)],
- password: Annotated[str, Form(min_length=8)],
- email: Annotated[Optional[str], Form()] = None,
-):
- return {"username": username, "password": password, "email": email}
-
-
-@app.post("/upload/")
-def upload_file(
- file: Annotated[bytes, File()],
- description: Annotated[Optional[str], Form()] = None,
-):
- return {"file_size": len(file), "description": description}
-
-
-@app.post("/upload-multiple/")
-def upload_multiple_files(
- files: Annotated[List[bytes], File()],
- note: Annotated[str, Form()] = "",
-):
- return {
- "file_count": len(files),
- "total_size": sum(len(f) for f in files),
- "note": note,
- }
-
-
-client = TestClient(app)
-
-
-# Path parameter tests
-def test_path_param_valid():
- response = client.get("/items/50")
- assert response.status_code == 200
- assert response.json() == {"item_id": 50}
-
-
-def test_path_param_too_large():
- response = client.get("/items/1001")
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["path", "item_id"]
-
-
-def test_path_param_too_small():
- response = client.get("/items/0")
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["path", "item_id"]
-
-
-# Query parameter tests
-def test_query_params_valid():
- response = client.get("/items/?q=test search&skip=5&limit=20")
- assert response.status_code == 200
- assert response.json() == {"q": "test search", "skip": 5, "limit": 20}
-
-
-def test_query_params_defaults():
- response = client.get("/items/")
- assert response.status_code == 200
- assert response.json() == {"q": None, "skip": 0, "limit": 10}
-
-
-def test_query_param_too_short():
- response = client.get("/items/?q=ab")
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["query", "q"]
-
-
-def test_query_param_invalid_pattern():
- response = client.get("/items/?q=test@#$")
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["query", "q"]
-
-
-def test_query_param_limit_too_large():
- response = client.get("/items/?limit=101")
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["query", "limit"]
-
-
-# Header parameter tests
-def test_header_params():
- response = client.get(
- "/users/",
- headers={"X-Custom": "Plumbus", "X-Token": "secret-token"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "x_custom": "Plumbus",
- "x_token": "secret-token",
- }
-
-
-def test_header_underscore_conversion():
- response = client.get(
- "/users/",
- headers={"x-token": "secret-token-with-dash"},
- )
- assert response.status_code == 200
- assert response.json()["x_token"] == "secret-token-with-dash"
-
-
-def test_header_params_none():
- response = client.get("/users/")
- assert response.status_code == 200
- assert response.json() == {"x_custom": None, "x_token": None}
-
-
-# Cookie parameter tests
-def test_cookie_params():
- with TestClient(app) as client:
- client.cookies.set("session_id", "abc123")
- client.cookies.set("tracking_id", "1234567890abcdef")
- response = client.get("/cookies/")
- assert response.status_code == 200
- assert response.json() == {
- "session_id": "abc123",
- "tracking_id": "1234567890abcdef",
- }
-
-
-def test_cookie_tracking_id_too_short():
- with TestClient(app) as client:
- client.cookies.set("tracking_id", "short")
- response = client.get("/cookies/")
- assert response.status_code == 422
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["cookie", "tracking_id"],
- "msg": "ensure this value has at least 10 characters",
- "type": "value_error.any_str.min_length",
- "ctx": {"limit_value": 10},
- }
- ]
- }
- )
-
-
-def test_cookie_params_none():
- response = client.get("/cookies/")
- assert response.status_code == 200
- assert response.json() == {"session_id": None, "tracking_id": None}
-
-
-# Body parameter tests
-def test_body_param():
- response = client.post(
- "/items/",
- json={"name": "Test Item", "price": 29.99, "description": "A test item"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "item": {
- "name": "Test Item",
- "price": 29.99,
- "description": "A test item",
- }
- }
-
-
-def test_body_param_minimal():
- response = client.post(
- "/items/",
- json={"name": "Minimal", "price": 9.99},
- )
- assert response.status_code == 200
- assert response.json() == {
- "item": {"name": "Minimal", "price": 9.99, "description": None}
- }
-
-
-def test_body_param_missing_required():
- response = client.post(
- "/items/",
- json={"name": "Incomplete"},
- )
- assert response.status_code == 422
- error = response.json()["detail"][0]
- assert error["loc"] == ["body", "price"]
-
-
-def test_body_embed():
- response = client.post(
- "/items-embed/",
- json={"item": {"name": "Embedded", "price": 15.0}},
- )
- assert response.status_code == 200
- assert response.json() == {
- "item": {"name": "Embedded", "price": 15.0, "description": None}
- }
-
-
-def test_body_embed_wrong_structure():
- response = client.post(
- "/items-embed/",
- json={"name": "Not Embedded", "price": 15.0},
- )
- assert response.status_code == 422
-
-
-# Multiple body parameters test
-def test_multiple_body_params():
- response = client.put(
- "/items/5",
- json={
- "item": {"name": "Updated Item", "price": 49.99},
- "importance": 8,
- },
- )
- assert response.status_code == 200
- assert response.json() == snapshot(
- {
- "item": {"name": "Updated Item", "price": 49.99, "description": None},
- "importance": 8,
- }
- )
-
-
-def test_multiple_body_params_importance_too_large():
- response = client.put(
- "/items/5",
- json={
- "item": {"name": "Item", "price": 10.0},
- "importance": 11,
- },
- )
- assert response.status_code == 422
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "importance"],
- "msg": "ensure this value is less than or equal to 10",
- "type": "value_error.number.not_le",
- "ctx": {"limit_value": 10},
- }
- ]
- }
- )
-
-
-def test_multiple_body_params_importance_too_small():
- response = client.put(
- "/items/5",
- json={
- "item": {"name": "Item", "price": 10.0},
- "importance": 0,
- },
- )
- assert response.status_code == 422
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "importance"],
- "msg": "ensure this value is greater than 0",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 0},
- }
- ]
- }
- )
-
-
-# Form parameter tests
-def test_form_data_valid():
- response = client.post(
- "/form-data/",
- data={
- "username": "testuser",
- "password": "password123",
- "email": "test@example.com",
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "username": "testuser",
- "password": "password123",
- "email": "test@example.com",
- }
-
-
-def test_form_data_optional_field():
- response = client.post(
- "/form-data/",
- data={"username": "testuser", "password": "password123"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "username": "testuser",
- "password": "password123",
- "email": None,
- }
-
-
-def test_form_data_username_too_short():
- response = client.post(
- "/form-data/",
- data={"username": "ab", "password": "password123"},
- )
- assert response.status_code == 422
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "username"],
- "msg": "ensure this value has at least 3 characters",
- "type": "value_error.any_str.min_length",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
-
-
-def test_form_data_password_too_short():
- response = client.post(
- "/form-data/",
- data={"username": "testuser", "password": "short"},
- )
- assert response.status_code == 422
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "password"],
- "msg": "ensure this value has at least 8 characters",
- "type": "value_error.any_str.min_length",
- "ctx": {"limit_value": 8},
- }
- ]
- }
- )
-
-
-# File upload tests
-def test_upload_file():
- response = client.post(
- "/upload/",
- files={"file": ("test.txt", b"Hello, World!", "text/plain")},
- data={"description": "A test file"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "file_size": 13,
- "description": "A test file",
- }
-
-
-def test_upload_file_without_description():
- response = client.post(
- "/upload/",
- files={"file": ("test.txt", b"Hello!", "text/plain")},
- )
- assert response.status_code == 200
- assert response.json() == {
- "file_size": 6,
- "description": None,
- }
-
-
-def test_upload_multiple_files():
- response = client.post(
- "/upload-multiple/",
- files=[
- ("files", ("file1.txt", b"Content 1", "text/plain")),
- ("files", ("file2.txt", b"Content 2", "text/plain")),
- ("files", ("file3.txt", b"Content 3", "text/plain")),
- ],
- data={"note": "Multiple files uploaded"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "file_count": 3,
- "total_size": 27,
- "note": "Multiple files uploaded",
- }
-
-
-def test_upload_multiple_files_empty_note():
- response = client.post(
- "/upload-multiple/",
- files=[
- ("files", ("file1.txt", b"Test", "text/plain")),
- ],
- )
- assert response.status_code == 200
- assert response.json()["file_count"] == 1
- assert response.json()["note"] == ""
-
-
-# __repr__ tests
-def test_query_repr():
- query_param = Query(default=None, min_length=3)
- assert repr(query_param) == "Query(None)"
-
-
-def test_body_repr():
- body_param = Body(default=None)
- assert repr(body_param) == "Body(None)"
-
-
-# Deprecation warning tests for regex parameter
-def test_query_regex_deprecation_warning():
- with pytest.warns(DeprecationWarning, match="`regex` has been deprecated"):
- Query(regex="^test$")
-
-
-def test_body_regex_deprecation_warning():
- with pytest.warns(DeprecationWarning, match="`regex` has been deprecated"):
- Body(regex="^test$")
-
-
-# Deprecation warning tests for example parameter
-def test_query_example_deprecation_warning():
- with pytest.warns(DeprecationWarning, match="`example` has been deprecated"):
- Query(example="test example")
-
-
-def test_body_example_deprecation_warning():
- with pytest.warns(DeprecationWarning, match="`example` has been deprecated"):
- Body(example={"test": "example"})
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "get": {
- "summary": "Get Item With Path",
- "operationId": "get_item_with_path_items__item_id__get",
- "parameters": [
- {
- "name": "item_id",
- "in": "path",
- "required": True,
- "schema": {
- "title": "The ID of the item",
- "minimum": 1,
- "maximum": 1000,
- "type": "integer",
- },
- }
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- },
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "name": "item_id",
- "in": "path",
- "required": True,
- "schema": {
- "title": "Item Id",
- "minimum": 1,
- "type": "integer",
- },
- }
- ],
- "requestBody": {
- "required": True,
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v1=snapshot(
- {
- "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
- }
- ),
- v2=snapshot(
- {
- "title": "Body",
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
- }
- ],
- }
- ),
- ),
- }
- },
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- },
- },
- "/items/": {
- "get": {
- "summary": "Get Items With Query",
- "operationId": "get_items_with_query_items__get",
- "parameters": [
- {
- "name": "q",
- "in": "query",
- "required": False,
- "schema": {
- "title": "Q",
- "maxLength": 50,
- "minLength": 3,
- "pattern": "^[a-zA-Z0-9 ]+$",
- "type": "string",
- },
- },
- {
- "name": "skip",
- "in": "query",
- "required": False,
- "schema": {
- "title": "Skip",
- "default": 0,
- "minimum": 0,
- "type": "integer",
- },
- },
- {
- "name": "limit",
- "in": "query",
- "required": False,
- "schema": {
- "title": "Limit",
- "default": 10,
- "minimum": 1,
- "maximum": 100,
- "examples": [5],
- "type": "integer",
- },
- },
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- },
- "post": {
- "summary": "Create Item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "required": True,
- "content": {
- "application/json": {
- "schema": {
- "title": "Item",
- "examples": [
- {
- "name": "Foo",
- "price": 35.4,
- "description": "The Foo item",
- }
- ],
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- }
- }
- },
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- },
- },
- "/users/": {
- "get": {
- "summary": "Get User With Header",
- "operationId": "get_user_with_header_users__get",
- "parameters": [
- {
- "name": "x-custom",
- "in": "header",
- "required": False,
- "schema": {"title": "X-Custom", "type": "string"},
- },
- {
- "name": "x-token",
- "in": "header",
- "required": False,
- "schema": {"title": "X-Token", "type": "string"},
- },
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/cookies/": {
- "get": {
- "summary": "Get Cookies",
- "operationId": "get_cookies_cookies__get",
- "parameters": [
- {
- "name": "session_id",
- "in": "cookie",
- "required": False,
- "schema": {"title": "Session Id", "type": "string"},
- },
- {
- "name": "tracking_id",
- "in": "cookie",
- "required": False,
- "schema": {
- "title": "Tracking Id",
- "minLength": 10,
- "type": "string",
- },
- },
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/items-embed/": {
- "post": {
- "summary": "Create Item Embed",
- "operationId": "create_item_embed_items_embed__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v1=snapshot(
- {
- "$ref": "#/components/schemas/Body_create_item_embed_items_embed__post"
- }
- ),
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_item_embed_items_embed__post"
- }
- ],
- "title": "Body",
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/form-data/": {
- "post": {
- "summary": "Submit Form",
- "operationId": "submit_form_form_data__post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": pydantic_snapshot(
- v1=snapshot(
- {
- "$ref": "#/components/schemas/Body_submit_form_form_data__post"
- }
- ),
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_submit_form_form_data__post"
- }
- ],
- "title": "Body",
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/upload/": {
- "post": {
- "summary": "Upload File",
- "operationId": "upload_file_upload__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": pydantic_snapshot(
- v1=snapshot(
- {
- "$ref": "#/components/schemas/Body_upload_file_upload__post"
- }
- ),
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_upload_file_upload__post"
- }
- ],
- "title": "Body",
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/upload-multiple/": {
- "post": {
- "summary": "Upload Multiple Files",
- "operationId": "upload_multiple_files_upload_multiple__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": pydantic_snapshot(
- v1=snapshot(
- {
- "$ref": "#/components/schemas/Body_upload_multiple_files_upload_multiple__post"
- }
- ),
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_upload_multiple_files_upload_multiple__post"
- }
- ],
- "title": "Body",
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "Body_create_item_embed_items_embed__post": {
- "properties": pydantic_snapshot(
- v1=snapshot(
- {"item": {"$ref": "#/components/schemas/Item"}}
- ),
- v2=snapshot(
- {
- "item": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- }
- }
- ),
- ),
- "type": "object",
- "required": ["item"],
- "title": "Body_create_item_embed_items_embed__post",
- },
- "Body_submit_form_form_data__post": {
- "properties": {
- "username": {
- "type": "string",
- "maxLength": 50,
- "minLength": 3,
- "title": "Username",
- },
- "password": {
- "type": "string",
- "minLength": 8,
- "title": "Password",
- },
- "email": {"type": "string", "title": "Email"},
- },
- "type": "object",
- "required": ["username", "password"],
- "title": "Body_submit_form_form_data__post",
- },
- "Body_update_item_items__item_id__put": {
- "properties": {
- "item": pydantic_snapshot(
- v1=snapshot({"$ref": "#/components/schemas/Item"}),
- v2=snapshot(
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- }
- ),
- ),
- "importance": {
- "type": "integer",
- "maximum": 10.0,
- "exclusiveMinimum": 0.0,
- "title": "Importance",
- },
- },
- "type": "object",
- "required": ["item", "importance"],
- "title": "Body_update_item_items__item_id__put",
- },
- "Body_upload_file_upload__post": {
- "properties": {
- "file": {
- "type": "string",
- "format": "binary",
- "title": "File",
- },
- "description": {"type": "string", "title": "Description"},
- },
- "type": "object",
- "required": ["file"],
- "title": "Body_upload_file_upload__post",
- },
- "Body_upload_multiple_files_upload_multiple__post": {
- "properties": {
- "files": {
- "items": {"type": "string", "format": "binary"},
- "type": "array",
- "title": "Files",
- },
- "note": {"type": "string", "title": "Note", "default": ""},
- },
- "type": "object",
- "required": ["files"],
- "title": "Body_upload_multiple_files_upload_multiple__post",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "price": {"type": "number", "title": "Price"},
- "description": {"type": "string", "title": "Description"},
- },
- "type": "object",
- "required": ["name", "price"],
- "title": "Item",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py
index a1b4121688..e7f969f7cf 100644
--- a/tests/test_computed_fields.py
+++ b/tests/test_computed_fields.py
@@ -2,12 +2,11 @@ import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
-from .utils import needs_pydanticv2
-
@pytest.fixture(name="client")
-def get_client():
- app = FastAPI()
+def get_client(request):
+ separate_input_output_schemas = request.param
+ app = FastAPI(separate_input_output_schemas=separate_input_output_schemas)
from pydantic import BaseModel, computed_field
@@ -32,15 +31,15 @@ def get_client():
return client
+@pytest.mark.parametrize("client", [True, False], indirect=True)
@pytest.mark.parametrize("path", ["/", "/responses"])
-@needs_pydanticv2
def test_get(client: TestClient, path: str):
response = client.get(path)
assert response.status_code == 200, response.text
assert response.json() == {"width": 3, "length": 4, "area": 12}
-@needs_pydanticv2
+@pytest.mark.parametrize("client", [True, False], indirect=True)
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
diff --git a/tests/test_custom_schema_fields.py b/tests/test_custom_schema_fields.py
index d890291b19..60b795e9ba 100644
--- a/tests/test_custom_schema_fields.py
+++ b/tests/test_custom_schema_fields.py
@@ -1,13 +1,8 @@
-from typing import Optional
+from typing import Annotated, Optional
from fastapi import FastAPI
-from fastapi._compat import PYDANTIC_V2
from fastapi.testclient import TestClient
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-if PYDANTIC_V2:
- from pydantic import WithJsonSchema
+from pydantic import BaseModel, WithJsonSchema
app = FastAPI()
@@ -15,23 +10,15 @@ app = FastAPI()
class Item(BaseModel):
name: str
- if PYDANTIC_V2:
- description: Annotated[
- Optional[str], WithJsonSchema({"type": ["string", "null"]})
- ] = None
+ description: Annotated[
+ Optional[str], WithJsonSchema({"type": ["string", "null"]})
+ ] = None
- model_config = {
- "json_schema_extra": {
- "x-something-internal": {"level": 4},
- }
+ model_config = {
+ "json_schema_extra": {
+ "x-something-internal": {"level": 4},
}
- else:
- description: Optional[str] = None # type: ignore[no-redef]
-
- class Config:
- schema_extra = {
- "x-something-internal": {"level": 4},
- }
+ }
@app.get("/foo", response_model=Item)
@@ -56,7 +43,7 @@ item_schema = {
},
"description": {
"title": "Description",
- "type": ["string", "null"] if PYDANTIC_V2 else "string",
+ "type": ["string", "null"],
},
},
}
diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py
index 7e57d525ce..29a70cae0c 100644
--- a/tests/test_datastructures.py
+++ b/tests/test_datastructures.py
@@ -1,6 +1,5 @@
import io
from pathlib import Path
-from typing import List
import pytest
from fastapi import FastAPI, UploadFile
@@ -8,12 +7,6 @@ from fastapi.datastructures import Default
from fastapi.testclient import TestClient
-# TODO: remove when deprecating Pydantic v1
-def test_upload_file_invalid():
- with pytest.raises(ValueError):
- UploadFile.validate("not a Starlette UploadFile")
-
-
def test_upload_file_invalid_pydantic_v2():
with pytest.raises(ValueError):
UploadFile._validate("not a Starlette UploadFile", {})
@@ -38,7 +31,7 @@ def test_upload_file_is_closed(tmp_path: Path):
path.write_bytes(b"")
app = FastAPI()
- testing_file_store: List[UploadFile] = []
+ testing_file_store: list[UploadFile] = []
@app.post("/uploadfile/")
def create_upload_file(file: UploadFile):
diff --git a/tests/test_datetime_custom_encoder.py b/tests/test_datetime_custom_encoder.py
index 3aa77c0b1d..f154ede029 100644
--- a/tests/test_datetime_custom_encoder.py
+++ b/tests/test_datetime_custom_encoder.py
@@ -4,10 +4,7 @@ from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
-from .utils import needs_pydanticv1, needs_pydanticv2
-
-@needs_pydanticv2
def test_pydanticv2():
from pydantic import field_serializer
@@ -29,29 +26,3 @@ def test_pydanticv2():
with client:
response = client.get("/model")
assert response.json() == {"dt_field": "2019-01-01T08:00:00+00:00"}
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_pydanticv1():
- class ModelWithDatetimeField(BaseModel):
- dt_field: datetime
-
- class Config:
- json_encoders = {
- datetime: lambda dt: dt.replace(
- microsecond=0, tzinfo=timezone.utc
- ).isoformat()
- }
-
- app = FastAPI()
- model = ModelWithDatetimeField(dt_field=datetime(2019, 1, 1, 8))
-
- @app.get("/model", response_model=ModelWithDatetimeField)
- def get_model():
- return model
-
- client = TestClient(app)
- with client:
- response = client.get("/model")
- assert response.json() == {"dt_field": "2019-01-01T08:00:00+00:00"}
diff --git a/tests/test_dependency_after_yield_raise.py b/tests/test_dependency_after_yield_raise.py
index b560dc36f9..b561402772 100644
--- a/tests/test_dependency_after_yield_raise.py
+++ b/tests/test_dependency_after_yield_raise.py
@@ -1,9 +1,8 @@
-from typing import Any
+from typing import Annotated, Any
import pytest
from fastapi import Depends, FastAPI, HTTPException
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
class CustomError(Exception):
diff --git a/tests/test_dependency_after_yield_streaming.py b/tests/test_dependency_after_yield_streaming.py
index 7e1c8822b8..cbadff8f87 100644
--- a/tests/test_dependency_after_yield_streaming.py
+++ b/tests/test_dependency_after_yield_streaming.py
@@ -1,11 +1,11 @@
+from collections.abc import Generator
from contextlib import contextmanager
-from typing import Any, Generator
+from typing import Annotated, Any
import pytest
from fastapi import Depends, FastAPI
from fastapi.responses import StreamingResponse
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
class Session:
diff --git a/tests/test_dependency_after_yield_websockets.py b/tests/test_dependency_after_yield_websockets.py
index 7c323c338b..0fdf697b6c 100644
--- a/tests/test_dependency_after_yield_websockets.py
+++ b/tests/test_dependency_after_yield_websockets.py
@@ -1,10 +1,10 @@
+from collections.abc import Generator
from contextlib import contextmanager
-from typing import Any, Generator
+from typing import Annotated, Any
import pytest
from fastapi import Depends, FastAPI, WebSocket
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
class Session:
diff --git a/tests/test_dependency_class.py b/tests/test_dependency_class.py
index 0233492e6c..95ff3e9d95 100644
--- a/tests/test_dependency_class.py
+++ b/tests/test_dependency_class.py
@@ -1,4 +1,4 @@
-from typing import AsyncGenerator, Generator
+from collections.abc import AsyncGenerator, Generator
import pytest
from fastapi import Depends, FastAPI
@@ -48,6 +48,34 @@ async_callable_gen_dependency = AsyncCallableGenDependency()
methods_dependency = MethodsDependency()
+@app.get("/callable-dependency-class")
+async def get_callable_dependency_class(
+ value: str, instance: CallableDependency = Depends()
+):
+ return instance(value)
+
+
+@app.get("/callable-gen-dependency-class")
+async def get_callable_gen_dependency_class(
+ value: str, instance: CallableGenDependency = Depends()
+):
+ return next(instance(value))
+
+
+@app.get("/async-callable-dependency-class")
+async def get_async_callable_dependency_class(
+ value: str, instance: AsyncCallableDependency = Depends()
+):
+ return await instance(value)
+
+
+@app.get("/async-callable-gen-dependency-class")
+async def get_async_callable_gen_dependency_class(
+ value: str, instance: AsyncCallableGenDependency = Depends()
+):
+ return await instance(value).__anext__()
+
+
@app.get("/callable-dependency")
async def get_callable_dependency(value: str = Depends(callable_dependency)):
return value
@@ -114,6 +142,10 @@ client = TestClient(app)
("/synchronous-method-gen-dependency", "synchronous-method-gen-dependency"),
("/asynchronous-method-dependency", "asynchronous-method-dependency"),
("/asynchronous-method-gen-dependency", "asynchronous-method-gen-dependency"),
+ ("/callable-dependency-class", "callable-dependency-class"),
+ ("/callable-gen-dependency-class", "callable-gen-dependency-class"),
+ ("/async-callable-dependency-class", "async-callable-dependency-class"),
+ ("/async-callable-gen-dependency-class", "async-callable-gen-dependency-class"),
],
)
def test_class_dependency(route, value):
diff --git a/tests/test_dependency_contextmanager.py b/tests/test_dependency_contextmanager.py
index 02c10458cb..5a89934741 100644
--- a/tests/test_dependency_contextmanager.py
+++ b/tests/test_dependency_contextmanager.py
@@ -1,5 +1,4 @@
import json
-from typing import Dict
import pytest
from fastapi import BackgroundTasks, Depends, FastAPI
@@ -37,19 +36,19 @@ class OtherDependencyError(Exception):
pass
-async def asyncgen_state(state: Dict[str, str] = Depends(get_state)):
+async def asyncgen_state(state: dict[str, str] = Depends(get_state)):
state["/async"] = "asyncgen started"
yield state["/async"]
state["/async"] = "asyncgen completed"
-def generator_state(state: Dict[str, str] = Depends(get_state)):
+def generator_state(state: dict[str, str] = Depends(get_state)):
state["/sync"] = "generator started"
yield state["/sync"]
state["/sync"] = "generator completed"
-async def asyncgen_state_try(state: Dict[str, str] = Depends(get_state)):
+async def asyncgen_state_try(state: dict[str, str] = Depends(get_state)):
state["/async_raise"] = "asyncgen raise started"
try:
yield state["/async_raise"]
@@ -60,7 +59,7 @@ async def asyncgen_state_try(state: Dict[str, str] = Depends(get_state)):
state["/async_raise"] = "asyncgen raise finalized"
-def generator_state_try(state: Dict[str, str] = Depends(get_state)):
+def generator_state_try(state: dict[str, str] = Depends(get_state)):
state["/sync_raise"] = "generator raise started"
try:
yield state["/sync_raise"]
diff --git a/tests/test_dependency_contextvars.py b/tests/test_dependency_contextvars.py
index 076802df84..0c2e5594b6 100644
--- a/tests/test_dependency_contextvars.py
+++ b/tests/test_dependency_contextvars.py
@@ -1,10 +1,11 @@
+from collections.abc import Awaitable
from contextvars import ContextVar
-from typing import Any, Awaitable, Callable, Dict, Optional
+from typing import Any, Callable, Optional
from fastapi import Depends, FastAPI, Request, Response
from fastapi.testclient import TestClient
-legacy_request_state_context_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
+legacy_request_state_context_var: ContextVar[Optional[dict[str, Any]]] = ContextVar(
"legacy_request_state_context_var", default=None
)
diff --git a/tests/test_dependency_duplicates.py b/tests/test_dependency_duplicates.py
index 8e8d07c2d6..a8658e03bb 100644
--- a/tests/test_dependency_duplicates.py
+++ b/tests/test_dependency_duplicates.py
@@ -1,6 +1,3 @@
-from typing import List
-
-from dirty_equals import IsDict
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -40,7 +37,7 @@ async def no_duplicates(item: Item, item2: Item = Depends(dependency)):
@app.post("/with-duplicates-sub")
async def no_duplicates_sub(
- item: Item, sub_items: List[Item] = Depends(sub_duplicate_dependency)
+ item: Item, sub_items: list[Item] = Depends(sub_duplicate_dependency)
):
return [item, sub_items]
@@ -48,29 +45,16 @@ async def no_duplicates_sub(
def test_no_duplicates_invalid():
response = client.post("/no-duplicates", json={"item": {"data": "myitem"}})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item2"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item2"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "item2"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_no_duplicates():
diff --git a/tests/test_dependency_overrides.py b/tests/test_dependency_overrides.py
index 154937fa0b..e25db624d8 100644
--- a/tests/test_dependency_overrides.py
+++ b/tests/test_dependency_overrides.py
@@ -1,7 +1,6 @@
from typing import Optional
import pytest
-from dirty_equals import IsDict
from fastapi import APIRouter, Depends, FastAPI
from fastapi.testclient import TestClient
@@ -54,29 +53,16 @@ async def overrider_dependency_with_sub(msg: dict = Depends(overrider_sub_depend
def test_main_depends():
response = client.get("/main-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "q"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "q"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "q"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_main_depends_q_foo():
@@ -100,29 +86,16 @@ def test_main_depends_q_foo_skip_100_limit_200():
def test_decorator_depends():
response = client.get("/decorator-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "q"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "q"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "q"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_decorator_depends_q_foo():
@@ -140,29 +113,16 @@ def test_decorator_depends_q_foo_skip_100_limit_200():
def test_router_depends():
response = client.get("/router-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "q"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "q"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "q"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_router_depends_q_foo():
@@ -186,29 +146,16 @@ def test_router_depends_q_foo_skip_100_limit_200():
def test_router_decorator_depends():
response = client.get("/router-decorator-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "q"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "q"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "q"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_router_decorator_depends_q_foo():
@@ -272,29 +219,17 @@ def test_override_with_sub_main_depends():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/main-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -302,29 +237,17 @@ def test_override_with_sub__main_depends_q_foo():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/main-depends/?q=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -340,29 +263,17 @@ def test_override_with_sub_decorator_depends():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/decorator-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -370,29 +281,17 @@ def test_override_with_sub_decorator_depends_q_foo():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/decorator-depends/?q=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -408,29 +307,17 @@ def test_override_with_sub_router_depends():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/router-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -438,29 +325,17 @@ def test_override_with_sub_router_depends_q_foo():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/router-depends/?q=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -476,29 +351,17 @@ def test_override_with_sub_router_decorator_depends():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/router-decorator-depends/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
@@ -506,29 +369,17 @@ def test_override_with_sub_router_decorator_depends_q_foo():
app.dependency_overrides[common_parameters] = overrider_dependency_with_sub
response = client.get("/router-decorator-depends/?q=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "k"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "k"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "k"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
app.dependency_overrides = {}
diff --git a/tests/test_dependency_paramless.py b/tests/test_dependency_paramless.py
index 9c3cc3878b..1774196fe4 100644
--- a/tests/test_dependency_paramless.py
+++ b/tests/test_dependency_paramless.py
@@ -1,4 +1,4 @@
-from typing import Union
+from typing import Annotated, Union
from fastapi import FastAPI, HTTPException, Security
from fastapi.security import (
@@ -6,7 +6,6 @@ from fastapi.security import (
SecurityScopes,
)
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
app = FastAPI()
diff --git a/tests/test_dependency_partial.py b/tests/test_dependency_partial.py
new file mode 100644
index 0000000000..05a3cffa5a
--- /dev/null
+++ b/tests/test_dependency_partial.py
@@ -0,0 +1,251 @@
+from collections.abc import AsyncGenerator, Generator
+from functools import partial
+from typing import Annotated
+
+import pytest
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+def function_dependency(value: str) -> str:
+ return value
+
+
+async def async_function_dependency(value: str) -> str:
+ return value
+
+
+def gen_dependency(value: str) -> Generator[str, None, None]:
+ yield value
+
+
+async def async_gen_dependency(value: str) -> AsyncGenerator[str, None]:
+ yield value
+
+
+class CallableDependency:
+ def __call__(self, value: str) -> str:
+ return value
+
+
+class CallableGenDependency:
+ def __call__(self, value: str) -> Generator[str, None, None]:
+ yield value
+
+
+class AsyncCallableDependency:
+ async def __call__(self, value: str) -> str:
+ return value
+
+
+class AsyncCallableGenDependency:
+ async def __call__(self, value: str) -> AsyncGenerator[str, None]:
+ yield value
+
+
+class MethodsDependency:
+ def synchronous(self, value: str) -> str:
+ return value
+
+ async def asynchronous(self, value: str) -> str:
+ return value
+
+ def synchronous_gen(self, value: str) -> Generator[str, None, None]:
+ yield value
+
+ async def asynchronous_gen(self, value: str) -> AsyncGenerator[str, None]:
+ yield value
+
+
+callable_dependency = CallableDependency()
+callable_gen_dependency = CallableGenDependency()
+async_callable_dependency = AsyncCallableDependency()
+async_callable_gen_dependency = AsyncCallableGenDependency()
+methods_dependency = MethodsDependency()
+
+
+@app.get("/partial-function-dependency")
+async def get_partial_function_dependency(
+ value: Annotated[
+ str, Depends(partial(function_dependency, "partial-function-dependency"))
+ ],
+) -> str:
+ return value
+
+
+@app.get("/partial-async-function-dependency")
+async def get_partial_async_function_dependency(
+ value: Annotated[
+ str,
+ Depends(
+ partial(async_function_dependency, "partial-async-function-dependency")
+ ),
+ ],
+) -> str:
+ return value
+
+
+@app.get("/partial-gen-dependency")
+async def get_partial_gen_dependency(
+ value: Annotated[str, Depends(partial(gen_dependency, "partial-gen-dependency"))],
+) -> str:
+ return value
+
+
+@app.get("/partial-async-gen-dependency")
+async def get_partial_async_gen_dependency(
+ value: Annotated[
+ str, Depends(partial(async_gen_dependency, "partial-async-gen-dependency"))
+ ],
+) -> str:
+ return value
+
+
+@app.get("/partial-callable-dependency")
+async def get_partial_callable_dependency(
+ value: Annotated[
+ str, Depends(partial(callable_dependency, "partial-callable-dependency"))
+ ],
+) -> str:
+ return value
+
+
+@app.get("/partial-callable-gen-dependency")
+async def get_partial_callable_gen_dependency(
+ value: Annotated[
+ str,
+ Depends(partial(callable_gen_dependency, "partial-callable-gen-dependency")),
+ ],
+) -> str:
+ return value
+
+
+@app.get("/partial-async-callable-dependency")
+async def get_partial_async_callable_dependency(
+ value: Annotated[
+ str,
+ Depends(
+ partial(async_callable_dependency, "partial-async-callable-dependency")
+ ),
+ ],
+) -> str:
+ return value
+
+
+@app.get("/partial-async-callable-gen-dependency")
+async def get_partial_async_callable_gen_dependency(
+ value: Annotated[
+ str,
+ Depends(
+ partial(
+ async_callable_gen_dependency, "partial-async-callable-gen-dependency"
+ )
+ ),
+ ],
+) -> str:
+ return value
+
+
+@app.get("/partial-synchronous-method-dependency")
+async def get_partial_synchronous_method_dependency(
+ value: Annotated[
+ str,
+ Depends(
+ partial(
+ methods_dependency.synchronous, "partial-synchronous-method-dependency"
+ )
+ ),
+ ],
+) -> str:
+ return value
+
+
+@app.get("/partial-synchronous-method-gen-dependency")
+async def get_partial_synchronous_method_gen_dependency(
+ value: Annotated[
+ str,
+ Depends(
+ partial(
+ methods_dependency.synchronous_gen,
+ "partial-synchronous-method-gen-dependency",
+ )
+ ),
+ ],
+) -> str:
+ return value
+
+
+@app.get("/partial-asynchronous-method-dependency")
+async def get_partial_asynchronous_method_dependency(
+ value: Annotated[
+ str,
+ Depends(
+ partial(
+ methods_dependency.asynchronous,
+ "partial-asynchronous-method-dependency",
+ )
+ ),
+ ],
+) -> str:
+ return value
+
+
+@app.get("/partial-asynchronous-method-gen-dependency")
+async def get_partial_asynchronous_method_gen_dependency(
+ value: Annotated[
+ str,
+ Depends(
+ partial(
+ methods_dependency.asynchronous_gen,
+ "partial-asynchronous-method-gen-dependency",
+ )
+ ),
+ ],
+) -> str:
+ return value
+
+
+client = TestClient(app)
+
+
+@pytest.mark.parametrize(
+ "route,value",
+ [
+ ("/partial-function-dependency", "partial-function-dependency"),
+ (
+ "/partial-async-function-dependency",
+ "partial-async-function-dependency",
+ ),
+ ("/partial-gen-dependency", "partial-gen-dependency"),
+ ("/partial-async-gen-dependency", "partial-async-gen-dependency"),
+ ("/partial-callable-dependency", "partial-callable-dependency"),
+ ("/partial-callable-gen-dependency", "partial-callable-gen-dependency"),
+ ("/partial-async-callable-dependency", "partial-async-callable-dependency"),
+ (
+ "/partial-async-callable-gen-dependency",
+ "partial-async-callable-gen-dependency",
+ ),
+ (
+ "/partial-synchronous-method-dependency",
+ "partial-synchronous-method-dependency",
+ ),
+ (
+ "/partial-synchronous-method-gen-dependency",
+ "partial-synchronous-method-gen-dependency",
+ ),
+ (
+ "/partial-asynchronous-method-dependency",
+ "partial-asynchronous-method-dependency",
+ ),
+ (
+ "/partial-asynchronous-method-gen-dependency",
+ "partial-asynchronous-method-gen-dependency",
+ ),
+ ],
+)
+def test_dependency_types_with_partial(route: str, value: str) -> None:
+ response = client.get(route)
+ assert response.status_code == 200, response.text
+ assert response.json() == value
diff --git a/tests/test_dependency_security_overrides.py b/tests/test_dependency_security_overrides.py
index b89d82db43..14b65c7770 100644
--- a/tests/test_dependency_security_overrides.py
+++ b/tests/test_dependency_security_overrides.py
@@ -1,5 +1,3 @@
-from typing import List, Tuple
-
from fastapi import Depends, FastAPI, Security
from fastapi.security import SecurityScopes
from fastapi.testclient import TestClient
@@ -25,8 +23,8 @@ def get_data_override():
@app.get("/user")
def read_user(
- user_data: Tuple[str, List[str]] = Security(get_user, scopes=["foo", "bar"]),
- data: List[int] = Depends(get_data),
+ user_data: tuple[str, list[str]] = Security(get_user, scopes=["foo", "bar"]),
+ data: list[int] = Depends(get_data),
):
return {"user": user_data[0], "scopes": user_data[1], "data": data}
diff --git a/tests/test_dependency_wrapped.py b/tests/test_dependency_wrapped.py
new file mode 100644
index 0000000000..a4044112a2
--- /dev/null
+++ b/tests/test_dependency_wrapped.py
@@ -0,0 +1,449 @@
+import inspect
+import sys
+from collections.abc import AsyncGenerator, Generator
+from functools import wraps
+
+import pytest
+from fastapi import Depends, FastAPI
+from fastapi.concurrency import iterate_in_threadpool, run_in_threadpool
+from fastapi.testclient import TestClient
+
+if sys.version_info >= (3, 13): # pragma: no cover
+ from inspect import iscoroutinefunction
+else: # pragma: no cover
+ from asyncio import iscoroutinefunction
+
+
+def noop_wrap(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+
+ return wrapper
+
+
+def noop_wrap_async(func):
+ if inspect.isgeneratorfunction(func):
+
+ @wraps(func)
+ async def gen_wrapper(*args, **kwargs):
+ async for item in iterate_in_threadpool(func(*args, **kwargs)):
+ yield item
+
+ return gen_wrapper
+
+ elif inspect.isasyncgenfunction(func):
+
+ @wraps(func)
+ async def async_gen_wrapper(*args, **kwargs):
+ async for item in func(*args, **kwargs):
+ yield item
+
+ return async_gen_wrapper
+
+ @wraps(func)
+ async def wrapper(*args, **kwargs):
+ if inspect.isroutine(func) and iscoroutinefunction(func):
+ return await func(*args, **kwargs)
+ if inspect.isclass(func):
+ return await run_in_threadpool(func, *args, **kwargs)
+ dunder_call = getattr(func, "__call__", None) # noqa: B004
+ if iscoroutinefunction(dunder_call):
+ return await dunder_call(*args, **kwargs)
+ return await run_in_threadpool(func, *args, **kwargs)
+
+ return wrapper
+
+
+class ClassInstanceDep:
+ def __call__(self):
+ return True
+
+
+class_instance_dep = ClassInstanceDep()
+wrapped_class_instance_dep = noop_wrap(class_instance_dep)
+wrapped_class_instance_dep_async_wrapper = noop_wrap_async(class_instance_dep)
+
+
+class ClassInstanceGenDep:
+ def __call__(self):
+ yield True
+
+
+class_instance_gen_dep = ClassInstanceGenDep()
+wrapped_class_instance_gen_dep = noop_wrap(class_instance_gen_dep)
+
+
+class ClassInstanceWrappedDep:
+ @noop_wrap
+ def __call__(self):
+ return True
+
+
+class_instance_wrapped_dep = ClassInstanceWrappedDep()
+
+
+class ClassInstanceWrappedAsyncDep:
+ @noop_wrap_async
+ def __call__(self):
+ return True
+
+
+class_instance_wrapped_async_dep = ClassInstanceWrappedAsyncDep()
+
+
+class ClassInstanceWrappedGenDep:
+ @noop_wrap
+ def __call__(self):
+ yield True
+
+
+class_instance_wrapped_gen_dep = ClassInstanceWrappedGenDep()
+
+
+class ClassInstanceWrappedAsyncGenDep:
+ @noop_wrap_async
+ def __call__(self):
+ yield True
+
+
+class_instance_wrapped_async_gen_dep = ClassInstanceWrappedAsyncGenDep()
+
+
+class ClassDep:
+ def __init__(self):
+ self.value = True
+
+
+wrapped_class_dep = noop_wrap(ClassDep)
+wrapped_class_dep_async_wrapper = noop_wrap_async(ClassDep)
+
+
+class ClassInstanceAsyncDep:
+ async def __call__(self):
+ return True
+
+
+class_instance_async_dep = ClassInstanceAsyncDep()
+wrapped_class_instance_async_dep = noop_wrap(class_instance_async_dep)
+wrapped_class_instance_async_dep_async_wrapper = noop_wrap_async(
+ class_instance_async_dep
+)
+
+
+class ClassInstanceAsyncGenDep:
+ async def __call__(self):
+ yield True
+
+
+class_instance_async_gen_dep = ClassInstanceAsyncGenDep()
+wrapped_class_instance_async_gen_dep = noop_wrap(class_instance_async_gen_dep)
+
+
+class ClassInstanceAsyncWrappedDep:
+ @noop_wrap
+ async def __call__(self):
+ return True
+
+
+class_instance_async_wrapped_dep = ClassInstanceAsyncWrappedDep()
+
+
+class ClassInstanceAsyncWrappedAsyncDep:
+ @noop_wrap_async
+ async def __call__(self):
+ return True
+
+
+class_instance_async_wrapped_async_dep = ClassInstanceAsyncWrappedAsyncDep()
+
+
+class ClassInstanceAsyncWrappedGenDep:
+ @noop_wrap
+ async def __call__(self):
+ yield True
+
+
+class_instance_async_wrapped_gen_dep = ClassInstanceAsyncWrappedGenDep()
+
+
+class ClassInstanceAsyncWrappedGenAsyncDep:
+ @noop_wrap_async
+ async def __call__(self):
+ yield True
+
+
+class_instance_async_wrapped_gen_async_dep = ClassInstanceAsyncWrappedGenAsyncDep()
+
+app = FastAPI()
+
+# Sync wrapper
+
+
+@noop_wrap
+def wrapped_dependency() -> bool:
+ return True
+
+
+@noop_wrap
+def wrapped_gen_dependency() -> Generator[bool, None, None]:
+ yield True
+
+
+@noop_wrap
+async def async_wrapped_dependency() -> bool:
+ return True
+
+
+@noop_wrap
+async def async_wrapped_gen_dependency() -> AsyncGenerator[bool, None]:
+ yield True
+
+
+@app.get("/wrapped-dependency/")
+async def get_wrapped_dependency(value: bool = Depends(wrapped_dependency)):
+ return value
+
+
+@app.get("/wrapped-gen-dependency/")
+async def get_wrapped_gen_dependency(value: bool = Depends(wrapped_gen_dependency)):
+ return value
+
+
+@app.get("/async-wrapped-dependency/")
+async def get_async_wrapped_dependency(value: bool = Depends(async_wrapped_dependency)):
+ return value
+
+
+@app.get("/async-wrapped-gen-dependency/")
+async def get_async_wrapped_gen_dependency(
+ value: bool = Depends(async_wrapped_gen_dependency),
+):
+ return value
+
+
+@app.get("/wrapped-class-instance-dependency/")
+async def get_wrapped_class_instance_dependency(
+ value: bool = Depends(wrapped_class_instance_dep),
+):
+ return value
+
+
+@app.get("/wrapped-class-instance-async-dependency/")
+async def get_wrapped_class_instance_async_dependency(
+ value: bool = Depends(wrapped_class_instance_async_dep),
+):
+ return value
+
+
+@app.get("/wrapped-class-instance-gen-dependency/")
+async def get_wrapped_class_instance_gen_dependency(
+ value: bool = Depends(wrapped_class_instance_gen_dep),
+):
+ return value
+
+
+@app.get("/wrapped-class-instance-async-gen-dependency/")
+async def get_wrapped_class_instance_async_gen_dependency(
+ value: bool = Depends(wrapped_class_instance_async_gen_dep),
+):
+ return value
+
+
+@app.get("/class-instance-wrapped-dependency/")
+async def get_class_instance_wrapped_dependency(
+ value: bool = Depends(class_instance_wrapped_dep),
+):
+ return value
+
+
+@app.get("/class-instance-wrapped-async-dependency/")
+async def get_class_instance_wrapped_async_dependency(
+ value: bool = Depends(class_instance_wrapped_async_dep),
+):
+ return value
+
+
+@app.get("/class-instance-async-wrapped-dependency/")
+async def get_class_instance_async_wrapped_dependency(
+ value: bool = Depends(class_instance_async_wrapped_dep),
+):
+ return value
+
+
+@app.get("/class-instance-async-wrapped-async-dependency/")
+async def get_class_instance_async_wrapped_async_dependency(
+ value: bool = Depends(class_instance_async_wrapped_async_dep),
+):
+ return value
+
+
+@app.get("/class-instance-wrapped-gen-dependency/")
+async def get_class_instance_wrapped_gen_dependency(
+ value: bool = Depends(class_instance_wrapped_gen_dep),
+):
+ return value
+
+
+@app.get("/class-instance-wrapped-async-gen-dependency/")
+async def get_class_instance_wrapped_async_gen_dependency(
+ value: bool = Depends(class_instance_wrapped_async_gen_dep),
+):
+ return value
+
+
+@app.get("/class-instance-async-wrapped-gen-dependency/")
+async def get_class_instance_async_wrapped_gen_dependency(
+ value: bool = Depends(class_instance_async_wrapped_gen_dep),
+):
+ return value
+
+
+@app.get("/class-instance-async-wrapped-gen-async-dependency/")
+async def get_class_instance_async_wrapped_gen_async_dependency(
+ value: bool = Depends(class_instance_async_wrapped_gen_async_dep),
+):
+ return value
+
+
+@app.get("/wrapped-class-dependency/")
+async def get_wrapped_class_dependency(value: ClassDep = Depends(wrapped_class_dep)):
+ return value.value
+
+
+@app.get("/wrapped-endpoint/")
+@noop_wrap
+def get_wrapped_endpoint():
+ return True
+
+
+@app.get("/async-wrapped-endpoint/")
+@noop_wrap
+async def get_async_wrapped_endpoint():
+ return True
+
+
+# Async wrapper
+
+
+@noop_wrap_async
+def wrapped_dependency_async_wrapper() -> bool:
+ return True
+
+
+@noop_wrap_async
+def wrapped_gen_dependency_async_wrapper() -> Generator[bool, None, None]:
+ yield True
+
+
+@noop_wrap_async
+async def async_wrapped_dependency_async_wrapper() -> bool:
+ return True
+
+
+@noop_wrap_async
+async def async_wrapped_gen_dependency_async_wrapper() -> AsyncGenerator[bool, None]:
+ yield True
+
+
+@app.get("/wrapped-dependency-async-wrapper/")
+async def get_wrapped_dependency_async_wrapper(
+ value: bool = Depends(wrapped_dependency_async_wrapper),
+):
+ return value
+
+
+@app.get("/wrapped-gen-dependency-async-wrapper/")
+async def get_wrapped_gen_dependency_async_wrapper(
+ value: bool = Depends(wrapped_gen_dependency_async_wrapper),
+):
+ return value
+
+
+@app.get("/async-wrapped-dependency-async-wrapper/")
+async def get_async_wrapped_dependency_async_wrapper(
+ value: bool = Depends(async_wrapped_dependency_async_wrapper),
+):
+ return value
+
+
+@app.get("/async-wrapped-gen-dependency-async-wrapper/")
+async def get_async_wrapped_gen_dependency_async_wrapper(
+ value: bool = Depends(async_wrapped_gen_dependency_async_wrapper),
+):
+ return value
+
+
+@app.get("/wrapped-class-instance-dependency-async-wrapper/")
+async def get_wrapped_class_instance_dependency_async_wrapper(
+ value: bool = Depends(wrapped_class_instance_dep_async_wrapper),
+):
+ return value
+
+
+@app.get("/wrapped-class-instance-async-dependency-async-wrapper/")
+async def get_wrapped_class_instance_async_dependency_async_wrapper(
+ value: bool = Depends(wrapped_class_instance_async_dep_async_wrapper),
+):
+ return value
+
+
+@app.get("/wrapped-class-dependency-async-wrapper/")
+async def get_wrapped_class_dependency_async_wrapper(
+ value: ClassDep = Depends(wrapped_class_dep_async_wrapper),
+):
+ return value.value
+
+
+@app.get("/wrapped-endpoint-async-wrapper/")
+@noop_wrap_async
+def get_wrapped_endpoint_async_wrapper():
+ return True
+
+
+@app.get("/async-wrapped-endpoint-async-wrapper/")
+@noop_wrap_async
+async def get_async_wrapped_endpoint_async_wrapper():
+ return True
+
+
+client = TestClient(app)
+
+
+@pytest.mark.parametrize(
+ "route",
+ [
+ "/wrapped-dependency/",
+ "/wrapped-gen-dependency/",
+ "/async-wrapped-dependency/",
+ "/async-wrapped-gen-dependency/",
+ "/wrapped-class-instance-dependency/",
+ "/wrapped-class-instance-async-dependency/",
+ "/wrapped-class-instance-gen-dependency/",
+ "/wrapped-class-instance-async-gen-dependency/",
+ "/class-instance-wrapped-dependency/",
+ "/class-instance-wrapped-async-dependency/",
+ "/class-instance-async-wrapped-dependency/",
+ "/class-instance-async-wrapped-async-dependency/",
+ "/class-instance-wrapped-gen-dependency/",
+ "/class-instance-wrapped-async-gen-dependency/",
+ "/class-instance-async-wrapped-gen-dependency/",
+ "/class-instance-async-wrapped-gen-async-dependency/",
+ "/wrapped-class-dependency/",
+ "/wrapped-endpoint/",
+ "/async-wrapped-endpoint/",
+ "/wrapped-dependency-async-wrapper/",
+ "/wrapped-gen-dependency-async-wrapper/",
+ "/async-wrapped-dependency-async-wrapper/",
+ "/async-wrapped-gen-dependency-async-wrapper/",
+ "/wrapped-class-instance-dependency-async-wrapper/",
+ "/wrapped-class-instance-async-dependency-async-wrapper/",
+ "/wrapped-class-dependency-async-wrapper/",
+ "/wrapped-endpoint-async-wrapper/",
+ "/async-wrapped-endpoint-async-wrapper/",
+ ],
+)
+def test_class_dependency(route):
+ response = client.get(route)
+ assert response.status_code == 200, response.text
+ assert response.json() is True
diff --git a/tests/test_dependency_yield_scope.py b/tests/test_dependency_yield_scope.py
index a5227dd7ab..f3fc3cc94f 100644
--- a/tests/test_dependency_yield_scope.py
+++ b/tests/test_dependency_yield_scope.py
@@ -1,12 +1,11 @@
import json
-from typing import Any, Tuple
+from typing import Annotated, Any
import pytest
-from fastapi import Depends, FastAPI
+from fastapi import APIRouter, Depends, FastAPI, HTTPException
from fastapi.exceptions import FastAPIError
from fastapi.responses import StreamingResponse
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
class Session:
@@ -20,6 +19,11 @@ def dep_session() -> Any:
s.open = False
+def raise_after_yield() -> Any:
+ yield
+ raise HTTPException(status_code=503, detail="Exception after yield")
+
+
SessionFuncDep = Annotated[Session, Depends(dep_session, scope="function")]
SessionRequestDep = Annotated[Session, Depends(dep_session, scope="request")]
SessionDefaultDep = Annotated[Session, Depends(dep_session)]
@@ -38,7 +42,7 @@ def get_named_session(session: SessionRequestDep, session_b: SessionDefaultDep)
named_session.open = False
-NamedSessionsDep = Annotated[Tuple[NamedSession, Session], Depends(get_named_session)]
+NamedSessionsDep = Annotated[tuple[NamedSession, Session], Depends(get_named_session)]
def get_named_func_session(session: SessionFuncDep) -> Any:
@@ -53,17 +57,23 @@ def get_named_regular_func_session(session: SessionFuncDep) -> Any:
BrokenSessionsDep = Annotated[
- Tuple[NamedSession, Session], Depends(get_named_func_session)
+ tuple[NamedSession, Session], Depends(get_named_func_session)
]
NamedSessionsFuncDep = Annotated[
- Tuple[NamedSession, Session], Depends(get_named_func_session, scope="function")
+ tuple[NamedSession, Session], Depends(get_named_func_session, scope="function")
]
RegularSessionsDep = Annotated[
- Tuple[NamedSession, Session], Depends(get_named_regular_func_session)
+ tuple[NamedSession, Session], Depends(get_named_regular_func_session)
]
app = FastAPI()
+router = APIRouter()
+
+
+@router.get("/")
+def get_index():
+ return {"status": "ok"}
@app.get("/function-scope")
@@ -124,6 +134,18 @@ def get_regular_function_scope(sessions: RegularSessionsDep) -> Any:
return StreamingResponse(iter_data())
+app.include_router(
+ prefix="/router-scope-function",
+ router=router,
+ dependencies=[Depends(raise_after_yield, scope="function")],
+)
+
+app.include_router(
+ prefix="/router-scope-request",
+ router=router,
+ dependencies=[Depends(raise_after_yield, scope="request")],
+)
+
client = TestClient(app)
@@ -182,3 +204,42 @@ def test_regular_function_scope() -> None:
data = response.json()
assert data["named_session_open"] is True
assert data["session_open"] is False
+
+
+def test_router_level_dep_scope_function() -> None:
+ response = client.get("/router-scope-function/")
+ assert response.status_code == 503
+ assert response.json() == {"detail": "Exception after yield"}
+
+
+def test_router_level_dep_scope_request() -> None:
+ with TestClient(app, raise_server_exceptions=False) as client:
+ response = client.get("/router-scope-request/")
+ assert response.status_code == 200
+ assert response.json() == {"status": "ok"}
+
+
+def test_app_level_dep_scope_function() -> None:
+ app = FastAPI(dependencies=[Depends(raise_after_yield, scope="function")])
+
+ @app.get("/app-scope-function")
+ def get_app_scope_function():
+ return {"status": "ok"}
+
+ with TestClient(app) as client:
+ response = client.get("/app-scope-function")
+ assert response.status_code == 503
+ assert response.json() == {"detail": "Exception after yield"}
+
+
+def test_app_level_dep_scope_request() -> None:
+ app = FastAPI(dependencies=[Depends(raise_after_yield, scope="request")])
+
+ @app.get("/app-scope-request")
+ def get_app_scope_request():
+ return {"status": "ok"}
+
+ with TestClient(app, raise_server_exceptions=False) as client:
+ response = client.get("/app-scope-request")
+ assert response.status_code == 200
+ assert response.json() == {"status": "ok"}
diff --git a/tests/test_dependency_yield_scope_websockets.py b/tests/test_dependency_yield_scope_websockets.py
index 52a30ae7a7..dbe35e576c 100644
--- a/tests/test_dependency_yield_scope_websockets.py
+++ b/tests/test_dependency_yield_scope_websockets.py
@@ -1,13 +1,12 @@
from contextvars import ContextVar
-from typing import Any, Dict, Tuple
+from typing import Annotated, Any
import pytest
from fastapi import Depends, FastAPI, WebSocket
from fastapi.exceptions import FastAPIError
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
-global_context: ContextVar[Dict[str, Any]] = ContextVar("global_context", default={}) # noqa: B039
+global_context: ContextVar[dict[str, Any]] = ContextVar("global_context", default={}) # noqa: B039
class Session:
@@ -43,7 +42,7 @@ def get_named_session(session: SessionRequestDep, session_b: SessionDefaultDep)
global_state["named_session_closed"] = True
-NamedSessionsDep = Annotated[Tuple[NamedSession, Session], Depends(get_named_session)]
+NamedSessionsDep = Annotated[tuple[NamedSession, Session], Depends(get_named_session)]
def get_named_func_session(session: SessionFuncDep) -> Any:
@@ -60,14 +59,14 @@ def get_named_regular_func_session(session: SessionFuncDep) -> Any:
BrokenSessionsDep = Annotated[
- Tuple[NamedSession, Session], Depends(get_named_func_session)
+ tuple[NamedSession, Session], Depends(get_named_func_session)
]
NamedSessionsFuncDep = Annotated[
- Tuple[NamedSession, Session], Depends(get_named_func_session, scope="function")
+ tuple[NamedSession, Session], Depends(get_named_func_session, scope="function")
]
RegularSessionsDep = Annotated[
- Tuple[NamedSession, Session], Depends(get_named_regular_func_session)
+ tuple[NamedSession, Session], Depends(get_named_regular_func_session)
]
app = FastAPI()
diff --git a/tests/test_depends_hashable.py b/tests/test_depends_hashable.py
new file mode 100644
index 0000000000..d57f2726ec
--- /dev/null
+++ b/tests/test_depends_hashable.py
@@ -0,0 +1,25 @@
+# This is more or less a workaround to make Depends and Security hashable
+# as other tools that use them depend on that
+# Ref: https://github.com/fastapi/fastapi/pull/14320
+
+from fastapi import Depends, Security
+
+
+def dep():
+ pass
+
+
+def test_depends_hashable():
+ dep() # just for coverage
+ d1 = Depends(dep)
+ d2 = Depends(dep)
+ d3 = Depends(dep, scope="function")
+ d4 = Depends(dep, scope="function")
+
+ s1 = Security(dep)
+ s2 = Security(dep)
+
+ assert hash(d1) == hash(d2)
+ assert hash(s1) == hash(s2)
+ assert hash(d1) != hash(d3)
+ assert hash(d3) == hash(d4)
diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py
index bd16fe9254..45734ec28a 100644
--- a/tests/test_extra_routes.py
+++ b/tests/test_extra_routes.py
@@ -1,6 +1,5 @@
from typing import Optional
-from dirty_equals import IsDict
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
@@ -328,14 +327,10 @@ def test_openapi_schema():
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "price": IsDict(
- {
- "title": "Price",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- # TODO: remove when deprecating Pydantic v1
- | IsDict({"title": "Price", "type": "number"}),
+ "price": {
+ "title": "Price",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_fastapi_cli.py b/tests/test_fastapi_cli.py
index a5c10778ad..78a49a1fbe 100644
--- a/tests/test_fastapi_cli.py
+++ b/tests/test_fastapi_cli.py
@@ -1,3 +1,4 @@
+import os
import subprocess
import sys
from unittest.mock import patch
@@ -20,6 +21,7 @@ def test_fastapi_cli():
],
capture_output=True,
encoding="utf-8",
+ env={**os.environ, "PYTHONIOENCODING": "utf-8"},
)
assert result.returncode == 1, result.stdout
assert "Path does not exist non_existent_file.py" in result.stdout
diff --git a/tests/test_file_and_form_order_issue_9116.py b/tests/test_file_and_form_order_issue_9116.py
index cb9a31d314..75290b60cd 100644
--- a/tests/test_file_and_form_order_issue_9116.py
+++ b/tests/test_file_and_form_order_issue_9116.py
@@ -4,12 +4,11 @@ See https://github.com/tiangolo/fastapi/discussions/9116
"""
from pathlib import Path
-from typing import List
+from typing import Annotated
import pytest
from fastapi import FastAPI, File, Form
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
app = FastAPI()
@@ -32,7 +31,7 @@ def file_after_form(
@app.post("/file_list_before_form")
def file_list_before_form(
- files: Annotated[List[bytes], File()],
+ files: Annotated[list[bytes], File()],
city: Annotated[str, Form()],
):
return {"file_contents": files, "city": city}
@@ -41,7 +40,7 @@ def file_list_before_form(
@app.post("/file_list_after_form")
def file_list_after_form(
city: Annotated[str, Form()],
- files: Annotated[List[bytes], File()],
+ files: Annotated[list[bytes], File()],
):
return {"file_contents": files, "city": city}
diff --git a/tests/test_filter_pydantic_sub_model/app_pv1.py b/tests/test_filter_pydantic_sub_model/app_pv1.py
deleted file mode 100644
index 657e8c5d12..0000000000
--- a/tests/test_filter_pydantic_sub_model/app_pv1.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from typing import Optional
-
-from fastapi import Depends, FastAPI
-from pydantic import BaseModel, validator
-
-app = FastAPI()
-
-
-class ModelB(BaseModel):
- username: str
-
-
-class ModelC(ModelB):
- password: str
-
-
-class ModelA(BaseModel):
- name: str
- description: Optional[str] = None
- model_b: ModelB
-
- @validator("name")
- def lower_username(cls, name: str, values):
- if not name.endswith("A"):
- raise ValueError("name must end in A")
- return name
-
-
-async def get_model_c() -> ModelC:
- return ModelC(username="test-user", password="test-password")
-
-
-@app.get("/model/{name}", response_model=ModelA)
-async def get_model_a(name: str, model_c=Depends(get_model_c)):
- return {"name": name, "description": "model-a-desc", "model_b": model_c}
diff --git a/tests/test_filter_pydantic_sub_model_pv2.py b/tests/test_filter_pydantic_sub_model_pv2.py
index 2e2c26ddcb..fc5876410d 100644
--- a/tests/test_filter_pydantic_sub_model_pv2.py
+++ b/tests/test_filter_pydantic_sub_model_pv2.py
@@ -1,12 +1,11 @@
from typing import Optional
import pytest
-from dirty_equals import HasRepr, IsDict, IsOneOf
+from dirty_equals import HasRepr
from fastapi import Depends, FastAPI
from fastapi.exceptions import ResponseValidationError
from fastapi.testclient import TestClient
-
-from .utils import needs_pydanticv2
+from inline_snapshot import snapshot
@pytest.fixture(name="client")
@@ -25,6 +24,7 @@ def get_client():
name: str
description: Optional[str] = None
foo: ModelB
+ tags: dict[str, str] = {}
@field_validator("name")
def lower_username(cls, name: str, info: ValidationInfo):
@@ -37,13 +37,17 @@ def get_client():
@app.get("/model/{name}", response_model=ModelA)
async def get_model_a(name: str, model_c=Depends(get_model_c)):
- return {"name": name, "description": "model-a-desc", "foo": model_c}
+ return {
+ "name": name,
+ "description": "model-a-desc",
+ "foo": model_c,
+ "tags": {"key1": "value1", "key2": "value2"},
+ }
client = TestClient(app)
return client
-@needs_pydanticv2
def test_filter_sub_model(client: TestClient):
response = client.get("/model/modelA")
assert response.status_code == 200, response.text
@@ -51,134 +55,128 @@ def test_filter_sub_model(client: TestClient):
"name": "modelA",
"description": "model-a-desc",
"foo": {"username": "test-user"},
+ "tags": {"key1": "value1", "key2": "value2"},
}
-@needs_pydanticv2
def test_validator_is_cloned(client: TestClient):
with pytest.raises(ResponseValidationError) as err:
client.get("/model/modelX")
assert err.value.errors() == [
- IsDict(
- {
- "type": "value_error",
- "loc": ("response", "name"),
- "msg": "Value error, name must end in A",
- "input": "modelX",
- "ctx": {"error": HasRepr("ValueError('name must end in A')")},
- }
- )
- | IsDict(
- # TODO remove when deprecating Pydantic v1
- {
- "loc": ("response", "name"),
- "msg": "name must end in A",
- "type": "value_error",
- }
- )
+ {
+ "type": "value_error",
+ "loc": ("response", "name"),
+ "msg": "Value error, name must end in A",
+ "input": "modelX",
+ "ctx": {"error": HasRepr("ValueError('name must end in A')")},
+ }
]
-@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/model/{name}": {
- "get": {
- "summary": "Get Model A",
- "operationId": "get_model_a_model__name__get",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Name", "type": "string"},
- "name": "name",
- "in": "path",
- }
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/ModelA"}
- }
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/model/{name}": {
+ "get": {
+ "summary": "Get Model A",
+ "operationId": "get_model_a_model__name__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Name", "type": "string"},
+ "name": "name",
+ "in": "path",
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ModelA"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
},
},
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
+ },
+ },
+ "ModelA": {
+ "title": "ModelA",
+ "required": ["name", "foo"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
},
+ "foo": {"$ref": "#/components/schemas/ModelB"},
+ "tags": {
+ "additionalProperties": {"type": "string"},
+ "type": "object",
+ "title": "Tags",
+ "default": {},
+ },
+ },
+ },
+ "ModelB": {
+ "title": "ModelB",
+ "required": ["username"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"}
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
},
},
}
- }
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ModelA": {
- "title": "ModelA",
- "required": IsOneOf(
- ["name", "description", "foo"],
- # TODO remove when deprecating Pydantic v1
- ["name", "foo"],
- ),
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- |
- # TODO remove when deprecating Pydantic v1
- IsDict({"title": "Description", "type": "string"}),
- "foo": {"$ref": "#/components/schemas/ModelB"},
- },
- },
- "ModelB": {
- "title": "ModelB",
- "required": ["username"],
- "type": "object",
- "properties": {"username": {"title": "Username", "type": "string"}},
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_form_default.py b/tests/test_form_default.py
new file mode 100644
index 0000000000..0b3eb8f2e2
--- /dev/null
+++ b/tests/test_form_default.py
@@ -0,0 +1,34 @@
+from typing import Annotated, Optional
+
+from fastapi import FastAPI, File, Form
+from starlette.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.post("/urlencoded")
+async def post_url_encoded(age: Annotated[Optional[int], Form()] = None):
+ return age
+
+
+@app.post("/multipart")
+async def post_multi_part(
+ age: Annotated[Optional[int], Form()] = None,
+ file: Annotated[Optional[bytes], File()] = None,
+):
+ return {"file": file, "age": age}
+
+
+client = TestClient(app)
+
+
+def test_form_default_url_encoded():
+ response = client.post("/urlencoded", data={"age": ""})
+ assert response.status_code == 200
+ assert response.text == "null"
+
+
+def test_form_default_multi_part():
+ response = client.post("/multipart", data={"age": ""})
+ assert response.status_code == 200
+ assert response.json() == {"file": None, "age": None}
diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py
index 880ab38200..7d03d29572 100644
--- a/tests/test_forms_single_model.py
+++ b/tests/test_forms_single_model.py
@@ -1,10 +1,8 @@
-from typing import List, Optional
+from typing import Annotated, Optional
-from dirty_equals import IsDict
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
-from typing_extensions import Annotated
app = FastAPI()
@@ -13,15 +11,26 @@ class FormModel(BaseModel):
username: str
lastname: str
age: Optional[int] = None
- tags: List[str] = ["foo", "bar"]
+ tags: list[str] = ["foo", "bar"]
alias_with: str = Field(alias="with", default="nothing")
+class FormModelExtraAllow(BaseModel):
+ param: str
+
+ model_config = {"extra": "allow"}
+
+
@app.post("/form/")
def post_form(user: Annotated[FormModel, Form()]):
return user
+@app.post("/form-extra-allow/")
+def post_form_extra_allow(params: Annotated[FormModelExtraAllow, Form()]):
+ return params
+
+
client = TestClient(app)
@@ -69,65 +78,64 @@ def test_invalid_data():
},
)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["body", "age"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "seventy",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "age"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["body", "age"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "seventy",
+ }
+ ]
+ }
def test_no_data():
response = client.post("/form/")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": {"tags": ["foo", "bar"], "with": "nothing"},
- },
- {
- "type": "missing",
- "loc": ["body", "lastname"],
- "msg": "Field required",
- "input": {"tags": ["foo", "bar"], "with": "nothing"},
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "lastname"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {"tags": ["foo", "bar"], "with": "nothing"},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "lastname"],
+ "msg": "Field required",
+ "input": {"tags": ["foo", "bar"], "with": "nothing"},
+ },
+ ]
+ }
+
+
+def test_extra_param_single():
+ response = client.post(
+ "/form-extra-allow/",
+ data={
+ "param": "123",
+ "extra_param": "456",
+ },
)
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "param": "123",
+ "extra_param": "456",
+ }
+
+
+def test_extra_param_list():
+ response = client.post(
+ "/form-extra-allow/",
+ data={
+ "param": "123",
+ "extra_params": ["456", "789"],
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "param": "123",
+ "extra_params": ["456", "789"],
+ }
diff --git a/tests/test_forms_single_param.py b/tests/test_forms_single_param.py
index 3bb951441f..67f054b34e 100644
--- a/tests/test_forms_single_param.py
+++ b/tests/test_forms_single_param.py
@@ -1,6 +1,7 @@
+from typing import Annotated
+
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
app = FastAPI()
diff --git a/tests/test_generate_unique_id_function.py b/tests/test_generate_unique_id_function.py
index 5aeec66367..62ebfbc964 100644
--- a/tests/test_generate_unique_id_function.py
+++ b/tests/test_generate_unique_id_function.py
@@ -1,5 +1,4 @@
import warnings
-from typing import List
from fastapi import APIRouter, FastAPI
from fastapi.routing import APIRoute
@@ -33,12 +32,12 @@ def test_top_level_generate_unique_id():
app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
router = APIRouter()
- @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ @app.post("/", response_model=list[Item], responses={404: {"model": list[Message]}})
def post_root(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
@router.post(
- "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ "/router", response_model=list[Item], responses={404: {"model": list[Message]}}
)
def post_router(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
@@ -234,12 +233,12 @@ def test_router_overrides_generate_unique_id():
app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
- @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ @app.post("/", response_model=list[Item], responses={404: {"model": list[Message]}})
def post_root(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
@router.post(
- "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ "/router", response_model=list[Item], responses={404: {"model": list[Message]}}
)
def post_router(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
@@ -435,12 +434,12 @@ def test_router_include_overrides_generate_unique_id():
app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
- @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ @app.post("/", response_model=list[Item], responses={404: {"model": list[Message]}})
def post_root(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
@router.post(
- "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ "/router", response_model=list[Item], responses={404: {"model": list[Message]}}
)
def post_router(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
@@ -637,20 +636,20 @@ def test_subrouter_top_level_include_overrides_generate_unique_id():
router = APIRouter()
sub_router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
- @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ @app.post("/", response_model=list[Item], responses={404: {"model": list[Message]}})
def post_root(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
@router.post(
- "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ "/router", response_model=list[Item], responses={404: {"model": list[Message]}}
)
def post_router(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
@sub_router.post(
"/subrouter",
- response_model=List[Item],
- responses={404: {"model": List[Message]}},
+ response_model=list[Item],
+ responses={404: {"model": list[Message]}},
)
def post_subrouter(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
@@ -910,14 +909,14 @@ def test_router_path_operation_overrides_generate_unique_id():
app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
- @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ @app.post("/", response_model=list[Item], responses={404: {"model": list[Message]}})
def post_root(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
@router.post(
"/router",
- response_model=List[Item],
- responses={404: {"model": List[Message]}},
+ response_model=list[Item],
+ responses={404: {"model": list[Message]}},
generate_unique_id_function=custom_generate_unique_id3,
)
def post_router(item1: Item, item2: Item):
@@ -1116,8 +1115,8 @@ def test_app_path_operation_overrides_generate_unique_id():
@app.post(
"/",
- response_model=List[Item],
- responses={404: {"model": List[Message]}},
+ response_model=list[Item],
+ responses={404: {"model": list[Message]}},
generate_unique_id_function=custom_generate_unique_id3,
)
def post_root(item1: Item, item2: Item):
@@ -1125,8 +1124,8 @@ def test_app_path_operation_overrides_generate_unique_id():
@router.post(
"/router",
- response_model=List[Item],
- responses={404: {"model": List[Message]}},
+ response_model=list[Item],
+ responses={404: {"model": list[Message]}},
)
def post_router(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
@@ -1324,8 +1323,8 @@ def test_callback_override_generate_unique_id():
@callback_router.post(
"/post-callback",
- response_model=List[Item],
- responses={404: {"model": List[Message]}},
+ response_model=list[Item],
+ responses={404: {"model": list[Message]}},
generate_unique_id_function=custom_generate_unique_id3,
)
def post_callback(item1: Item, item2: Item):
@@ -1333,8 +1332,8 @@ def test_callback_override_generate_unique_id():
@app.post(
"/",
- response_model=List[Item],
- responses={404: {"model": List[Message]}},
+ response_model=list[Item],
+ responses={404: {"model": list[Message]}},
generate_unique_id_function=custom_generate_unique_id3,
callbacks=callback_router.routes,
)
@@ -1343,8 +1342,8 @@ def test_callback_override_generate_unique_id():
@app.post(
"/tocallback",
- response_model=List[Item],
- responses={404: {"model": List[Message]}},
+ response_model=list[Item],
+ responses={404: {"model": list[Message]}},
)
def post_with_callback(item1: Item, item2: Item):
return item1, item2 # pragma: nocover
diff --git a/tests/test_generic_parameterless_depends.py b/tests/test_generic_parameterless_depends.py
index 5aa35320c9..93b72ad243 100644
--- a/tests/test_generic_parameterless_depends.py
+++ b/tests/test_generic_parameterless_depends.py
@@ -1,8 +1,7 @@
-from typing import TypeVar
+from typing import Annotated, TypeVar
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
app = FastAPI()
diff --git a/tests/test_get_model_definitions_formfeed_escape.py b/tests/test_get_model_definitions_formfeed_escape.py
index 6601585ef0..eb7939b69a 100644
--- a/tests/test_get_model_definitions_formfeed_escape.py
+++ b/tests/test_get_model_definitions_formfeed_escape.py
@@ -1,180 +1,158 @@
-from typing import Any, Iterator, Set, Type
-
-import fastapi._compat
-import fastapi.openapi.utils
-import pydantic.schema
import pytest
from fastapi import FastAPI
-from pydantic import BaseModel
-from starlette.testclient import TestClient
-
-from .utils import needs_pydanticv1
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
-class Address(BaseModel):
- """
- This is a public description of an Address
- \f
- You can't see this part of the docstring, it's private!
- """
+@pytest.fixture(name="client")
+def client_fixture() -> TestClient:
+ from pydantic import BaseModel
- line_1: str
- city: str
- state_province: str
+ class Address(BaseModel):
+ """
+ This is a public description of an Address
+ \f
+ You can't see this part of the docstring, it's private!
+ """
+
+ line_1: str
+ city: str
+ state_province: str
+
+ class Facility(BaseModel):
+ id: str
+ address: Address
+
+ app = FastAPI()
+
+ @app.get("/facilities/{facility_id}")
+ def get_facility(facility_id: str) -> Facility:
+ return Facility(
+ id=facility_id,
+ address=Address(line_1="123 Main St", city="Anytown", state_province="CA"),
+ )
+
+ client = TestClient(app)
+ return client
-class Facility(BaseModel):
- id: str
- address: Address
+def test_get(client: TestClient):
+ response = client.get("/facilities/42")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "id": "42",
+ "address": {
+ "line_1": "123 Main St",
+ "city": "Anytown",
+ "state_province": "CA",
+ },
+ }
-app = FastAPI()
-
-client = TestClient(app)
-
-
-@app.get("/facilities/{facility_id}")
-def get_facility(facility_id: str) -> Facility: ...
-
-
-openapi_schema = {
- "components": {
- "schemas": {
- "Address": {
- # NOTE: the description of this model shows only the public-facing text, before the `\f` in docstring
- "description": "This is a public description of an Address\n",
- "properties": {
- "city": {"title": "City", "type": "string"},
- "line_1": {"title": "Line 1", "type": "string"},
- "state_province": {"title": "State Province", "type": "string"},
- },
- "required": ["line_1", "city", "state_province"],
- "title": "Address",
- "type": "object",
- },
- "Facility": {
- "properties": {
- "address": {"$ref": "#/components/schemas/Address"},
- "id": {"title": "Id", "type": "string"},
- },
- "required": ["id", "address"],
- "title": "Facility",
- "type": "object",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "title": "Detail",
- "type": "array",
- }
- },
- "title": "HTTPValidationError",
- "type": "object",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
- "title": "Location",
- "type": "array",
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- "type": "object",
- },
- }
- },
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "openapi": "3.1.0",
- "paths": {
- "/facilities/{facility_id}": {
- "get": {
- "operationId": "get_facility_facilities__facility_id__get",
- "parameters": [
- {
- "in": "path",
- "name": "facility_id",
- "required": True,
- "schema": {"title": "Facility Id", "type": "string"},
- }
- ],
- "responses": {
- "200": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Facility"}
- }
- },
- "description": "Successful Response",
- },
- "422": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- "description": "Validation Error",
- },
- },
- "summary": "Get Facility",
- }
- }
- },
-}
-
-
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
"""
Sanity check to ensure our app's openapi schema renders as we expect
"""
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == openapi_schema
-
-
-class SortedTypeSet(set):
- """
- Set of Types whose `__iter__()` method yields results sorted by the type names
- """
-
- def __init__(self, seq: Set[Type[Any]], *, sort_reversed: bool):
- super().__init__(seq)
- self.sort_reversed = sort_reversed
-
- def __iter__(self) -> Iterator[Type[Any]]:
- members_sorted = sorted(
- super().__iter__(),
- key=lambda type_: type_.__name__,
- reverse=self.sort_reversed,
- )
- yield from members_sorted
-
-
-@needs_pydanticv1
-@pytest.mark.parametrize("sort_reversed", [True, False])
-def test_model_description_escaped_with_formfeed(sort_reversed: bool):
- """
- Regression test for bug fixed by https://github.com/fastapi/fastapi/pull/6039.
-
- Test `get_model_definitions` with models passed in different order.
- """
- from fastapi._compat import v1
-
- all_fields = fastapi.openapi.utils.get_fields_from_routes(app.routes)
-
- flat_models = v1.get_flat_models_from_fields(all_fields, known_models=set())
- model_name_map = pydantic.schema.get_model_name_map(flat_models)
-
- expected_address_description = "This is a public description of an Address\n"
-
- models = v1.get_model_definitions(
- flat_models=SortedTypeSet(flat_models, sort_reversed=sort_reversed),
- model_name_map=model_name_map,
+ assert response.json() == snapshot(
+ {
+ "components": {
+ "schemas": {
+ "Address": {
+ # NOTE: the description of this model shows only the public-facing text, before the `\f` in docstring
+ "description": "This is a public description of an Address\n",
+ "properties": {
+ "city": {"title": "City", "type": "string"},
+ "line_1": {"title": "Line 1", "type": "string"},
+ "state_province": {
+ "title": "State Province",
+ "type": "string",
+ },
+ },
+ "required": ["line_1", "city", "state_province"],
+ "title": "Address",
+ "type": "object",
+ },
+ "Facility": {
+ "properties": {
+ "address": {"$ref": "#/components/schemas/Address"},
+ "id": {"title": "Id", "type": "string"},
+ },
+ "required": ["id", "address"],
+ "title": "Facility",
+ "type": "object",
+ },
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "title": "Detail",
+ "type": "array",
+ }
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ }
+ },
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "openapi": "3.1.0",
+ "paths": {
+ "/facilities/{facility_id}": {
+ "get": {
+ "operationId": "get_facility_facilities__facility_id__get",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "facility_id",
+ "required": True,
+ "schema": {"title": "Facility Id", "type": "string"},
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Facility"
+ }
+ }
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Get Facility",
+ }
+ }
+ },
+ }
)
- assert models["Address"]["description"] == expected_address_description
diff --git a/tests/test_infer_param_optionality.py b/tests/test_infer_param_optionality.py
index e3d57bb428..147018996e 100644
--- a/tests/test_infer_param_optionality.py
+++ b/tests/test_infer_param_optionality.py
@@ -1,6 +1,5 @@
from typing import Optional
-from dirty_equals import IsDict
from fastapi import APIRouter, FastAPI
from fastapi.testclient import TestClient
@@ -163,16 +162,10 @@ def test_openapi_schema():
"required": False,
"name": "user_id",
"in": "query",
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "User Id",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "User Id", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "User Id",
+ },
}
],
"responses": {
@@ -208,16 +201,10 @@ def test_openapi_schema():
"required": False,
"name": "user_id",
"in": "query",
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "User Id",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "User Id", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "User Id",
+ },
},
],
"responses": {
@@ -247,16 +234,10 @@ def test_openapi_schema():
"required": True,
"name": "user_id",
"in": "path",
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "User Id",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "User Id", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "User Id",
+ },
}
],
"responses": {
@@ -292,16 +273,10 @@ def test_openapi_schema():
"required": True,
"name": "user_id",
"in": "path",
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "User Id",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "User Id", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "User Id",
+ },
},
],
"responses": {
diff --git a/tests/test_inherited_custom_class.py b/tests/test_inherited_custom_class.py
index fe9350f4ef..8cf8952f92 100644
--- a/tests/test_inherited_custom_class.py
+++ b/tests/test_inherited_custom_class.py
@@ -5,8 +5,6 @@ from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
-from .utils import needs_pydanticv1, needs_pydanticv2
-
class MyUuid:
def __init__(self, uuid_string: str):
@@ -26,7 +24,6 @@ class MyUuid:
raise TypeError("vars() argument must have __dict__ attribute")
-@needs_pydanticv2
def test_pydanticv2():
from pydantic import field_serializer
@@ -68,44 +65,3 @@ def test_pydanticv2():
assert response_pydantic.json() == {
"a_uuid": "b8799909-f914-42de-91bc-95c819218d01"
}
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_pydanticv1():
- app = FastAPI()
-
- @app.get("/fast_uuid")
- def return_fast_uuid():
- asyncpg_uuid = MyUuid("a10ff360-3b1e-4984-a26f-d3ab460bdb51")
- assert isinstance(asyncpg_uuid, uuid.UUID)
- assert type(asyncpg_uuid) is not uuid.UUID
- with pytest.raises(TypeError):
- vars(asyncpg_uuid)
- return {"fast_uuid": asyncpg_uuid}
-
- class SomeCustomClass(BaseModel):
- class Config:
- arbitrary_types_allowed = True
- json_encoders = {uuid.UUID: str}
-
- a_uuid: MyUuid
-
- @app.get("/get_custom_class")
- def return_some_user():
- # Test that the fix also works for custom pydantic classes
- return SomeCustomClass(a_uuid=MyUuid("b8799909-f914-42de-91bc-95c819218d01"))
-
- client = TestClient(app)
-
- with client:
- response_simple = client.get("/fast_uuid")
- response_pydantic = client.get("/get_custom_class")
-
- assert response_simple.json() == {
- "fast_uuid": "a10ff360-3b1e-4984-a26f-d3ab460bdb51"
- }
-
- assert response_pydantic.json() == {
- "a_uuid": "b8799909-f914-42de-91bc-95c819218d01"
- }
diff --git a/tests/test_invalid_path_param.py b/tests/test_invalid_path_param.py
index d5fa53c1fc..35c00363da 100644
--- a/tests/test_invalid_path_param.py
+++ b/tests/test_invalid_path_param.py
@@ -1,5 +1,3 @@
-from typing import Dict, List, Tuple
-
import pytest
from fastapi import FastAPI
from pydantic import BaseModel
@@ -13,7 +11,7 @@ def test_invalid_sequence():
title: str
@app.get("/items/{id}")
- def read_items(id: List[Item]):
+ def read_items(id: list[Item]):
pass # pragma: no cover
@@ -25,7 +23,7 @@ def test_invalid_tuple():
title: str
@app.get("/items/{id}")
- def read_items(id: Tuple[Item, Item]):
+ def read_items(id: tuple[Item, Item]):
pass # pragma: no cover
@@ -37,7 +35,7 @@ def test_invalid_dict():
title: str
@app.get("/items/{id}")
- def read_items(id: Dict[str, Item]):
+ def read_items(id: dict[str, Item]):
pass # pragma: no cover
diff --git a/tests/test_invalid_sequence_param.py b/tests/test_invalid_sequence_param.py
index 475786adbf..2b8fd059e1 100644
--- a/tests/test_invalid_sequence_param.py
+++ b/tests/test_invalid_sequence_param.py
@@ -1,4 +1,4 @@
-from typing import Dict, List, Optional, Tuple
+from typing import Optional
import pytest
from fastapi import FastAPI, Query
@@ -13,7 +13,7 @@ def test_invalid_sequence():
title: str
@app.get("/items/")
- def read_items(q: List[Item] = Query(default=None)):
+ def read_items(q: list[Item] = Query(default=None)):
pass # pragma: no cover
@@ -25,7 +25,7 @@ def test_invalid_tuple():
title: str
@app.get("/items/")
- def read_items(q: Tuple[Item, Item] = Query(default=None)):
+ def read_items(q: tuple[Item, Item] = Query(default=None)):
pass # pragma: no cover
@@ -37,7 +37,7 @@ def test_invalid_dict():
title: str
@app.get("/items/")
- def read_items(q: Dict[str, Item] = Query(default=None)):
+ def read_items(q: dict[str, Item] = Query(default=None)):
pass # pragma: no cover
diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py
index 447c5b4d6a..4528dff440 100644
--- a/tests/test_jsonable_encoder.py
+++ b/tests/test_jsonable_encoder.py
@@ -1,18 +1,19 @@
+import warnings
from collections import deque
from dataclasses import dataclass
from datetime import datetime, timezone
from decimal import Decimal
from enum import Enum
+from math import isinf, isnan
from pathlib import PurePath, PurePosixPath, PureWindowsPath
-from typing import Optional
+from typing import Optional, TypedDict
import pytest
-from fastapi._compat import PYDANTIC_V2, Undefined
+from fastapi._compat import Undefined
from fastapi.encoders import jsonable_encoder
+from fastapi.exceptions import PydanticV1NotSupportedError
from pydantic import BaseModel, Field, ValidationError
-from .utils import needs_pydanticv1, needs_pydanticv2
-
class Person:
def __init__(self, name: str):
@@ -58,12 +59,7 @@ class RoleEnum(Enum):
class ModelWithConfig(BaseModel):
role: Optional[RoleEnum] = None
- if PYDANTIC_V2:
- model_config = {"use_enum_values": True}
- else:
-
- class Config:
- use_enum_values = True
+ model_config = {"use_enum_values": True}
class ModelWithAlias(BaseModel):
@@ -88,6 +84,18 @@ def test_encode_dict():
}
+def test_encode_dict_include_exclude_list():
+ pet = {"name": "Firulais", "owner": {"name": "Foo"}}
+ assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}}
+ assert jsonable_encoder(pet, include=["name"]) == {"name": "Firulais"}
+ assert jsonable_encoder(pet, exclude=["owner"]) == {"name": "Firulais"}
+ assert jsonable_encoder(pet, include=[]) == {}
+ assert jsonable_encoder(pet, exclude=[]) == {
+ "name": "Firulais",
+ "owner": {"name": "Foo"},
+ }
+
+
def test_encode_class():
person = Person(name="Foo")
pet = Pet(owner=person, name="Firulais")
@@ -129,7 +137,6 @@ def test_encode_unsupported():
jsonable_encoder(unserializable)
-@needs_pydanticv2
def test_encode_custom_json_encoders_model_pydanticv2():
from pydantic import field_serializer
@@ -149,27 +156,17 @@ def test_encode_custom_json_encoders_model_pydanticv2():
assert jsonable_encoder(subclass_model) == {"dt_field": "2019-01-01T08:00:00+00:00"}
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_encode_custom_json_encoders_model_pydanticv1():
- class ModelWithCustomEncoder(BaseModel):
- dt_field: datetime
+def test_json_encoder_error_with_pydanticv1():
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", UserWarning)
+ from pydantic import v1
- class Config:
- json_encoders = {
- datetime: lambda dt: dt.replace(
- microsecond=0, tzinfo=timezone.utc
- ).isoformat()
- }
+ class ModelV1(v1.BaseModel):
+ name: str
- class ModelWithCustomEncoderSubclass(ModelWithCustomEncoder):
- class Config:
- pass
-
- model = ModelWithCustomEncoder(dt_field=datetime(2019, 1, 1, 8))
- assert jsonable_encoder(model) == {"dt_field": "2019-01-01T08:00:00+00:00"}
- subclass_model = ModelWithCustomEncoderSubclass(dt_field=datetime(2019, 1, 1, 8))
- assert jsonable_encoder(subclass_model) == {"dt_field": "2019-01-01T08:00:00+00:00"}
+ data = ModelV1(name="test")
+ with pytest.raises(PydanticV1NotSupportedError):
+ jsonable_encoder(data)
def test_encode_model_with_config():
@@ -205,23 +202,27 @@ def test_encode_model_with_default():
}
-@needs_pydanticv1
def test_custom_encoders():
class safe_datetime(datetime):
pass
- class MyModel(BaseModel):
+ class MyDict(TypedDict):
dt_field: safe_datetime
- instance = MyModel(dt_field=safe_datetime.now())
+ instance = MyDict(dt_field=safe_datetime.now())
encoded_instance = jsonable_encoder(
instance, custom_encoder={safe_datetime: lambda o: o.strftime("%H:%M:%S")}
)
- assert encoded_instance["dt_field"] == instance.dt_field.strftime("%H:%M:%S")
+ assert encoded_instance["dt_field"] == instance["dt_field"].strftime("%H:%M:%S")
+
+ encoded_instance = jsonable_encoder(
+ instance, custom_encoder={datetime: lambda o: o.strftime("%H:%M:%S")}
+ )
+ assert encoded_instance["dt_field"] == instance["dt_field"].strftime("%H:%M:%S")
encoded_instance2 = jsonable_encoder(instance)
- assert encoded_instance2["dt_field"] == instance.dt_field.isoformat()
+ assert encoded_instance2["dt_field"] == instance["dt_field"].isoformat()
def test_custom_enum_encoders():
@@ -243,12 +244,7 @@ def test_encode_model_with_pure_path():
class ModelWithPath(BaseModel):
path: PurePath
- if PYDANTIC_V2:
- model_config = {"arbitrary_types_allowed": True}
- else:
-
- class Config:
- arbitrary_types_allowed = True
+ model_config = {"arbitrary_types_allowed": True}
test_path = PurePath("/foo", "bar")
obj = ModelWithPath(path=test_path)
@@ -259,12 +255,7 @@ def test_encode_model_with_pure_posix_path():
class ModelWithPath(BaseModel):
path: PurePosixPath
- if PYDANTIC_V2:
- model_config = {"arbitrary_types_allowed": True}
- else:
-
- class Config:
- arbitrary_types_allowed = True
+ model_config = {"arbitrary_types_allowed": True}
obj = ModelWithPath(path=PurePosixPath("/foo", "bar"))
assert jsonable_encoder(obj) == {"path": "/foo/bar"}
@@ -274,38 +265,40 @@ def test_encode_model_with_pure_windows_path():
class ModelWithPath(BaseModel):
path: PureWindowsPath
- if PYDANTIC_V2:
- model_config = {"arbitrary_types_allowed": True}
- else:
-
- class Config:
- arbitrary_types_allowed = True
+ model_config = {"arbitrary_types_allowed": True}
obj = ModelWithPath(path=PureWindowsPath("/foo", "bar"))
assert jsonable_encoder(obj) == {"path": "\\foo\\bar"}
-@needs_pydanticv1
-def test_encode_root():
- class ModelWithRoot(BaseModel):
- __root__: str
+def test_encode_pure_path():
+ test_path = PurePath("/foo", "bar")
- model = ModelWithRoot(__root__="Foo")
- assert jsonable_encoder(model) == "Foo"
+ assert jsonable_encoder({"path": test_path}) == {"path": str(test_path)}
-@needs_pydanticv2
def test_decimal_encoder_float():
data = {"value": Decimal(1.23)}
assert jsonable_encoder(data) == {"value": 1.23}
-@needs_pydanticv2
def test_decimal_encoder_int():
data = {"value": Decimal(2)}
assert jsonable_encoder(data) == {"value": 2}
+def test_decimal_encoder_nan():
+ data = {"value": Decimal("NaN")}
+ assert isnan(jsonable_encoder(data)["value"])
+
+
+def test_decimal_encoder_infinity():
+ data = {"value": Decimal("Infinity")}
+ assert isinf(jsonable_encoder(data)["value"])
+ data = {"value": Decimal("-Infinity")}
+ assert isinf(jsonable_encoder(data)["value"])
+
+
def test_encode_deque_encodes_child_models():
class Model(BaseModel):
test: str
@@ -315,7 +308,6 @@ def test_encode_deque_encodes_child_models():
assert jsonable_encoder(dq)[0]["test"] == "test"
-@needs_pydanticv2
def test_encode_pydantic_undefined():
data = {"value": Undefined}
assert jsonable_encoder(data) == {"value": None}
diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py
index 33304827a9..4418c77cb0 100644
--- a/tests/test_multi_body_errors.py
+++ b/tests/test_multi_body_errors.py
@@ -1,9 +1,9 @@
from decimal import Decimal
-from typing import List
-from dirty_equals import IsDict, IsOneOf
+from dirty_equals import IsOneOf
from fastapi import FastAPI
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from pydantic import BaseModel, condecimal
app = FastAPI()
@@ -15,7 +15,7 @@ class Item(BaseModel):
@app.post("/items/")
-def save_item_no_body(item: List[Item]):
+def save_item_no_body(item: list[Item]):
return {"item": item}
@@ -25,109 +25,65 @@ client = TestClient(app)
def test_put_correct_body():
response = client.post("/items/", json=[{"name": "Foo", "age": 5}])
assert response.status_code == 200, response.text
- assert response.json() == {
- "item": [
- {
- "name": "Foo",
- "age": IsOneOf(
- 5,
- # TODO: remove when deprecating Pydantic v1
- "5",
- ),
- }
- ]
- }
+ assert response.json() == snapshot(
+ {
+ "item": [
+ {
+ "name": "Foo",
+ "age": "5",
+ }
+ ]
+ }
+ )
def test_jsonable_encoder_requiring_error():
response = client.post("/items/", json=[{"name": "Foo", "age": -1.0}])
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["body", 0, "age"],
- "msg": "Input should be greater than 0",
- "input": -1.0,
- "ctx": {"gt": 0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"limit_value": 0.0},
- "loc": ["body", 0, "age"],
- "msg": "ensure this value is greater than 0",
- "type": "value_error.number.not_gt",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["body", 0, "age"],
+ "msg": "Input should be greater than 0",
+ "input": -1.0,
+ "ctx": {"gt": 0},
+ }
+ ]
+ }
def test_put_incorrect_body_multiple():
response = client.post("/items/", json=[{"age": "five"}, {"age": "six"}])
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", 0, "name"],
- "msg": "Field required",
- "input": {"age": "five"},
- },
- {
- "type": "decimal_parsing",
- "loc": ["body", 0, "age"],
- "msg": "Input should be a valid decimal",
- "input": "five",
- },
- {
- "type": "missing",
- "loc": ["body", 1, "name"],
- "msg": "Field required",
- "input": {"age": "six"},
- },
- {
- "type": "decimal_parsing",
- "loc": ["body", 1, "age"],
- "msg": "Input should be a valid decimal",
- "input": "six",
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", 0, "name"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", 0, "age"],
- "msg": "value is not a valid decimal",
- "type": "type_error.decimal",
- },
- {
- "loc": ["body", 1, "name"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", 1, "age"],
- "msg": "value is not a valid decimal",
- "type": "type_error.decimal",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", 0, "name"],
+ "msg": "Field required",
+ "input": {"age": "five"},
+ },
+ {
+ "type": "decimal_parsing",
+ "loc": ["body", 0, "age"],
+ "msg": "Input should be a valid decimal",
+ "input": "five",
+ },
+ {
+ "type": "missing",
+ "loc": ["body", 1, "name"],
+ "msg": "Field required",
+ "input": {"age": "six"},
+ },
+ {
+ "type": "decimal_parsing",
+ "loc": ["body", 1, "age"],
+ "msg": "Input should be a valid decimal",
+ "input": "six",
+ },
+ ]
+ }
def test_openapi_schema():
@@ -180,31 +136,21 @@ def test_openapi_schema():
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "age": IsDict(
- {
- "title": "Age",
- "anyOf": [
- {"exclusiveMinimum": 0.0, "type": "number"},
- IsOneOf(
- # pydantic < 2.12.0
- {"type": "string"},
- # pydantic >= 2.12.0
- {
- "type": "string",
- "pattern": r"^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$",
- },
- ),
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Age",
- "exclusiveMinimum": 0.0,
- "type": "number",
- }
- ),
+ "age": {
+ "title": "Age",
+ "anyOf": [
+ {"exclusiveMinimum": 0.0, "type": "number"},
+ IsOneOf(
+ # pydantic < 2.12.0
+ {"type": "string"},
+ # pydantic >= 2.12.0
+ {
+ "type": "string",
+ "pattern": r"^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$",
+ },
+ ),
+ ],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_multi_query_errors.py b/tests/test_multi_query_errors.py
index 8162d986c5..5df51ba185 100644
--- a/tests/test_multi_query_errors.py
+++ b/tests/test_multi_query_errors.py
@@ -1,6 +1,3 @@
-from typing import List
-
-from dirty_equals import IsDict
from fastapi import FastAPI, Query
from fastapi.testclient import TestClient
@@ -8,7 +5,7 @@ app = FastAPI()
@app.get("/items/")
-def read_items(q: List[int] = Query(default=None)):
+def read_items(q: list[int] = Query(default=None)):
return {"q": q}
@@ -24,40 +21,22 @@ def test_multi_query():
def test_multi_query_incorrect():
response = client.get("/items/?q=five&q=six")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "q", 0],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "five",
- },
- {
- "type": "int_parsing",
- "loc": ["query", "q", 1],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "six",
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "q", 0],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- },
- {
- "loc": ["query", "q", 1],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "q", 0],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "five",
+ },
+ {
+ "type": "int_parsing",
+ "loc": ["query", "q", 1],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "six",
+ },
+ ]
+ }
def test_openapi_schema():
diff --git a/tests/test_no_schema_split.py b/tests/test_no_schema_split.py
index b0b5958c1b..131a3755e7 100644
--- a/tests/test_no_schema_split.py
+++ b/tests/test_no_schema_split.py
@@ -3,15 +3,12 @@
# Made an issue in:
# https://github.com/fastapi/fastapi/issues/14247
from enum import Enum
-from typing import List
from fastapi import FastAPI
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from pydantic import BaseModel, Field
-from tests.utils import pydantic_snapshot
-
class MessageEventType(str, Enum):
alpha = "alpha"
@@ -25,7 +22,7 @@ class MessageEvent(BaseModel):
class MessageOutput(BaseModel):
body: str = ""
- events: List[MessageEvent] = []
+ events: list[MessageEvent] = []
class Message(BaseModel):
@@ -127,47 +124,21 @@ def test_openapi_schema():
},
"MessageEvent": {
"properties": {
- "event_type": pydantic_snapshot(
- v2=snapshot(
- {
- "$ref": "#/components/schemas/MessageEventType",
- "default": "alpha",
- }
- ),
- v1=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/MessageEventType"
- }
- ],
- "default": "alpha",
- }
- ),
- ),
+ "event_type": {
+ "$ref": "#/components/schemas/MessageEventType",
+ "default": "alpha",
+ },
"output": {"type": "string", "title": "Output"},
},
"type": "object",
"required": ["output"],
"title": "MessageEvent",
},
- "MessageEventType": pydantic_snapshot(
- v2=snapshot(
- {
- "type": "string",
- "enum": ["alpha", "beta"],
- "title": "MessageEventType",
- }
- ),
- v1=snapshot(
- {
- "type": "string",
- "enum": ["alpha", "beta"],
- "title": "MessageEventType",
- "description": "An enumeration.",
- }
- ),
- ),
+ "MessageEventType": {
+ "type": "string",
+ "enum": ["alpha", "beta"],
+ "title": "MessageEventType",
+ },
"MessageOutput": {
"properties": {
"body": {"type": "string", "title": "Body", "default": ""},
diff --git a/tests/test_openapi_examples.py b/tests/test_openapi_examples.py
index b3f83ae237..bd0d55452e 100644
--- a/tests/test_openapi_examples.py
+++ b/tests/test_openapi_examples.py
@@ -1,6 +1,5 @@
from typing import Union
-from dirty_equals import IsDict
from fastapi import Body, Cookie, FastAPI, Header, Path, Query
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -155,26 +154,12 @@ def test_openapi_schema():
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "$ref": "#/components/schemas/Item",
- "examples": [
- {"data": "Data in Body examples, example1"}
- ],
- }
- )
- | IsDict(
- {
- # TODO: remove when deprecating Pydantic v1
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- "examples": [
- {"data": "Data in Body examples, example1"}
- ],
- }
- ),
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ "examples": [
+ {"data": "Data in Body examples, example1"}
+ ],
+ },
"examples": {
"Example One": {
"summary": "Example One Summary",
@@ -265,27 +250,14 @@ def test_openapi_schema():
"name": "data",
"in": "query",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "examples": [
- "json_schema_query1",
- "json_schema_query2",
- ],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "examples": [
- "json_schema_query1",
- "json_schema_query2",
- ],
- "type": "string",
- "title": "Data",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "examples": [
+ "json_schema_query1",
+ "json_schema_query2",
+ ],
+ "title": "Data",
+ },
"examples": {
"Query One": {
"summary": "Query One Summary",
@@ -323,27 +295,14 @@ def test_openapi_schema():
"name": "data",
"in": "header",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "examples": [
- "json_schema_header1",
- "json_schema_header2",
- ],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "examples": [
- "json_schema_header1",
- "json_schema_header2",
- ],
- "title": "Data",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "examples": [
+ "json_schema_header1",
+ "json_schema_header2",
+ ],
+ "title": "Data",
+ },
"examples": {
"Header One": {
"summary": "Header One Summary",
@@ -381,27 +340,14 @@ def test_openapi_schema():
"name": "data",
"in": "cookie",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "examples": [
- "json_schema_cookie1",
- "json_schema_cookie2",
- ],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "examples": [
- "json_schema_cookie1",
- "json_schema_cookie2",
- ],
- "title": "Data",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "examples": [
+ "json_schema_cookie1",
+ "json_schema_cookie2",
+ ],
+ "title": "Data",
+ },
"examples": {
"Cookie One": {
"summary": "Cookie One Summary",
diff --git a/tests/test_openapi_query_parameter_extension.py b/tests/test_openapi_query_parameter_extension.py
index dc7147c712..084cb695d4 100644
--- a/tests/test_openapi_query_parameter_extension.py
+++ b/tests/test_openapi_query_parameter_extension.py
@@ -1,6 +1,5 @@
from typing import Optional
-from dirty_equals import IsDict
from fastapi import FastAPI
from fastapi.testclient import TestClient
@@ -53,21 +52,11 @@ def test_openapi():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "integer"}, {"type": "null"}],
- "default": 50,
- "title": "Standard Query Param",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Standard Query Param",
- "type": "integer",
- "default": 50,
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "integer"}, {"type": "null"}],
+ "default": 50,
+ "title": "Standard Query Param",
+ },
"name": "standard_query_param",
"in": "query",
},
diff --git a/tests/test_openapi_schema_type.py b/tests/test_openapi_schema_type.py
index a45ea20c8c..98d9787455 100644
--- a/tests/test_openapi_schema_type.py
+++ b/tests/test_openapi_schema_type.py
@@ -1,4 +1,4 @@
-from typing import List, Optional, Union
+from typing import Optional, Union
import pytest
from fastapi.openapi.models import Schema, SchemaType
@@ -13,7 +13,7 @@ from fastapi.openapi.models import Schema, SchemaType
],
)
def test_allowed_schema_type(
- type_value: Optional[Union[SchemaType, List[SchemaType]]],
+ type_value: Optional[Union[SchemaType, list[SchemaType]]],
) -> None:
"""Test that Schema accepts SchemaType, List[SchemaType] and None for type field."""
schema = Schema(type=type_value)
diff --git a/tests/test_openapi_separate_input_output_schemas.py b/tests/test_openapi_separate_input_output_schemas.py
index fa73620eae..1891f0bde0 100644
--- a/tests/test_openapi_separate_input_output_schemas.py
+++ b/tests/test_openapi_separate_input_output_schemas.py
@@ -1,27 +1,32 @@
-from typing import List, Optional
+from typing import Optional
from fastapi import FastAPI
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from pydantic import BaseModel
-
-from .utils import PYDANTIC_V2, needs_pydanticv2
+from pydantic import BaseModel, computed_field
class SubItem(BaseModel):
subname: str
sub_description: Optional[str] = None
- tags: List[str] = []
- if PYDANTIC_V2:
- model_config = {"json_schema_serialization_defaults_required": True}
+ tags: list[str] = []
+ model_config = {"json_schema_serialization_defaults_required": True}
class Item(BaseModel):
name: str
description: Optional[str] = None
sub: Optional[SubItem] = None
- if PYDANTIC_V2:
- model_config = {"json_schema_serialization_defaults_required": True}
+ model_config = {"json_schema_serialization_defaults_required": True}
+
+
+class WithComputedField(BaseModel):
+ name: str
+
+ @computed_field
+ @property
+ def computed_field(self) -> str:
+ return f"computed {self.name}"
def get_app_client(separate_input_output_schemas: bool = True) -> TestClient:
@@ -32,11 +37,11 @@ def get_app_client(separate_input_output_schemas: bool = True) -> TestClient:
return item
@app.post("/items-list/")
- def create_item_list(item: List[Item]):
+ def create_item_list(item: list[Item]):
return item
@app.get("/items/")
- def read_items() -> List[Item]:
+ def read_items() -> list[Item]:
return [
Item(
name="Portal Gun",
@@ -46,6 +51,12 @@ def get_app_client(separate_input_output_schemas: bool = True) -> TestClient:
Item(name="Plumbus"),
]
+ @app.post("/with-computed-field/")
+ def create_with_computed_field(
+ with_computed_field: WithComputedField,
+ ) -> WithComputedField:
+ return with_computed_field
+
client = TestClient(app)
return client
@@ -131,7 +142,22 @@ def test_read_items():
)
-@needs_pydanticv2
+def test_with_computed_field():
+ client = get_app_client()
+ client_no = get_app_client(separate_input_output_schemas=False)
+ response = client.post("/with-computed-field/", json={"name": "example"})
+ response2 = client_no.post("/with-computed-field/", json={"name": "example"})
+ assert response.status_code == response2.status_code == 200, response.text
+ assert (
+ response.json()
+ == response2.json()
+ == {
+ "name": "example",
+ "computed_field": "computed example",
+ }
+ )
+
+
def test_openapi_schema():
client = get_app_client()
response = client.get("/openapi.json")
@@ -245,6 +271,44 @@ def test_openapi_schema():
},
}
},
+ "/with-computed-field/": {
+ "post": {
+ "summary": "Create With Computed Field",
+ "operationId": "create_with_computed_field_with_computed_field__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/WithComputedField-Input"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/WithComputedField-Output"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
},
"components": {
"schemas": {
@@ -333,6 +397,25 @@ def test_openapi_schema():
"required": ["subname", "sub_description", "tags"],
"title": "SubItem",
},
+ "WithComputedField-Input": {
+ "properties": {"name": {"type": "string", "title": "Name"}},
+ "type": "object",
+ "required": ["name"],
+ "title": "WithComputedField",
+ },
+ "WithComputedField-Output": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "computed_field": {
+ "type": "string",
+ "title": "Computed Field",
+ "readOnly": True,
+ },
+ },
+ "type": "object",
+ "required": ["name", "computed_field"],
+ "title": "WithComputedField",
+ },
"ValidationError": {
"properties": {
"loc": {
@@ -355,7 +438,6 @@ def test_openapi_schema():
)
-@needs_pydanticv2
def test_openapi_schema_no_separate():
client = get_app_client(separate_input_output_schemas=False)
response = client.get("/openapi.json")
@@ -458,6 +540,44 @@ def test_openapi_schema_no_separate():
},
}
},
+ "/with-computed-field/": {
+ "post": {
+ "summary": "Create With Computed Field",
+ "operationId": "create_with_computed_field_with_computed_field__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/WithComputedField-Input"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/WithComputedField-Output"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
},
"components": {
"schemas": {
@@ -508,6 +628,25 @@ def test_openapi_schema_no_separate():
"required": ["subname"],
"title": "SubItem",
},
+ "WithComputedField-Input": {
+ "properties": {"name": {"type": "string", "title": "Name"}},
+ "type": "object",
+ "required": ["name"],
+ "title": "WithComputedField",
+ },
+ "WithComputedField-Output": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "computed_field": {
+ "type": "string",
+ "title": "Computed Field",
+ "readOnly": True,
+ },
+ },
+ "type": "object",
+ "required": ["name", "computed_field"],
+ "title": "WithComputedField",
+ },
"ValidationError": {
"properties": {
"loc": {
diff --git a/tests/test_openapi_servers.py b/tests/test_openapi_servers.py
index 8697c8438b..33079e4b1d 100644
--- a/tests/test_openapi_servers.py
+++ b/tests/test_openapi_servers.py
@@ -1,6 +1,6 @@
-from dirty_equals import IsOneOf
from fastapi import FastAPI
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
app = FastAPI(
servers=[
@@ -30,39 +30,31 @@ def test_app():
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "servers": [
- {"url": "/", "description": "Default, relative server"},
- {
- "url": IsOneOf(
- "http://staging.localhost.tiangolo.com:8000/",
- # TODO: remove when deprecating Pydantic v1
- "http://staging.localhost.tiangolo.com:8000",
- ),
- "description": "Staging but actually localhost still",
- },
- {
- "url": IsOneOf(
- "https://prod.example.com/",
- # TODO: remove when deprecating Pydantic v1
- "https://prod.example.com",
- )
- },
- ],
- "paths": {
- "/foo": {
- "get": {
- "summary": "Foo",
- "operationId": "foo_foo_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "servers": [
+ {"url": "/", "description": "Default, relative server"},
+ {
+ "url": "http://staging.localhost.tiangolo.com:8000",
+ "description": "Staging but actually localhost still",
+ },
+ {"url": "https://prod.example.com"},
+ ],
+ "paths": {
+ "/foo": {
+ "get": {
+ "summary": "Foo",
+ "operationId": "foo_foo_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
}
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_optional_file_list.py b/tests/test_optional_file_list.py
new file mode 100644
index 0000000000..6860258643
--- /dev/null
+++ b/tests/test_optional_file_list.py
@@ -0,0 +1,30 @@
+from typing import Optional
+
+from fastapi import FastAPI, File
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.post("/files")
+async def upload_files(files: Optional[list[bytes]] = File(None)):
+ if files is None:
+ return {"files_count": 0}
+ return {"files_count": len(files), "sizes": [len(f) for f in files]}
+
+
+def test_optional_bytes_list():
+ client = TestClient(app)
+ response = client.post(
+ "/files",
+ files=[("files", b"content1"), ("files", b"content2")],
+ )
+ assert response.status_code == 200
+ assert response.json() == {"files_count": 2, "sizes": [8, 8]}
+
+
+def test_optional_bytes_list_no_files():
+ client = TestClient(app)
+ response = client.post("/files")
+ assert response.status_code == 200
+ assert response.json() == {"files_count": 0}
diff --git a/tests/test_params_repr.py b/tests/test_params_repr.py
index baa172497d..670e4f5ddf 100644
--- a/tests/test_params_repr.py
+++ b/tests/test_params_repr.py
@@ -1,9 +1,8 @@
-from typing import Any, List
+from typing import Any
-from dirty_equals import IsOneOf
from fastapi.params import Body, Cookie, Header, Param, Path, Query
-test_data: List[Any] = ["teststr", None, ..., 1, []]
+test_data: list[Any] = ["teststr", None, ..., 1, []]
def get_user():
@@ -19,11 +18,7 @@ def test_param_repr_none():
def test_param_repr_ellipsis():
- assert repr(Param(...)) == IsOneOf(
- "Param(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Param(Ellipsis)",
- )
+ assert repr(Param(...)) == "Param(PydanticUndefined)"
def test_param_repr_number():
@@ -35,16 +30,8 @@ def test_param_repr_list():
def test_path_repr():
- assert repr(Path()) == IsOneOf(
- "Path(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Path(Ellipsis)",
- )
- assert repr(Path(...)) == IsOneOf(
- "Path(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Path(Ellipsis)",
- )
+ assert repr(Path()) == "Path(PydanticUndefined)"
+ assert repr(Path(...)) == "Path(PydanticUndefined)"
def test_query_repr_str():
@@ -56,11 +43,7 @@ def test_query_repr_none():
def test_query_repr_ellipsis():
- assert repr(Query(...)) == IsOneOf(
- "Query(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Query(Ellipsis)",
- )
+ assert repr(Query(...)) == "Query(PydanticUndefined)"
def test_query_repr_number():
@@ -80,11 +63,7 @@ def test_header_repr_none():
def test_header_repr_ellipsis():
- assert repr(Header(...)) == IsOneOf(
- "Header(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Header(Ellipsis)",
- )
+ assert repr(Header(...)) == "Header(PydanticUndefined)"
def test_header_repr_number():
@@ -104,11 +83,7 @@ def test_cookie_repr_none():
def test_cookie_repr_ellipsis():
- assert repr(Cookie(...)) == IsOneOf(
- "Cookie(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Cookie(Ellipsis)",
- )
+ assert repr(Cookie(...)) == "Cookie(PydanticUndefined)"
def test_cookie_repr_number():
@@ -128,11 +103,7 @@ def test_body_repr_none():
def test_body_repr_ellipsis():
- assert repr(Body(...)) == IsOneOf(
- "Body(PydanticUndefined)",
- # TODO: remove when deprecating Pydantic v1
- "Body(Ellipsis)",
- )
+ assert repr(Body(...)) == "Body(PydanticUndefined)"
def test_body_repr_number():
diff --git a/tests/test_path.py b/tests/test_path.py
index 09c1f13fb1..47051b927c 100644
--- a/tests/test_path.py
+++ b/tests/test_path.py
@@ -1,4 +1,3 @@
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from .main import app
@@ -45,57 +44,31 @@ def test_path_str_True():
def test_path_int_foobar():
response = client.get("/path/int/foobar")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foobar",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foobar",
+ }
+ ]
+ }
def test_path_int_True():
response = client.get("/path/int/True")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "True",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "True",
+ }
+ ]
+ }
def test_path_int_42():
@@ -107,85 +80,46 @@ def test_path_int_42():
def test_path_int_42_5():
response = client.get("/path/int/42.5")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "42.5",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "42.5",
+ }
+ ]
+ }
def test_path_float_foobar():
response = client.get("/path/float/foobar")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "float_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid number, unable to parse string as a number",
- "input": "foobar",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid float",
- "type": "type_error.float",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "float_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid number, unable to parse string as a number",
+ "input": "foobar",
+ }
+ ]
+ }
def test_path_float_True():
response = client.get("/path/float/True")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "float_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid number, unable to parse string as a number",
- "input": "True",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid float",
- "type": "type_error.float",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "float_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid number, unable to parse string as a number",
+ "input": "True",
+ }
+ ]
+ }
def test_path_float_42():
@@ -203,29 +137,16 @@ def test_path_float_42_5():
def test_path_bool_foobar():
response = client.get("/path/bool/foobar")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "bool_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid boolean, unable to interpret input",
- "input": "foobar",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value could not be parsed to a boolean",
- "type": "type_error.bool",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "bool_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid boolean, unable to interpret input",
+ "input": "foobar",
+ }
+ ]
+ }
def test_path_bool_True():
@@ -237,57 +158,31 @@ def test_path_bool_True():
def test_path_bool_42():
response = client.get("/path/bool/42")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "bool_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid boolean, unable to interpret input",
- "input": "42",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value could not be parsed to a boolean",
- "type": "type_error.bool",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "bool_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid boolean, unable to interpret input",
+ "input": "42",
+ }
+ ]
+ }
def test_path_bool_42_5():
response = client.get("/path/bool/42.5")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "bool_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid boolean, unable to interpret input",
- "input": "42.5",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value could not be parsed to a boolean",
- "type": "type_error.bool",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "bool_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid boolean, unable to interpret input",
+ "input": "42.5",
+ }
+ ]
+ }
def test_path_bool_1():
@@ -335,31 +230,17 @@ def test_path_param_minlength_foo():
def test_path_param_minlength_fo():
response = client.get("/path/param-minlength/fo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_too_short",
- "loc": ["path", "item_id"],
- "msg": "String should have at least 3 characters",
- "input": "fo",
- "ctx": {"min_length": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value has at least 3 characters",
- "type": "value_error.any_str.min_length",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_short",
+ "loc": ["path", "item_id"],
+ "msg": "String should have at least 3 characters",
+ "input": "fo",
+ "ctx": {"min_length": 3},
+ }
+ ]
+ }
def test_path_param_maxlength_foo():
@@ -371,31 +252,17 @@ def test_path_param_maxlength_foo():
def test_path_param_maxlength_foobar():
response = client.get("/path/param-maxlength/foobar")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_too_long",
- "loc": ["path", "item_id"],
- "msg": "String should have at most 3 characters",
- "input": "foobar",
- "ctx": {"max_length": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value has at most 3 characters",
- "type": "value_error.any_str.max_length",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_long",
+ "loc": ["path", "item_id"],
+ "msg": "String should have at most 3 characters",
+ "input": "foobar",
+ "ctx": {"max_length": 3},
+ }
+ ]
+ }
def test_path_param_min_maxlength_foo():
@@ -407,60 +274,33 @@ def test_path_param_min_maxlength_foo():
def test_path_param_min_maxlength_foobar():
response = client.get("/path/param-min_maxlength/foobar")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_too_long",
- "loc": ["path", "item_id"],
- "msg": "String should have at most 3 characters",
- "input": "foobar",
- "ctx": {"max_length": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value has at most 3 characters",
- "type": "value_error.any_str.max_length",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_long",
+ "loc": ["path", "item_id"],
+ "msg": "String should have at most 3 characters",
+ "input": "foobar",
+ "ctx": {"max_length": 3},
+ }
+ ]
+ }
def test_path_param_min_maxlength_f():
response = client.get("/path/param-min_maxlength/f")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_too_short",
- "loc": ["path", "item_id"],
- "msg": "String should have at least 2 characters",
- "input": "f",
- "ctx": {"min_length": 2},
- }
- ]
- }
- ) | IsDict(
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value has at least 2 characters",
- "type": "value_error.any_str.min_length",
- "ctx": {"limit_value": 2},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_short",
+ "loc": ["path", "item_id"],
+ "msg": "String should have at least 2 characters",
+ "input": "f",
+ "ctx": {"min_length": 2},
+ }
+ ]
+ }
def test_path_param_gt_42():
@@ -472,31 +312,17 @@ def test_path_param_gt_42():
def test_path_param_gt_2():
response = client.get("/path/param-gt/2")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than 3",
- "input": "2",
- "ctx": {"gt": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than 3",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than 3",
+ "input": "2",
+ "ctx": {"gt": 3.0},
+ }
+ ]
+ }
def test_path_param_gt0_0_05():
@@ -508,31 +334,17 @@ def test_path_param_gt0_0_05():
def test_path_param_gt0_0():
response = client.get("/path/param-gt0/0")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than 0",
- "input": "0",
- "ctx": {"gt": 0.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than 0",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 0},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than 0",
+ "input": "0",
+ "ctx": {"gt": 0.0},
+ }
+ ]
+ }
def test_path_param_ge_42():
@@ -550,61 +362,33 @@ def test_path_param_ge_3():
def test_path_param_ge_2():
response = client.get("/path/param-ge/2")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than or equal to 3",
- "input": "2",
- "ctx": {"ge": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than or equal to 3",
- "type": "value_error.number.not_ge",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than or equal to 3",
+ "input": "2",
+ "ctx": {"ge": 3.0},
+ }
+ ]
+ }
def test_path_param_lt_42():
response = client.get("/path/param-lt/42")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than 3",
- "input": "42",
- "ctx": {"lt": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than 3",
- "type": "value_error.number.not_lt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than 3",
+ "input": "42",
+ "ctx": {"lt": 3.0},
+ }
+ ]
+ }
def test_path_param_lt_2():
@@ -622,61 +406,33 @@ def test_path_param_lt0__1():
def test_path_param_lt0_0():
response = client.get("/path/param-lt0/0")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than 0",
- "input": "0",
- "ctx": {"lt": 0.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than 0",
- "type": "value_error.number.not_lt",
- "ctx": {"limit_value": 0},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than 0",
+ "input": "0",
+ "ctx": {"lt": 0.0},
+ }
+ ]
+ }
def test_path_param_le_42():
response = client.get("/path/param-le/42")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than or equal to 3",
- "input": "42",
- "ctx": {"le": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than or equal to 3",
- "type": "value_error.number.not_le",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than or equal to 3",
+ "input": "42",
+ "ctx": {"le": 3.0},
+ }
+ ]
+ }
def test_path_param_le_3():
@@ -700,61 +456,33 @@ def test_path_param_lt_gt_2():
def test_path_param_lt_gt_4():
response = client.get("/path/param-lt-gt/4")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than 3",
- "input": "4",
- "ctx": {"lt": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than 3",
- "type": "value_error.number.not_lt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than 3",
+ "input": "4",
+ "ctx": {"lt": 3.0},
+ }
+ ]
+ }
def test_path_param_lt_gt_0():
response = client.get("/path/param-lt-gt/0")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than 1",
- "input": "0",
- "ctx": {"gt": 1.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than 1",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 1},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than 1",
+ "input": "0",
+ "ctx": {"gt": 1.0},
+ }
+ ]
+ }
def test_path_param_le_ge_2():
@@ -777,31 +505,17 @@ def test_path_param_le_ge_3():
def test_path_param_le_ge_4():
response = client.get("/path/param-le-ge/4")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than or equal to 3",
- "input": "4",
- "ctx": {"le": 3.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than or equal to 3",
- "type": "value_error.number.not_le",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than or equal to 3",
+ "input": "4",
+ "ctx": {"le": 3.0},
+ }
+ ]
+ }
def test_path_param_lt_int_2():
@@ -813,59 +527,32 @@ def test_path_param_lt_int_2():
def test_path_param_lt_int_42():
response = client.get("/path/param-lt-int/42")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than 3",
- "input": "42",
- "ctx": {"lt": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than 3",
- "type": "value_error.number.not_lt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than 3",
+ "input": "42",
+ "ctx": {"lt": 3},
+ }
+ ]
+ }
def test_path_param_lt_int_2_7():
response = client.get("/path/param-lt-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "2.7",
+ }
+ ]
+ }
def test_path_param_gt_int_42():
@@ -877,89 +564,48 @@ def test_path_param_gt_int_42():
def test_path_param_gt_int_2():
response = client.get("/path/param-gt-int/2")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than 3",
- "input": "2",
- "ctx": {"gt": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than 3",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than 3",
+ "input": "2",
+ "ctx": {"gt": 3},
+ }
+ ]
+ }
def test_path_param_gt_int_2_7():
response = client.get("/path/param-gt-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "2.7",
+ }
+ ]
+ }
def test_path_param_le_int_42():
response = client.get("/path/param-le-int/42")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than or equal to 3",
- "input": "42",
- "ctx": {"le": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than or equal to 3",
- "type": "value_error.number.not_le",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than or equal to 3",
+ "input": "42",
+ "ctx": {"le": 3},
+ }
+ ]
+ }
def test_path_param_le_int_3():
@@ -977,29 +623,16 @@ def test_path_param_le_int_2():
def test_path_param_le_int_2_7():
response = client.get("/path/param-le-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "2.7",
+ }
+ ]
+ }
def test_path_param_ge_int_42():
@@ -1017,59 +650,32 @@ def test_path_param_ge_int_3():
def test_path_param_ge_int_2():
response = client.get("/path/param-ge-int/2")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than or equal to 3",
- "input": "2",
- "ctx": {"ge": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than or equal to 3",
- "type": "value_error.number.not_ge",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than or equal to 3",
+ "input": "2",
+ "ctx": {"ge": 3},
+ }
+ ]
+ }
def test_path_param_ge_int_2_7():
response = client.get("/path/param-ge-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "2.7",
+ }
+ ]
+ }
def test_path_param_lt_gt_int_2():
@@ -1081,89 +687,48 @@ def test_path_param_lt_gt_int_2():
def test_path_param_lt_gt_int_4():
response = client.get("/path/param-lt-gt-int/4")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than 3",
- "input": "4",
- "ctx": {"lt": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than 3",
- "type": "value_error.number.not_lt",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than 3",
+ "input": "4",
+ "ctx": {"lt": 3},
+ }
+ ]
+ }
def test_path_param_lt_gt_int_0():
response = client.get("/path/param-lt-gt-int/0")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["path", "item_id"],
- "msg": "Input should be greater than 1",
- "input": "0",
- "ctx": {"gt": 1},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is greater than 1",
- "type": "value_error.number.not_gt",
- "ctx": {"limit_value": 1},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be greater than 1",
+ "input": "0",
+ "ctx": {"gt": 1},
+ }
+ ]
+ }
def test_path_param_lt_gt_int_2_7():
response = client.get("/path/param-lt-gt-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "2.7",
+ }
+ ]
+ }
def test_path_param_le_ge_int_2():
@@ -1187,56 +752,29 @@ def test_path_param_le_ge_int_3():
def test_path_param_le_ge_int_4():
response = client.get("/path/param-le-ge-int/4")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "less_than_equal",
- "loc": ["path", "item_id"],
- "msg": "Input should be less than or equal to 3",
- "input": "4",
- "ctx": {"le": 3},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "ensure this value is less than or equal to 3",
- "type": "value_error.number.not_le",
- "ctx": {"limit_value": 3},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "less_than_equal",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be less than or equal to 3",
+ "input": "4",
+ "ctx": {"le": 3},
+ }
+ ]
+ }
def test_path_param_le_ge_int_2_7():
response = client.get("/path/param-le-ge-int/2.7")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "2.7",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "2.7",
+ }
+ ]
+ }
diff --git a/tests/test_pydantic_v1_error.py b/tests/test_pydantic_v1_error.py
new file mode 100644
index 0000000000..13229a3137
--- /dev/null
+++ b/tests/test_pydantic_v1_error.py
@@ -0,0 +1,97 @@
+import sys
+import warnings
+from typing import Union
+
+import pytest
+
+from tests.utils import skip_module_if_py_gte_314
+
+if sys.version_info >= (3, 14):
+ skip_module_if_py_gte_314()
+
+from fastapi import FastAPI
+from fastapi.exceptions import PydanticV1NotSupportedError
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore", UserWarning)
+ from pydantic.v1 import BaseModel
+
+
+def test_raises_pydantic_v1_model_in_endpoint_param() -> None:
+ class ParamModelV1(BaseModel):
+ name: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.post("/param")
+ def endpoint(data: ParamModelV1): # pragma: no cover
+ return data
+
+
+def test_raises_pydantic_v1_model_in_return_type() -> None:
+ class ReturnModelV1(BaseModel):
+ name: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.get("/return")
+ def endpoint() -> ReturnModelV1: # pragma: no cover
+ return ReturnModelV1(name="test")
+
+
+def test_raises_pydantic_v1_model_in_response_model() -> None:
+ class ResponseModelV1(BaseModel):
+ name: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.get("/response-model", response_model=ResponseModelV1)
+ def endpoint(): # pragma: no cover
+ return {"name": "test"}
+
+
+def test_raises_pydantic_v1_model_in_additional_responses_model() -> None:
+ class ErrorModelV1(BaseModel):
+ detail: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.get(
+ "/responses", response_model=None, responses={400: {"model": ErrorModelV1}}
+ )
+ def endpoint(): # pragma: no cover
+ return {"ok": True}
+
+
+def test_raises_pydantic_v1_model_in_union() -> None:
+ class ModelV1A(BaseModel):
+ name: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.post("/union")
+ def endpoint(data: Union[dict, ModelV1A]): # pragma: no cover
+ return data
+
+
+def test_raises_pydantic_v1_model_in_sequence() -> None:
+ class ModelV1A(BaseModel):
+ name: str
+
+ app = FastAPI()
+
+ with pytest.raises(PydanticV1NotSupportedError):
+
+ @app.post("/sequence")
+ def endpoint(data: list[ModelV1A]): # pragma: no cover
+ return data
diff --git a/tests/test_pydantic_v1_v2_01.py b/tests/test_pydantic_v1_v2_01.py
deleted file mode 100644
index 769e5fab62..0000000000
--- a/tests/test_pydantic_v1_v2_01.py
+++ /dev/null
@@ -1,475 +0,0 @@
-import sys
-from typing import Any, List, Union
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from fastapi import FastAPI
-from fastapi._compat.v1 import BaseModel
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-
-
-class SubItem(BaseModel):
- name: str
-
-
-class Item(BaseModel):
- title: str
- size: int
- description: Union[str, None] = None
- sub: SubItem
- multi: List[SubItem] = []
-
-
-app = FastAPI()
-
-
-@app.post("/simple-model")
-def handle_simple_model(data: SubItem) -> SubItem:
- return data
-
-
-@app.post("/simple-model-filter", response_model=SubItem)
-def handle_simple_model_filter(data: SubItem) -> Any:
- extended_data = data.dict()
- extended_data.update({"secret_price": 42})
- return extended_data
-
-
-@app.post("/item")
-def handle_item(data: Item) -> Item:
- return data
-
-
-@app.post("/item-filter", response_model=Item)
-def handle_item_filter(data: Item) -> Any:
- extended_data = data.dict()
- extended_data.update({"secret_data": "classified", "internal_id": 12345})
- extended_data["sub"].update({"internal_id": 67890})
- return extended_data
-
-
-client = TestClient(app)
-
-
-def test_old_simple_model():
- response = client.post(
- "/simple-model",
- json={"name": "Foo"},
- )
- assert response.status_code == 200, response.text
- assert response.json() == {"name": "Foo"}
-
-
-def test_old_simple_model_validation_error():
- response = client.post(
- "/simple-model",
- json={"wrong_name": "Foo"},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-def test_old_simple_model_filter():
- response = client.post(
- "/simple-model-filter",
- json={"name": "Foo"},
- )
- assert response.status_code == 200, response.text
- assert response.json() == {"name": "Foo"}
-
-
-def test_item_model():
- response = client.post(
- "/item",
- json={
- "title": "Test Item",
- "size": 100,
- "description": "This is a test item",
- "sub": {"name": "SubItem1"},
- "multi": [{"name": "Multi1"}, {"name": "Multi2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "Test Item",
- "size": 100,
- "description": "This is a test item",
- "sub": {"name": "SubItem1"},
- "multi": [{"name": "Multi1"}, {"name": "Multi2"}],
- }
-
-
-def test_item_model_minimal():
- response = client.post(
- "/item",
- json={"title": "Minimal Item", "size": 50, "sub": {"name": "SubMin"}},
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "Minimal Item",
- "size": 50,
- "description": None,
- "sub": {"name": "SubMin"},
- "multi": [],
- }
-
-
-def test_item_model_validation_errors():
- response = client.post(
- "/item",
- json={"title": "Missing fields"},
- )
- assert response.status_code == 422, response.text
- error_detail = response.json()["detail"]
- assert len(error_detail) == 2
- assert {
- "loc": ["body", "size"],
- "msg": "field required",
- "type": "value_error.missing",
- } in error_detail
- assert {
- "loc": ["body", "sub"],
- "msg": "field required",
- "type": "value_error.missing",
- } in error_detail
-
-
-def test_item_model_nested_validation_error():
- response = client.post(
- "/item",
- json={"title": "Test Item", "size": 100, "sub": {"wrong_field": "test"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "sub", "name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-def test_item_model_invalid_type():
- response = client.post(
- "/item",
- json={"title": "Test Item", "size": "not_a_number", "sub": {"name": "SubItem"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "size"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
-
-
-def test_item_filter():
- response = client.post(
- "/item-filter",
- json={
- "title": "Filtered Item",
- "size": 200,
- "description": "Test filtering",
- "sub": {"name": "SubFiltered"},
- "multi": [],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == {
- "title": "Filtered Item",
- "size": 200,
- "description": "Test filtering",
- "sub": {"name": "SubFiltered"},
- "multi": [],
- }
- assert "secret_data" not in result
- assert "internal_id" not in result
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/simple-model": {
- "post": {
- "summary": "Handle Simple Model",
- "operationId": "handle_simple_model_simple_model_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/SubItem"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/SubItem"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/SubItem"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/simple-model-filter": {
- "post": {
- "summary": "Handle Simple Model Filter",
- "operationId": "handle_simple_model_filter_simple_model_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/SubItem"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/SubItem"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/SubItem"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item": {
- "post": {
- "summary": "Handle Item",
- "operationId": "handle_item_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-filter": {
- "post": {
- "summary": "Handle Item Filter",
- "operationId": "handle_item_filter_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {"type": "string", "title": "Description"},
- "sub": {"$ref": "#/components/schemas/SubItem"},
- "multi": {
- "items": {"$ref": "#/components/schemas/SubItem"},
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "SubItem": {
- "properties": {"name": {"type": "string", "title": "Name"}},
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_pydantic_v1_v2_list.py b/tests/test_pydantic_v1_v2_list.py
deleted file mode 100644
index 64f3dd3446..0000000000
--- a/tests/test_pydantic_v1_v2_list.py
+++ /dev/null
@@ -1,701 +0,0 @@
-import sys
-from typing import Any, List, Union
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from fastapi import FastAPI
-from fastapi._compat.v1 import BaseModel
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-
-
-class SubItem(BaseModel):
- name: str
-
-
-class Item(BaseModel):
- title: str
- size: int
- description: Union[str, None] = None
- sub: SubItem
- multi: List[SubItem] = []
-
-
-app = FastAPI()
-
-
-@app.post("/item")
-def handle_item(data: Item) -> List[Item]:
- return [data, data]
-
-
-@app.post("/item-filter", response_model=List[Item])
-def handle_item_filter(data: Item) -> Any:
- extended_data = data.dict()
- extended_data.update({"secret_data": "classified", "internal_id": 12345})
- extended_data["sub"].update({"internal_id": 67890})
- return [extended_data, extended_data]
-
-
-@app.post("/item-list")
-def handle_item_list(data: List[Item]) -> Item:
- if data:
- return data[0]
- return Item(title="", size=0, sub=SubItem(name=""))
-
-
-@app.post("/item-list-filter", response_model=Item)
-def handle_item_list_filter(data: List[Item]) -> Any:
- if data:
- extended_data = data[0].dict()
- extended_data.update({"secret_data": "classified", "internal_id": 12345})
- extended_data["sub"].update({"internal_id": 67890})
- return extended_data
- return Item(title="", size=0, sub=SubItem(name=""))
-
-
-@app.post("/item-list-to-list")
-def handle_item_list_to_list(data: List[Item]) -> List[Item]:
- return data
-
-
-@app.post("/item-list-to-list-filter", response_model=List[Item])
-def handle_item_list_to_list_filter(data: List[Item]) -> Any:
- if data:
- extended_data = data[0].dict()
- extended_data.update({"secret_data": "classified", "internal_id": 12345})
- extended_data["sub"].update({"internal_id": 67890})
- return [extended_data, extended_data]
- return []
-
-
-client = TestClient(app)
-
-
-def test_item_to_list():
- response = client.post(
- "/item",
- json={
- "title": "Test Item",
- "size": 100,
- "description": "This is a test item",
- "sub": {"name": "SubItem1"},
- "multi": [{"name": "Multi1"}, {"name": "Multi2"}],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert isinstance(result, list)
- assert len(result) == 2
- for item in result:
- assert item == {
- "title": "Test Item",
- "size": 100,
- "description": "This is a test item",
- "sub": {"name": "SubItem1"},
- "multi": [{"name": "Multi1"}, {"name": "Multi2"}],
- }
-
-
-def test_item_to_list_filter():
- response = client.post(
- "/item-filter",
- json={
- "title": "Filtered Item",
- "size": 200,
- "description": "Test filtering",
- "sub": {"name": "SubFiltered"},
- "multi": [],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert isinstance(result, list)
- assert len(result) == 2
- for item in result:
- assert item == {
- "title": "Filtered Item",
- "size": 200,
- "description": "Test filtering",
- "sub": {"name": "SubFiltered"},
- "multi": [],
- }
- # Verify secret fields are filtered out
- assert "secret_data" not in item
- assert "internal_id" not in item
- assert "internal_id" not in item["sub"]
-
-
-def test_list_to_item():
- response = client.post(
- "/item-list",
- json=[
- {"title": "First Item", "size": 50, "sub": {"name": "First Sub"}},
- {"title": "Second Item", "size": 75, "sub": {"name": "Second Sub"}},
- ],
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "First Item",
- "size": 50,
- "description": None,
- "sub": {"name": "First Sub"},
- "multi": [],
- }
-
-
-def test_list_to_item_empty():
- response = client.post(
- "/item-list",
- json=[],
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "",
- "size": 0,
- "description": None,
- "sub": {"name": ""},
- "multi": [],
- }
-
-
-def test_list_to_item_filter():
- response = client.post(
- "/item-list-filter",
- json=[
- {
- "title": "First Item",
- "size": 100,
- "sub": {"name": "First Sub"},
- "multi": [{"name": "Multi1"}],
- },
- {"title": "Second Item", "size": 200, "sub": {"name": "Second Sub"}},
- ],
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == {
- "title": "First Item",
- "size": 100,
- "description": None,
- "sub": {"name": "First Sub"},
- "multi": [{"name": "Multi1"}],
- }
- # Verify secret fields are filtered out
- assert "secret_data" not in result
- assert "internal_id" not in result
-
-
-def test_list_to_item_filter_no_data():
- response = client.post("/item-list-filter", json=[])
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "",
- "size": 0,
- "description": None,
- "sub": {"name": ""},
- "multi": [],
- }
-
-
-def test_list_to_list():
- input_items = [
- {"title": "Item 1", "size": 10, "sub": {"name": "Sub1"}},
- {
- "title": "Item 2",
- "size": 20,
- "description": "Second item",
- "sub": {"name": "Sub2"},
- "multi": [{"name": "M1"}, {"name": "M2"}],
- },
- {"title": "Item 3", "size": 30, "sub": {"name": "Sub3"}},
- ]
- response = client.post(
- "/item-list-to-list",
- json=input_items,
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert isinstance(result, list)
- assert len(result) == 3
- assert result[0] == {
- "title": "Item 1",
- "size": 10,
- "description": None,
- "sub": {"name": "Sub1"},
- "multi": [],
- }
- assert result[1] == {
- "title": "Item 2",
- "size": 20,
- "description": "Second item",
- "sub": {"name": "Sub2"},
- "multi": [{"name": "M1"}, {"name": "M2"}],
- }
- assert result[2] == {
- "title": "Item 3",
- "size": 30,
- "description": None,
- "sub": {"name": "Sub3"},
- "multi": [],
- }
-
-
-def test_list_to_list_filter():
- response = client.post(
- "/item-list-to-list-filter",
- json=[{"title": "Item 1", "size": 100, "sub": {"name": "Sub1"}}],
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert isinstance(result, list)
- assert len(result) == 2
- for item in result:
- assert item == {
- "title": "Item 1",
- "size": 100,
- "description": None,
- "sub": {"name": "Sub1"},
- "multi": [],
- }
- # Verify secret fields are filtered out
- assert "secret_data" not in item
- assert "internal_id" not in item
-
-
-def test_list_to_list_filter_no_data():
- response = client.post(
- "/item-list-to-list-filter",
- json=[],
- )
- assert response.status_code == 200, response.text
- assert response.json() == []
-
-
-def test_list_validation_error():
- response = client.post(
- "/item-list",
- json=[
- {"title": "Valid Item", "size": 100, "sub": {"name": "Sub1"}},
- {
- "title": "Invalid Item"
- # Missing required fields: size and sub
- },
- ],
- )
- assert response.status_code == 422, response.text
- error_detail = response.json()["detail"]
- assert len(error_detail) == 2
- assert {
- "loc": ["body", 1, "size"],
- "msg": "field required",
- "type": "value_error.missing",
- } in error_detail
- assert {
- "loc": ["body", 1, "sub"],
- "msg": "field required",
- "type": "value_error.missing",
- } in error_detail
-
-
-def test_list_nested_validation_error():
- response = client.post(
- "/item-list",
- json=[
- {"title": "Item with bad sub", "size": 100, "sub": {"wrong_field": "value"}}
- ],
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", 0, "sub", "name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-def test_list_type_validation_error():
- response = client.post(
- "/item-list",
- json=[{"title": "Item", "size": "not_a_number", "sub": {"name": "Sub"}}],
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", 0, "size"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
-
-
-def test_invalid_list_structure():
- response = client.post(
- "/item-list",
- json={"title": "Not a list", "size": 100, "sub": {"name": "Sub"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid list",
- "type": "type_error.list",
- }
- ]
- }
- )
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/item": {
- "post": {
- "summary": "Handle Item",
- "operationId": "handle_item_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle Item Item Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-filter": {
- "post": {
- "summary": "Handle Item Filter",
- "operationId": "handle_item_filter_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle Item Filter Item Filter Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-list": {
- "post": {
- "summary": "Handle Item List",
- "operationId": "handle_item_list_item_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-list-filter": {
- "post": {
- "summary": "Handle Item List Filter",
- "operationId": "handle_item_list_filter_item_list_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-list-to-list": {
- "post": {
- "summary": "Handle Item List To List",
- "operationId": "handle_item_list_to_list_item_list_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle Item List To List Item List To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/item-list-to-list-filter": {
- "post": {
- "summary": "Handle Item List To List Filter",
- "operationId": "handle_item_list_to_list_filter_item_list_to_list_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle Item List To List Filter Item List To List Filter Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {"type": "string", "title": "Description"},
- "sub": {"$ref": "#/components/schemas/SubItem"},
- "multi": {
- "items": {"$ref": "#/components/schemas/SubItem"},
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "SubItem": {
- "properties": {"name": {"type": "string", "title": "Name"}},
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_pydantic_v1_v2_mixed.py b/tests/test_pydantic_v1_v2_mixed.py
deleted file mode 100644
index 54d408827f..0000000000
--- a/tests/test_pydantic_v1_v2_mixed.py
+++ /dev/null
@@ -1,1499 +0,0 @@
-import sys
-from typing import Any, List, Union
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from fastapi import FastAPI
-from fastapi._compat.v1 import BaseModel
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-from pydantic import BaseModel as NewBaseModel
-
-
-class SubItem(BaseModel):
- name: str
-
-
-class Item(BaseModel):
- title: str
- size: int
- description: Union[str, None] = None
- sub: SubItem
- multi: List[SubItem] = []
-
-
-class NewSubItem(NewBaseModel):
- new_sub_name: str
-
-
-class NewItem(NewBaseModel):
- new_title: str
- new_size: int
- new_description: Union[str, None] = None
- new_sub: NewSubItem
- new_multi: List[NewSubItem] = []
-
-
-app = FastAPI()
-
-
-@app.post("/v1-to-v2/item")
-def handle_v1_item_to_v2(data: Item) -> NewItem:
- return NewItem(
- new_title=data.title,
- new_size=data.size,
- new_description=data.description,
- new_sub=NewSubItem(new_sub_name=data.sub.name),
- new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi],
- )
-
-
-@app.post("/v1-to-v2/item-filter", response_model=NewItem)
-def handle_v1_item_to_v2_filter(data: Item) -> Any:
- result = {
- "new_title": data.title,
- "new_size": data.size,
- "new_description": data.description,
- "new_sub": {"new_sub_name": data.sub.name, "new_sub_secret": "sub_hidden"},
- "new_multi": [
- {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} for s in data.multi
- ],
- "secret": "hidden_v1_to_v2",
- }
- return result
-
-
-@app.post("/v2-to-v1/item")
-def handle_v2_item_to_v1(data: NewItem) -> Item:
- return Item(
- title=data.new_title,
- size=data.new_size,
- description=data.new_description,
- sub=SubItem(name=data.new_sub.new_sub_name),
- multi=[SubItem(name=s.new_sub_name) for s in data.new_multi],
- )
-
-
-@app.post("/v2-to-v1/item-filter", response_model=Item)
-def handle_v2_item_to_v1_filter(data: NewItem) -> Any:
- result = {
- "title": data.new_title,
- "size": data.new_size,
- "description": data.new_description,
- "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"},
- "multi": [
- {"name": s.new_sub_name, "sub_secret": "sub_hidden"} for s in data.new_multi
- ],
- "secret": "hidden_v2_to_v1",
- }
- return result
-
-
-@app.post("/v1-to-v2/item-to-list")
-def handle_v1_item_to_v2_list(data: Item) -> List[NewItem]:
- converted = NewItem(
- new_title=data.title,
- new_size=data.size,
- new_description=data.description,
- new_sub=NewSubItem(new_sub_name=data.sub.name),
- new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi],
- )
- return [converted, converted]
-
-
-@app.post("/v1-to-v2/list-to-list")
-def handle_v1_list_to_v2_list(data: List[Item]) -> List[NewItem]:
- result = []
- for item in data:
- result.append(
- NewItem(
- new_title=item.title,
- new_size=item.size,
- new_description=item.description,
- new_sub=NewSubItem(new_sub_name=item.sub.name),
- new_multi=[NewSubItem(new_sub_name=s.name) for s in item.multi],
- )
- )
- return result
-
-
-@app.post("/v1-to-v2/list-to-list-filter", response_model=List[NewItem])
-def handle_v1_list_to_v2_list_filter(data: List[Item]) -> Any:
- result = []
- for item in data:
- converted = {
- "new_title": item.title,
- "new_size": item.size,
- "new_description": item.description,
- "new_sub": {"new_sub_name": item.sub.name, "new_sub_secret": "sub_hidden"},
- "new_multi": [
- {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"}
- for s in item.multi
- ],
- "secret": "hidden_v2_to_v1",
- }
- result.append(converted)
- return result
-
-
-@app.post("/v1-to-v2/list-to-item")
-def handle_v1_list_to_v2_item(data: List[Item]) -> NewItem:
- if data:
- item = data[0]
- return NewItem(
- new_title=item.title,
- new_size=item.size,
- new_description=item.description,
- new_sub=NewSubItem(new_sub_name=item.sub.name),
- new_multi=[NewSubItem(new_sub_name=s.name) for s in item.multi],
- )
- return NewItem(new_title="", new_size=0, new_sub=NewSubItem(new_sub_name=""))
-
-
-@app.post("/v2-to-v1/item-to-list")
-def handle_v2_item_to_v1_list(data: NewItem) -> List[Item]:
- converted = Item(
- title=data.new_title,
- size=data.new_size,
- description=data.new_description,
- sub=SubItem(name=data.new_sub.new_sub_name),
- multi=[SubItem(name=s.new_sub_name) for s in data.new_multi],
- )
- return [converted, converted]
-
-
-@app.post("/v2-to-v1/list-to-list")
-def handle_v2_list_to_v1_list(data: List[NewItem]) -> List[Item]:
- result = []
- for item in data:
- result.append(
- Item(
- title=item.new_title,
- size=item.new_size,
- description=item.new_description,
- sub=SubItem(name=item.new_sub.new_sub_name),
- multi=[SubItem(name=s.new_sub_name) for s in item.new_multi],
- )
- )
- return result
-
-
-@app.post("/v2-to-v1/list-to-list-filter", response_model=List[Item])
-def handle_v2_list_to_v1_list_filter(data: List[NewItem]) -> Any:
- result = []
- for item in data:
- converted = {
- "title": item.new_title,
- "size": item.new_size,
- "description": item.new_description,
- "sub": {"name": item.new_sub.new_sub_name, "sub_secret": "sub_hidden"},
- "multi": [
- {"name": s.new_sub_name, "sub_secret": "sub_hidden"}
- for s in item.new_multi
- ],
- "secret": "hidden_v2_to_v1",
- }
- result.append(converted)
- return result
-
-
-@app.post("/v2-to-v1/list-to-item")
-def handle_v2_list_to_v1_item(data: List[NewItem]) -> Item:
- if data:
- item = data[0]
- return Item(
- title=item.new_title,
- size=item.new_size,
- description=item.new_description,
- sub=SubItem(name=item.new_sub.new_sub_name),
- multi=[SubItem(name=s.new_sub_name) for s in item.new_multi],
- )
- return Item(title="", size=0, sub=SubItem(name=""))
-
-
-client = TestClient(app)
-
-
-def test_v1_to_v2_item():
- response = client.post(
- "/v1-to-v2/item",
- json={
- "title": "Old Item",
- "size": 100,
- "description": "V1 description",
- "sub": {"name": "V1 Sub"},
- "multi": [{"name": "M1"}, {"name": "M2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "Old Item",
- "new_size": 100,
- "new_description": "V1 description",
- "new_sub": {"new_sub_name": "V1 Sub"},
- "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}],
- }
-
-
-def test_v1_to_v2_item_minimal():
- response = client.post(
- "/v1-to-v2/item",
- json={"title": "Minimal", "size": 50, "sub": {"name": "MinSub"}},
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "Minimal",
- "new_size": 50,
- "new_description": None,
- "new_sub": {"new_sub_name": "MinSub"},
- "new_multi": [],
- }
-
-
-def test_v1_to_v2_item_filter():
- response = client.post(
- "/v1-to-v2/item-filter",
- json={
- "title": "Filtered Item",
- "size": 50,
- "sub": {"name": "Sub"},
- "multi": [{"name": "Multi1"}],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == snapshot(
- {
- "new_title": "Filtered Item",
- "new_size": 50,
- "new_description": None,
- "new_sub": {"new_sub_name": "Sub"},
- "new_multi": [{"new_sub_name": "Multi1"}],
- }
- )
- # Verify secret fields are filtered out
- assert "secret" not in result
- assert "new_sub_secret" not in result["new_sub"]
- assert "new_sub_secret" not in result["new_multi"][0]
-
-
-def test_v2_to_v1_item():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "New Item",
- "new_size": 200,
- "new_description": "V2 description",
- "new_sub": {"new_sub_name": "V2 Sub"},
- "new_multi": [{"new_sub_name": "N1"}, {"new_sub_name": "N2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "New Item",
- "size": 200,
- "description": "V2 description",
- "sub": {"name": "V2 Sub"},
- "multi": [{"name": "N1"}, {"name": "N2"}],
- }
-
-
-def test_v2_to_v1_item_minimal():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "MinimalNew",
- "new_size": 75,
- "new_sub": {"new_sub_name": "MinNewSub"},
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "MinimalNew",
- "size": 75,
- "description": None,
- "sub": {"name": "MinNewSub"},
- "multi": [],
- }
-
-
-def test_v2_to_v1_item_filter():
- response = client.post(
- "/v2-to-v1/item-filter",
- json={
- "new_title": "Filtered New",
- "new_size": 75,
- "new_sub": {"new_sub_name": "NewSub"},
- "new_multi": [],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == snapshot(
- {
- "title": "Filtered New",
- "size": 75,
- "description": None,
- "sub": {"name": "NewSub"},
- "multi": [],
- }
- )
- # Verify secret fields are filtered out
- assert "secret" not in result
- assert "sub_secret" not in result["sub"]
-
-
-def test_v1_item_to_v2_list():
- response = client.post(
- "/v1-to-v2/item-to-list",
- json={
- "title": "Single to List",
- "size": 150,
- "description": "Convert to list",
- "sub": {"name": "Sub1"},
- "multi": [],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == [
- {
- "new_title": "Single to List",
- "new_size": 150,
- "new_description": "Convert to list",
- "new_sub": {"new_sub_name": "Sub1"},
- "new_multi": [],
- },
- {
- "new_title": "Single to List",
- "new_size": 150,
- "new_description": "Convert to list",
- "new_sub": {"new_sub_name": "Sub1"},
- "new_multi": [],
- },
- ]
-
-
-def test_v1_list_to_v2_list():
- response = client.post(
- "/v1-to-v2/list-to-list",
- json=[
- {"title": "Item1", "size": 10, "sub": {"name": "Sub1"}},
- {
- "title": "Item2",
- "size": 20,
- "description": "Second item",
- "sub": {"name": "Sub2"},
- "multi": [{"name": "M1"}, {"name": "M2"}],
- },
- {"title": "Item3", "size": 30, "sub": {"name": "Sub3"}},
- ],
- )
- assert response.status_code == 200, response.text
- assert response.json() == [
- {
- "new_title": "Item1",
- "new_size": 10,
- "new_description": None,
- "new_sub": {"new_sub_name": "Sub1"},
- "new_multi": [],
- },
- {
- "new_title": "Item2",
- "new_size": 20,
- "new_description": "Second item",
- "new_sub": {"new_sub_name": "Sub2"},
- "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}],
- },
- {
- "new_title": "Item3",
- "new_size": 30,
- "new_description": None,
- "new_sub": {"new_sub_name": "Sub3"},
- "new_multi": [],
- },
- ]
-
-
-def test_v1_list_to_v2_list_filter():
- response = client.post(
- "/v1-to-v2/list-to-list-filter",
- json=[{"title": "FilterMe", "size": 30, "sub": {"name": "SubF"}}],
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == snapshot(
- [
- {
- "new_title": "FilterMe",
- "new_size": 30,
- "new_description": None,
- "new_sub": {"new_sub_name": "SubF"},
- "new_multi": [],
- }
- ]
- )
- # Verify secret fields are filtered out
- assert "secret" not in result[0]
- assert "new_sub_secret" not in result[0]["new_sub"]
-
-
-def test_v1_list_to_v2_item():
- response = client.post(
- "/v1-to-v2/list-to-item",
- json=[
- {"title": "First", "size": 100, "sub": {"name": "FirstSub"}},
- {"title": "Second", "size": 200, "sub": {"name": "SecondSub"}},
- ],
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "First",
- "new_size": 100,
- "new_description": None,
- "new_sub": {"new_sub_name": "FirstSub"},
- "new_multi": [],
- }
-
-
-def test_v1_list_to_v2_item_empty():
- response = client.post("/v1-to-v2/list-to-item", json=[])
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "",
- "new_size": 0,
- "new_description": None,
- "new_sub": {"new_sub_name": ""},
- "new_multi": [],
- }
-
-
-def test_v2_item_to_v1_list():
- response = client.post(
- "/v2-to-v1/item-to-list",
- json={
- "new_title": "Single New",
- "new_size": 250,
- "new_description": "New to list",
- "new_sub": {"new_sub_name": "NewSub"},
- "new_multi": [],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == [
- {
- "title": "Single New",
- "size": 250,
- "description": "New to list",
- "sub": {"name": "NewSub"},
- "multi": [],
- },
- {
- "title": "Single New",
- "size": 250,
- "description": "New to list",
- "sub": {"name": "NewSub"},
- "multi": [],
- },
- ]
-
-
-def test_v2_list_to_v1_list():
- response = client.post(
- "/v2-to-v1/list-to-list",
- json=[
- {"new_title": "New1", "new_size": 15, "new_sub": {"new_sub_name": "NS1"}},
- {
- "new_title": "New2",
- "new_size": 25,
- "new_description": "Second new",
- "new_sub": {"new_sub_name": "NS2"},
- "new_multi": [{"new_sub_name": "NM1"}],
- },
- ],
- )
- assert response.status_code == 200, response.text
- assert response.json() == [
- {
- "title": "New1",
- "size": 15,
- "description": None,
- "sub": {"name": "NS1"},
- "multi": [],
- },
- {
- "title": "New2",
- "size": 25,
- "description": "Second new",
- "sub": {"name": "NS2"},
- "multi": [{"name": "NM1"}],
- },
- ]
-
-
-def test_v2_list_to_v1_list_filter():
- response = client.post(
- "/v2-to-v1/list-to-list-filter",
- json=[
- {
- "new_title": "FilterNew",
- "new_size": 35,
- "new_sub": {"new_sub_name": "NSF"},
- }
- ],
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result == snapshot(
- [
- {
- "title": "FilterNew",
- "size": 35,
- "description": None,
- "sub": {"name": "NSF"},
- "multi": [],
- }
- ]
- )
- # Verify secret fields are filtered out
- assert "secret" not in result[0]
- assert "sub_secret" not in result[0]["sub"]
-
-
-def test_v2_list_to_v1_item():
- response = client.post(
- "/v2-to-v1/list-to-item",
- json=[
- {
- "new_title": "FirstNew",
- "new_size": 300,
- "new_sub": {"new_sub_name": "FNS"},
- },
- {
- "new_title": "SecondNew",
- "new_size": 400,
- "new_sub": {"new_sub_name": "SNS"},
- },
- ],
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "FirstNew",
- "size": 300,
- "description": None,
- "sub": {"name": "FNS"},
- "multi": [],
- }
-
-
-def test_v2_list_to_v1_item_empty():
- response = client.post("/v2-to-v1/list-to-item", json=[])
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "",
- "size": 0,
- "description": None,
- "sub": {"name": ""},
- "multi": [],
- }
-
-
-def test_v1_to_v2_validation_error():
- response = client.post("/v1-to-v2/item", json={"title": "Missing fields"})
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-def test_v1_to_v2_nested_validation_error():
- response = client.post(
- "/v1-to-v2/item",
- json={"title": "Bad sub", "size": 100, "sub": {"wrong_field": "value"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "sub", "name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-def test_v1_to_v2_type_validation_error():
- response = client.post(
- "/v1-to-v2/item",
- json={"title": "Bad type", "size": "not_a_number", "sub": {"name": "Sub"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "size"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
-
-
-def test_v2_to_v1_validation_error():
- response = client.post(
- "/v2-to-v1/item",
- json={"new_title": "Missing fields"},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": pydantic_snapshot(
- v2=snapshot(
- [
- {
- "type": "missing",
- "loc": ["body", "new_size"],
- "msg": "Field required",
- "input": {"new_title": "Missing fields"},
- },
- {
- "type": "missing",
- "loc": ["body", "new_sub"],
- "msg": "Field required",
- "input": {"new_title": "Missing fields"},
- },
- ]
- ),
- v1=snapshot(
- [
- {
- "loc": ["body", "new_size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "new_sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- ),
- )
- }
- )
-
-
-def test_v2_to_v1_nested_validation_error():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "Bad sub",
- "new_size": 200,
- "new_sub": {"wrong_field": "value"},
- },
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- pydantic_snapshot(
- v2=snapshot(
- {
- "type": "missing",
- "loc": ["body", "new_sub", "new_sub_name"],
- "msg": "Field required",
- "input": {"wrong_field": "value"},
- }
- ),
- v1=snapshot(
- {
- "loc": ["body", "new_sub", "new_sub_name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ),
- )
- ]
- }
- )
-
-
-def test_v1_list_validation_error():
- response = client.post(
- "/v1-to-v2/list-to-list",
- json=[
- {"title": "Valid", "size": 10, "sub": {"name": "S"}},
- {"title": "Invalid"},
- ],
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", 1, "size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", 1, "sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-def test_v2_list_validation_error():
- response = client.post(
- "/v2-to-v1/list-to-list",
- json=[
- {"new_title": "Valid", "new_size": 10, "new_sub": {"new_sub_name": "NS"}},
- {"new_title": "Invalid"},
- ],
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": pydantic_snapshot(
- v2=snapshot(
- [
- {
- "type": "missing",
- "loc": ["body", 1, "new_size"],
- "msg": "Field required",
- "input": {"new_title": "Invalid"},
- },
- {
- "type": "missing",
- "loc": ["body", 1, "new_sub"],
- "msg": "Field required",
- "input": {"new_title": "Invalid"},
- },
- ]
- ),
- v1=snapshot(
- [
- {
- "loc": ["body", 1, "new_size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", 1, "new_sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- ),
- )
- }
- )
-
-
-def test_invalid_list_structure_v1():
- response = client.post(
- "/v1-to-v2/list-to-list",
- json={"title": "Not a list", "size": 100, "sub": {"name": "Sub"}},
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid list",
- "type": "type_error.list",
- }
- ]
- }
- )
-
-
-def test_invalid_list_structure_v2():
- response = client.post(
- "/v2-to-v1/list-to-list",
- json={
- "new_title": "Not a list",
- "new_size": 100,
- "new_sub": {"new_sub_name": "Sub"},
- },
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": pydantic_snapshot(
- v2=snapshot(
- [
- {
- "type": "list_type",
- "loc": ["body"],
- "msg": "Input should be a valid list",
- "input": {
- "new_title": "Not a list",
- "new_size": 100,
- "new_sub": {"new_sub_name": "Sub"},
- },
- }
- ]
- ),
- v1=snapshot(
- [
- {
- "loc": ["body"],
- "msg": "value is not a valid list",
- "type": "type_error.list",
- }
- ]
- ),
- )
- }
- )
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/v1-to-v2/item": {
- "post": {
- "summary": "Handle V1 Item To V2",
- "operationId": "handle_v1_item_to_v2_v1_to_v2_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/NewItem"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/item-filter": {
- "post": {
- "summary": "Handle V1 Item To V2 Filter",
- "operationId": "handle_v1_item_to_v2_filter_v1_to_v2_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/NewItem"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item": {
- "post": {
- "summary": "Handle V2 Item To V1",
- "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/NewItem"}
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item-filter": {
- "post": {
- "summary": "Handle V2 Item To V1 Filter",
- "operationId": "handle_v2_item_to_v1_filter_v2_to_v1_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/NewItem"}
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/item-to-list": {
- "post": {
- "summary": "Handle V1 Item To V2 List",
- "operationId": "handle_v1_item_to_v2_list_v1_to_v2_item_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Response Handle V1 Item To V2 List V1 To V2 Item To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/list-to-list": {
- "post": {
- "summary": "Handle V1 List To V2 List",
- "operationId": "handle_v1_list_to_v2_list_v1_to_v2_list_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Response Handle V1 List To V2 List V1 To V2 List To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/list-to-list-filter": {
- "post": {
- "summary": "Handle V1 List To V2 List Filter",
- "operationId": "handle_v1_list_to_v2_list_filter_v1_to_v2_list_to_list_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Response Handle V1 List To V2 List Filter V1 To V2 List To List Filter Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/list-to-item": {
- "post": {
- "summary": "Handle V1 List To V2 Item",
- "operationId": "handle_v1_list_to_v2_item_v1_to_v2_list_to_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/NewItem"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item-to-list": {
- "post": {
- "summary": "Handle V2 Item To V1 List",
- "operationId": "handle_v2_item_to_v1_list_v2_to_v1_item_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/NewItem"}
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle V2 Item To V1 List V2 To V1 Item To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-to-list": {
- "post": {
- "summary": "Handle V2 List To V1 List",
- "operationId": "handle_v2_list_to_v1_list_v2_to_v1_list_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle V2 List To V1 List V2 To V1 List To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-to-list-filter": {
- "post": {
- "summary": "Handle V2 List To V1 List Filter",
- "operationId": "handle_v2_list_to_v1_list_filter_v2_to_v1_list_to_list_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item"
- },
- "type": "array",
- "title": "Response Handle V2 List To V1 List Filter V2 To V1 List To List Filter Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-to-item": {
- "post": {
- "summary": "Handle V2 List To V1 Item",
- "operationId": "handle_v2_list_to_v1_item_v2_to_v1_list_to_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/NewItem"
- },
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {"type": "string", "title": "Description"},
- "sub": {"$ref": "#/components/schemas/SubItem"},
- "multi": {
- "items": {"$ref": "#/components/schemas/SubItem"},
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "NewItem": {
- "properties": {
- "new_title": {"type": "string", "title": "New Title"},
- "new_size": {"type": "integer", "title": "New Size"},
- "new_description": pydantic_snapshot(
- v2=snapshot(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "New Description",
- }
- ),
- v1=snapshot(
- {"type": "string", "title": "New Description"}
- ),
- ),
- "new_sub": {"$ref": "#/components/schemas/NewSubItem"},
- "new_multi": {
- "items": {"$ref": "#/components/schemas/NewSubItem"},
- "type": "array",
- "title": "New Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["new_title", "new_size", "new_sub"],
- "title": "NewItem",
- },
- "NewSubItem": {
- "properties": {
- "new_sub_name": {"type": "string", "title": "New Sub Name"}
- },
- "type": "object",
- "required": ["new_sub_name"],
- "title": "NewSubItem",
- },
- "SubItem": {
- "properties": {"name": {"type": "string", "title": "Name"}},
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_pydantic_v1_v2_multifile/main.py b/tests/test_pydantic_v1_v2_multifile/main.py
deleted file mode 100644
index 8985cb7b4c..0000000000
--- a/tests/test_pydantic_v1_v2_multifile/main.py
+++ /dev/null
@@ -1,142 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI
-
-from . import modelsv1, modelsv2, modelsv2b
-
-app = FastAPI()
-
-
-@app.post("/v1-to-v2/item")
-def handle_v1_item_to_v2(data: modelsv1.Item) -> modelsv2.Item:
- return modelsv2.Item(
- new_title=data.title,
- new_size=data.size,
- new_description=data.description,
- new_sub=modelsv2.SubItem(new_sub_name=data.sub.name),
- new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi],
- )
-
-
-@app.post("/v2-to-v1/item")
-def handle_v2_item_to_v1(data: modelsv2.Item) -> modelsv1.Item:
- return modelsv1.Item(
- title=data.new_title,
- size=data.new_size,
- description=data.new_description,
- sub=modelsv1.SubItem(name=data.new_sub.new_sub_name),
- multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi],
- )
-
-
-@app.post("/v1-to-v2/item-to-list")
-def handle_v1_item_to_v2_list(data: modelsv1.Item) -> List[modelsv2.Item]:
- converted = modelsv2.Item(
- new_title=data.title,
- new_size=data.size,
- new_description=data.description,
- new_sub=modelsv2.SubItem(new_sub_name=data.sub.name),
- new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi],
- )
- return [converted, converted]
-
-
-@app.post("/v1-to-v2/list-to-list")
-def handle_v1_list_to_v2_list(data: List[modelsv1.Item]) -> List[modelsv2.Item]:
- result = []
- for item in data:
- result.append(
- modelsv2.Item(
- new_title=item.title,
- new_size=item.size,
- new_description=item.description,
- new_sub=modelsv2.SubItem(new_sub_name=item.sub.name),
- new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in item.multi],
- )
- )
- return result
-
-
-@app.post("/v1-to-v2/list-to-item")
-def handle_v1_list_to_v2_item(data: List[modelsv1.Item]) -> modelsv2.Item:
- if data:
- item = data[0]
- return modelsv2.Item(
- new_title=item.title,
- new_size=item.size,
- new_description=item.description,
- new_sub=modelsv2.SubItem(new_sub_name=item.sub.name),
- new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in item.multi],
- )
- return modelsv2.Item(
- new_title="", new_size=0, new_sub=modelsv2.SubItem(new_sub_name="")
- )
-
-
-@app.post("/v2-to-v1/item-to-list")
-def handle_v2_item_to_v1_list(data: modelsv2.Item) -> List[modelsv1.Item]:
- converted = modelsv1.Item(
- title=data.new_title,
- size=data.new_size,
- description=data.new_description,
- sub=modelsv1.SubItem(name=data.new_sub.new_sub_name),
- multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi],
- )
- return [converted, converted]
-
-
-@app.post("/v2-to-v1/list-to-list")
-def handle_v2_list_to_v1_list(data: List[modelsv2.Item]) -> List[modelsv1.Item]:
- result = []
- for item in data:
- result.append(
- modelsv1.Item(
- title=item.new_title,
- size=item.new_size,
- description=item.new_description,
- sub=modelsv1.SubItem(name=item.new_sub.new_sub_name),
- multi=[modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi],
- )
- )
- return result
-
-
-@app.post("/v2-to-v1/list-to-item")
-def handle_v2_list_to_v1_item(data: List[modelsv2.Item]) -> modelsv1.Item:
- if data:
- item = data[0]
- return modelsv1.Item(
- title=item.new_title,
- size=item.new_size,
- description=item.new_description,
- sub=modelsv1.SubItem(name=item.new_sub.new_sub_name),
- multi=[modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi],
- )
- return modelsv1.Item(title="", size=0, sub=modelsv1.SubItem(name=""))
-
-
-@app.post("/v2-to-v1/same-name")
-def handle_v2_same_name_to_v1(
- item1: modelsv2.Item, item2: modelsv2b.Item
-) -> modelsv1.Item:
- return modelsv1.Item(
- title=item1.new_title,
- size=item2.dup_size,
- description=item1.new_description,
- sub=modelsv1.SubItem(name=item1.new_sub.new_sub_name),
- multi=[modelsv1.SubItem(name=s.dup_sub_name) for s in item2.dup_multi],
- )
-
-
-@app.post("/v2-to-v1/list-of-items-to-list-of-items")
-def handle_v2_items_in_list_to_v1_item_in_list(
- data1: List[modelsv2.ItemInList], data2: List[modelsv2b.ItemInList]
-) -> List[modelsv1.ItemInList]:
- result = []
- item1 = data1[0]
- item2 = data2[0]
- result = [
- modelsv1.ItemInList(name1=item1.name2),
- modelsv1.ItemInList(name1=item2.dup_name2),
- ]
- return result
diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv1.py b/tests/test_pydantic_v1_v2_multifile/modelsv1.py
deleted file mode 100644
index 889291a1a7..0000000000
--- a/tests/test_pydantic_v1_v2_multifile/modelsv1.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import List, Union
-
-from fastapi._compat.v1 import BaseModel
-
-
-class SubItem(BaseModel):
- name: str
-
-
-class Item(BaseModel):
- title: str
- size: int
- description: Union[str, None] = None
- sub: SubItem
- multi: List[SubItem] = []
-
-
-class ItemInList(BaseModel):
- name1: str
diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv2.py b/tests/test_pydantic_v1_v2_multifile/modelsv2.py
deleted file mode 100644
index 2c8c6ea356..0000000000
--- a/tests/test_pydantic_v1_v2_multifile/modelsv2.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import List, Union
-
-from pydantic import BaseModel
-
-
-class SubItem(BaseModel):
- new_sub_name: str
-
-
-class Item(BaseModel):
- new_title: str
- new_size: int
- new_description: Union[str, None] = None
- new_sub: SubItem
- new_multi: List[SubItem] = []
-
-
-class ItemInList(BaseModel):
- name2: str
diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv2b.py b/tests/test_pydantic_v1_v2_multifile/modelsv2b.py
deleted file mode 100644
index dc0c06c669..0000000000
--- a/tests/test_pydantic_v1_v2_multifile/modelsv2b.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import List, Union
-
-from pydantic import BaseModel
-
-
-class SubItem(BaseModel):
- dup_sub_name: str
-
-
-class Item(BaseModel):
- dup_title: str
- dup_size: int
- dup_description: Union[str, None] = None
- dup_sub: SubItem
- dup_multi: List[SubItem] = []
-
-
-class ItemInList(BaseModel):
- dup_name2: str
diff --git a/tests/test_pydantic_v1_v2_multifile/test_multifile.py b/tests/test_pydantic_v1_v2_multifile/test_multifile.py
deleted file mode 100644
index e66d102fb3..0000000000
--- a/tests/test_pydantic_v1_v2_multifile/test_multifile.py
+++ /dev/null
@@ -1,1226 +0,0 @@
-import sys
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-
-from .main import app
-
-client = TestClient(app)
-
-
-def test_v1_to_v2_item():
- response = client.post(
- "/v1-to-v2/item",
- json={"title": "Test", "size": 10, "sub": {"name": "SubTest"}},
- )
- assert response.status_code == 200
- assert response.json() == {
- "new_title": "Test",
- "new_size": 10,
- "new_description": None,
- "new_sub": {"new_sub_name": "SubTest"},
- "new_multi": [],
- }
-
-
-def test_v2_to_v1_item():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "NewTest",
- "new_size": 20,
- "new_sub": {"new_sub_name": "NewSubTest"},
- },
- )
- assert response.status_code == 200
- assert response.json() == {
- "title": "NewTest",
- "size": 20,
- "description": None,
- "sub": {"name": "NewSubTest"},
- "multi": [],
- }
-
-
-def test_v1_to_v2_item_to_list():
- response = client.post(
- "/v1-to-v2/item-to-list",
- json={"title": "ListTest", "size": 30, "sub": {"name": "SubListTest"}},
- )
- assert response.status_code == 200
- assert response.json() == [
- {
- "new_title": "ListTest",
- "new_size": 30,
- "new_description": None,
- "new_sub": {"new_sub_name": "SubListTest"},
- "new_multi": [],
- },
- {
- "new_title": "ListTest",
- "new_size": 30,
- "new_description": None,
- "new_sub": {"new_sub_name": "SubListTest"},
- "new_multi": [],
- },
- ]
-
-
-def test_v1_to_v2_list_to_list():
- response = client.post(
- "/v1-to-v2/list-to-list",
- json=[
- {"title": "Item1", "size": 40, "sub": {"name": "Sub1"}},
- {"title": "Item2", "size": 50, "sub": {"name": "Sub2"}},
- ],
- )
- assert response.status_code == 200
- assert response.json() == [
- {
- "new_title": "Item1",
- "new_size": 40,
- "new_description": None,
- "new_sub": {"new_sub_name": "Sub1"},
- "new_multi": [],
- },
- {
- "new_title": "Item2",
- "new_size": 50,
- "new_description": None,
- "new_sub": {"new_sub_name": "Sub2"},
- "new_multi": [],
- },
- ]
-
-
-def test_v1_to_v2_list_to_item():
- response = client.post(
- "/v1-to-v2/list-to-item",
- json=[
- {"title": "FirstItem", "size": 60, "sub": {"name": "FirstSub"}},
- {"title": "SecondItem", "size": 70, "sub": {"name": "SecondSub"}},
- ],
- )
- assert response.status_code == 200
- assert response.json() == {
- "new_title": "FirstItem",
- "new_size": 60,
- "new_description": None,
- "new_sub": {"new_sub_name": "FirstSub"},
- "new_multi": [],
- }
-
-
-def test_v2_to_v1_item_to_list():
- response = client.post(
- "/v2-to-v1/item-to-list",
- json={
- "new_title": "ListNew",
- "new_size": 80,
- "new_sub": {"new_sub_name": "SubListNew"},
- },
- )
- assert response.status_code == 200
- assert response.json() == [
- {
- "title": "ListNew",
- "size": 80,
- "description": None,
- "sub": {"name": "SubListNew"},
- "multi": [],
- },
- {
- "title": "ListNew",
- "size": 80,
- "description": None,
- "sub": {"name": "SubListNew"},
- "multi": [],
- },
- ]
-
-
-def test_v2_to_v1_list_to_list():
- response = client.post(
- "/v2-to-v1/list-to-list",
- json=[
- {
- "new_title": "New1",
- "new_size": 90,
- "new_sub": {"new_sub_name": "NewSub1"},
- },
- {
- "new_title": "New2",
- "new_size": 100,
- "new_sub": {"new_sub_name": "NewSub2"},
- },
- ],
- )
- assert response.status_code == 200
- assert response.json() == [
- {
- "title": "New1",
- "size": 90,
- "description": None,
- "sub": {"name": "NewSub1"},
- "multi": [],
- },
- {
- "title": "New2",
- "size": 100,
- "description": None,
- "sub": {"name": "NewSub2"},
- "multi": [],
- },
- ]
-
-
-def test_v2_to_v1_list_to_item():
- response = client.post(
- "/v2-to-v1/list-to-item",
- json=[
- {
- "new_title": "FirstNew",
- "new_size": 110,
- "new_sub": {"new_sub_name": "FirstNewSub"},
- },
- {
- "new_title": "SecondNew",
- "new_size": 120,
- "new_sub": {"new_sub_name": "SecondNewSub"},
- },
- ],
- )
- assert response.status_code == 200
- assert response.json() == {
- "title": "FirstNew",
- "size": 110,
- "description": None,
- "sub": {"name": "FirstNewSub"},
- "multi": [],
- }
-
-
-def test_v1_to_v2_list_to_item_empty():
- response = client.post("/v1-to-v2/list-to-item", json=[])
- assert response.status_code == 200
- assert response.json() == {
- "new_title": "",
- "new_size": 0,
- "new_description": None,
- "new_sub": {"new_sub_name": ""},
- "new_multi": [],
- }
-
-
-def test_v2_to_v1_list_to_item_empty():
- response = client.post("/v2-to-v1/list-to-item", json=[])
- assert response.status_code == 200
- assert response.json() == {
- "title": "",
- "size": 0,
- "description": None,
- "sub": {"name": ""},
- "multi": [],
- }
-
-
-def test_v2_same_name_to_v1():
- response = client.post(
- "/v2-to-v1/same-name",
- json={
- "item1": {
- "new_title": "Title1",
- "new_size": 100,
- "new_description": "Description1",
- "new_sub": {"new_sub_name": "Sub1"},
- "new_multi": [{"new_sub_name": "Multi1"}],
- },
- "item2": {
- "dup_title": "Title2",
- "dup_size": 200,
- "dup_description": "Description2",
- "dup_sub": {"dup_sub_name": "Sub2"},
- "dup_multi": [
- {"dup_sub_name": "Multi2a"},
- {"dup_sub_name": "Multi2b"},
- ],
- },
- },
- )
- assert response.status_code == 200
- assert response.json() == {
- "title": "Title1",
- "size": 200,
- "description": "Description1",
- "sub": {"name": "Sub1"},
- "multi": [{"name": "Multi2a"}, {"name": "Multi2b"}],
- }
-
-
-def test_v2_items_in_list_to_v1_item_in_list():
- response = client.post(
- "/v2-to-v1/list-of-items-to-list-of-items",
- json={
- "data1": [{"name2": "Item1"}, {"name2": "Item2"}],
- "data2": [{"dup_name2": "Item3"}, {"dup_name2": "Item4"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == [
- {"name1": "Item1"},
- {"name1": "Item3"},
- ]
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/v1-to-v2/item": {
- "post": {
- "summary": "Handle V1 Item To V2",
- "operationId": "handle_v1_item_to_v2_v1_to_v2_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item": {
- "post": {
- "summary": "Handle V2 Item To V1",
- "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/item-to-list": {
- "post": {
- "summary": "Handle V1 Item To V2 List",
- "operationId": "handle_v1_item_to_v2_list_v1_to_v2_item_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- },
- "type": "array",
- "title": "Response Handle V1 Item To V2 List V1 To V2 Item To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/list-to-list": {
- "post": {
- "summary": "Handle V1 List To V2 List",
- "operationId": "handle_v1_list_to_v2_list_v1_to_v2_list_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- },
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- },
- "type": "array",
- "title": "Response Handle V1 List To V2 List V1 To V2 List To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/list-to-item": {
- "post": {
- "summary": "Handle V1 List To V2 Item",
- "operationId": "handle_v1_list_to_v2_item_v1_to_v2_list_to_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- },
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item-to-list": {
- "post": {
- "summary": "Handle V2 Item To V1 List",
- "operationId": "handle_v2_item_to_v1_list_v2_to_v1_item_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- ),
- ),
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- },
- "type": "array",
- "title": "Response Handle V2 Item To V1 List V2 To V1 Item To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-to-list": {
- "post": {
- "summary": "Handle V2 List To V1 List",
- "operationId": "handle_v2_list_to_v1_list_v2_to_v1_list_to_list_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": pydantic_snapshot(
- v2=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- ),
- ),
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- },
- "type": "array",
- "title": "Response Handle V2 List To V1 List V2 To V1 List To List Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-to-item": {
- "post": {
- "summary": "Handle V2 List To V1 Item",
- "operationId": "handle_v2_list_to_v1_item_v2_to_v1_list_to_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": pydantic_snapshot(
- v2=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- }
- ),
- v1=snapshot(
- {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- }
- ),
- ),
- "type": "array",
- "title": "Data",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/same-name": {
- "post": {
- "summary": "Handle V2 Same Name To V1",
- "operationId": "handle_v2_same_name_to_v1_v2_to_v1_same_name_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post"
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/list-of-items-to-list-of-items": {
- "post": {
- "summary": "Handle V2 Items In List To V1 Item In List",
- "operationId": "handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post"
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList"
- },
- "type": "array",
- "title": "Response Handle V2 Items In List To V1 Item In List V2 To V1 List Of Items To List Of Items Post",
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": pydantic_snapshot(
- v1=snapshot(
- {
- "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": {
- "properties": {
- "data1": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList"
- },
- "type": "array",
- "title": "Data1",
- },
- "data2": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList"
- },
- "type": "array",
- "title": "Data2",
- },
- },
- "type": "object",
- "required": ["data1", "data2"],
- "title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post",
- },
- "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": {
- "properties": {
- "item1": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
- },
- "item2": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item"
- },
- },
- "type": "object",
- "required": ["item1", "item2"],
- "title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [
- {"type": "string"},
- {"type": "integer"},
- ]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {
- "type": "string",
- "title": "Description",
- },
- "sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem"
- },
- "multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem"
- },
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": {
- "properties": {
- "name1": {"type": "string", "title": "Name1"}
- },
- "type": "object",
- "required": ["name1"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": {
- "properties": {
- "name": {"type": "string", "title": "Name"}
- },
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__Item": {
- "properties": {
- "new_title": {
- "type": "string",
- "title": "New Title",
- },
- "new_size": {
- "type": "integer",
- "title": "New Size",
- },
- "new_description": {
- "type": "string",
- "title": "New Description",
- },
- "new_sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "new_multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "type": "array",
- "title": "New Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["new_title", "new_size", "new_sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": {
- "properties": {
- "name2": {"type": "string", "title": "Name2"}
- },
- "type": "object",
- "required": ["name2"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": {
- "properties": {
- "new_sub_name": {
- "type": "string",
- "title": "New Sub Name",
- }
- },
- "type": "object",
- "required": ["new_sub_name"],
- "title": "SubItem",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": {
- "properties": {
- "dup_title": {
- "type": "string",
- "title": "Dup Title",
- },
- "dup_size": {
- "type": "integer",
- "title": "Dup Size",
- },
- "dup_description": {
- "type": "string",
- "title": "Dup Description",
- },
- "dup_sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem"
- },
- "dup_multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem"
- },
- "type": "array",
- "title": "Dup Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["dup_title", "dup_size", "dup_sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": {
- "properties": {
- "dup_name2": {
- "type": "string",
- "title": "Dup Name2",
- }
- },
- "type": "object",
- "required": ["dup_name2"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": {
- "properties": {
- "dup_sub_name": {
- "type": "string",
- "title": "Dup Sub Name",
- }
- },
- "type": "object",
- "required": ["dup_sub_name"],
- "title": "SubItem",
- },
- }
- ),
- v2=snapshot(
- {
- "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": {
- "properties": {
- "data1": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList"
- },
- "type": "array",
- "title": "Data1",
- },
- "data2": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList"
- },
- "type": "array",
- "title": "Data2",
- },
- },
- "type": "object",
- "required": ["data1", "data2"],
- "title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post",
- },
- "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": {
- "properties": {
- "item1": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
- },
- "item2": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item"
- },
- },
- "type": "object",
- "required": ["item1", "item2"],
- "title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [
- {"type": "string"},
- {"type": "integer"},
- ]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {
- "type": "string",
- "title": "Description",
- },
- "sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem"
- },
- "multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem"
- },
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": {
- "properties": {
- "name1": {"type": "string", "title": "Name1"}
- },
- "type": "object",
- "required": ["name1"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": {
- "properties": {
- "name": {"type": "string", "title": "Name"}
- },
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__Item": {
- "properties": {
- "new_title": {
- "type": "string",
- "title": "New Title",
- },
- "new_size": {
- "type": "integer",
- "title": "New Size",
- },
- "new_description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "New Description",
- },
- "new_sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "new_multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "type": "array",
- "title": "New Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["new_title", "new_size", "new_sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input": {
- "properties": {
- "new_title": {
- "type": "string",
- "title": "New Title",
- },
- "new_size": {
- "type": "integer",
- "title": "New Size",
- },
- "new_description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "New Description",
- },
- "new_sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "new_multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
- },
- "type": "array",
- "title": "New Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["new_title", "new_size", "new_sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": {
- "properties": {
- "name2": {"type": "string", "title": "Name2"}
- },
- "type": "object",
- "required": ["name2"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": {
- "properties": {
- "new_sub_name": {
- "type": "string",
- "title": "New Sub Name",
- }
- },
- "type": "object",
- "required": ["new_sub_name"],
- "title": "SubItem",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": {
- "properties": {
- "dup_title": {
- "type": "string",
- "title": "Dup Title",
- },
- "dup_size": {
- "type": "integer",
- "title": "Dup Size",
- },
- "dup_description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Dup Description",
- },
- "dup_sub": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem"
- },
- "dup_multi": {
- "items": {
- "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem"
- },
- "type": "array",
- "title": "Dup Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["dup_title", "dup_size", "dup_sub"],
- "title": "Item",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": {
- "properties": {
- "dup_name2": {
- "type": "string",
- "title": "Dup Name2",
- }
- },
- "type": "object",
- "required": ["dup_name2"],
- "title": "ItemInList",
- },
- "tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": {
- "properties": {
- "dup_sub_name": {
- "type": "string",
- "title": "Dup Sub Name",
- }
- },
- "type": "object",
- "required": ["dup_sub_name"],
- "title": "SubItem",
- },
- }
- ),
- ),
- },
- }
- )
diff --git a/tests/test_pydantic_v1_v2_noneable.py b/tests/test_pydantic_v1_v2_noneable.py
deleted file mode 100644
index d2d6f6635b..0000000000
--- a/tests/test_pydantic_v1_v2_noneable.py
+++ /dev/null
@@ -1,766 +0,0 @@
-import sys
-from typing import Any, List, Union
-
-from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-from fastapi import FastAPI
-from fastapi._compat.v1 import BaseModel
-from fastapi.testclient import TestClient
-from inline_snapshot import snapshot
-from pydantic import BaseModel as NewBaseModel
-
-
-class SubItem(BaseModel):
- name: str
-
-
-class Item(BaseModel):
- title: str
- size: int
- description: Union[str, None] = None
- sub: SubItem
- multi: List[SubItem] = []
-
-
-class NewSubItem(NewBaseModel):
- new_sub_name: str
-
-
-class NewItem(NewBaseModel):
- new_title: str
- new_size: int
- new_description: Union[str, None] = None
- new_sub: NewSubItem
- new_multi: List[NewSubItem] = []
-
-
-app = FastAPI()
-
-
-@app.post("/v1-to-v2/")
-def handle_v1_item_to_v2(data: Item) -> Union[NewItem, None]:
- if data.size < 0:
- return None
- return NewItem(
- new_title=data.title,
- new_size=data.size,
- new_description=data.description,
- new_sub=NewSubItem(new_sub_name=data.sub.name),
- new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi],
- )
-
-
-@app.post("/v1-to-v2/item-filter", response_model=Union[NewItem, None])
-def handle_v1_item_to_v2_filter(data: Item) -> Any:
- if data.size < 0:
- return None
- result = {
- "new_title": data.title,
- "new_size": data.size,
- "new_description": data.description,
- "new_sub": {"new_sub_name": data.sub.name, "new_sub_secret": "sub_hidden"},
- "new_multi": [
- {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} for s in data.multi
- ],
- "secret": "hidden_v1_to_v2",
- }
- return result
-
-
-@app.post("/v2-to-v1/item")
-def handle_v2_item_to_v1(data: NewItem) -> Union[Item, None]:
- if data.new_size < 0:
- return None
- return Item(
- title=data.new_title,
- size=data.new_size,
- description=data.new_description,
- sub=SubItem(name=data.new_sub.new_sub_name),
- multi=[SubItem(name=s.new_sub_name) for s in data.new_multi],
- )
-
-
-@app.post("/v2-to-v1/item-filter", response_model=Union[Item, None])
-def handle_v2_item_to_v1_filter(data: NewItem) -> Any:
- if data.new_size < 0:
- return None
- result = {
- "title": data.new_title,
- "size": data.new_size,
- "description": data.new_description,
- "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"},
- "multi": [
- {"name": s.new_sub_name, "sub_secret": "sub_hidden"} for s in data.new_multi
- ],
- "secret": "hidden_v2_to_v1",
- }
- return result
-
-
-client = TestClient(app)
-
-
-def test_v1_to_v2_item_success():
- response = client.post(
- "/v1-to-v2/",
- json={
- "title": "Old Item",
- "size": 100,
- "description": "V1 description",
- "sub": {"name": "V1 Sub"},
- "multi": [{"name": "M1"}, {"name": "M2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "Old Item",
- "new_size": 100,
- "new_description": "V1 description",
- "new_sub": {"new_sub_name": "V1 Sub"},
- "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}],
- }
-
-
-def test_v1_to_v2_item_returns_none():
- response = client.post(
- "/v1-to-v2/",
- json={"title": "Invalid Item", "size": -10, "sub": {"name": "Sub"}},
- )
- assert response.status_code == 200, response.text
- assert response.json() is None
-
-
-def test_v1_to_v2_item_minimal():
- response = client.post(
- "/v1-to-v2/", json={"title": "Minimal", "size": 50, "sub": {"name": "MinSub"}}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "new_title": "Minimal",
- "new_size": 50,
- "new_description": None,
- "new_sub": {"new_sub_name": "MinSub"},
- "new_multi": [],
- }
-
-
-def test_v1_to_v2_item_filter_success():
- response = client.post(
- "/v1-to-v2/item-filter",
- json={
- "title": "Filtered Item",
- "size": 50,
- "sub": {"name": "Sub"},
- "multi": [{"name": "Multi1"}],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result["new_title"] == "Filtered Item"
- assert result["new_size"] == 50
- assert result["new_sub"]["new_sub_name"] == "Sub"
- assert result["new_multi"][0]["new_sub_name"] == "Multi1"
- # Verify secret fields are filtered out
- assert "secret" not in result
- assert "new_sub_secret" not in result["new_sub"]
- assert "new_sub_secret" not in result["new_multi"][0]
-
-
-def test_v1_to_v2_item_filter_returns_none():
- response = client.post(
- "/v1-to-v2/item-filter",
- json={"title": "Invalid", "size": -1, "sub": {"name": "Sub"}},
- )
- assert response.status_code == 200, response.text
- assert response.json() is None
-
-
-def test_v2_to_v1_item_success():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "New Item",
- "new_size": 200,
- "new_description": "V2 description",
- "new_sub": {"new_sub_name": "V2 Sub"},
- "new_multi": [{"new_sub_name": "N1"}, {"new_sub_name": "N2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "New Item",
- "size": 200,
- "description": "V2 description",
- "sub": {"name": "V2 Sub"},
- "multi": [{"name": "N1"}, {"name": "N2"}],
- }
-
-
-def test_v2_to_v1_item_returns_none():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "Invalid New",
- "new_size": -5,
- "new_sub": {"new_sub_name": "NewSub"},
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() is None
-
-
-def test_v2_to_v1_item_minimal():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "MinimalNew",
- "new_size": 75,
- "new_sub": {"new_sub_name": "MinNewSub"},
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "title": "MinimalNew",
- "size": 75,
- "description": None,
- "sub": {"name": "MinNewSub"},
- "multi": [],
- }
-
-
-def test_v2_to_v1_item_filter_success():
- response = client.post(
- "/v2-to-v1/item-filter",
- json={
- "new_title": "Filtered New",
- "new_size": 75,
- "new_sub": {"new_sub_name": "NewSub"},
- "new_multi": [],
- },
- )
- assert response.status_code == 200, response.text
- result = response.json()
- assert result["title"] == "Filtered New"
- assert result["size"] == 75
- assert result["sub"]["name"] == "NewSub"
- # Verify secret fields are filtered out
- assert "secret" not in result
- assert "sub_secret" not in result["sub"]
-
-
-def test_v2_to_v1_item_filter_returns_none():
- response = client.post(
- "/v2-to-v1/item-filter",
- json={
- "new_title": "Invalid Filtered",
- "new_size": -100,
- "new_sub": {"new_sub_name": "Sub"},
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() is None
-
-
-def test_v1_to_v2_validation_error():
- response = client.post("/v1-to-v2/", json={"title": "Missing fields"})
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- {
- "loc": ["body", "size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-def test_v1_to_v2_nested_validation_error():
- response = client.post(
- "/v1-to-v2/",
- json={"title": "Bad sub", "size": 100, "sub": {"wrong_field": "value"}},
- )
- assert response.status_code == 422, response.text
- error_detail = response.json()["detail"]
- assert len(error_detail) == 1
- assert error_detail[0]["loc"] == ["body", "sub", "name"]
-
-
-def test_v1_to_v2_type_validation_error():
- response = client.post(
- "/v1-to-v2/",
- json={"title": "Bad type", "size": "not_a_number", "sub": {"name": "Sub"}},
- )
- assert response.status_code == 422, response.text
- error_detail = response.json()["detail"]
- assert len(error_detail) == 1
- assert error_detail[0]["loc"] == ["body", "size"]
-
-
-def test_v2_to_v1_validation_error():
- response = client.post("/v2-to-v1/item", json={"new_title": "Missing fields"})
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": pydantic_snapshot(
- v2=snapshot(
- [
- {
- "type": "missing",
- "loc": ["body", "new_size"],
- "msg": "Field required",
- "input": {"new_title": "Missing fields"},
- },
- {
- "type": "missing",
- "loc": ["body", "new_sub"],
- "msg": "Field required",
- "input": {"new_title": "Missing fields"},
- },
- ]
- ),
- v1=snapshot(
- [
- {
- "loc": ["body", "new_size"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "new_sub"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- ),
- )
- }
- )
-
-
-def test_v2_to_v1_nested_validation_error():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "Bad sub",
- "new_size": 200,
- "new_sub": {"wrong_field": "value"},
- },
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- pydantic_snapshot(
- v2=snapshot(
- {
- "type": "missing",
- "loc": ["body", "new_sub", "new_sub_name"],
- "msg": "Field required",
- "input": {"wrong_field": "value"},
- }
- ),
- v1=snapshot(
- {
- "loc": ["body", "new_sub", "new_sub_name"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ),
- )
- ]
- }
- )
-
-
-def test_v2_to_v1_type_validation_error():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "Bad type",
- "new_size": "not_a_number",
- "new_sub": {"new_sub_name": "Sub"},
- },
- )
- assert response.status_code == 422, response.text
- assert response.json() == snapshot(
- {
- "detail": [
- pydantic_snapshot(
- v2=snapshot(
- {
- "type": "int_parsing",
- "loc": ["body", "new_size"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "not_a_number",
- }
- ),
- v1=snapshot(
- {
- "loc": ["body", "new_size"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ),
- )
- ]
- }
- )
-
-
-def test_v1_to_v2_with_multi_items():
- response = client.post(
- "/v1-to-v2/",
- json={
- "title": "Complex Item",
- "size": 300,
- "description": "Item with multiple sub-items",
- "sub": {"name": "Main Sub"},
- "multi": [{"name": "Sub1"}, {"name": "Sub2"}, {"name": "Sub3"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "new_title": "Complex Item",
- "new_size": 300,
- "new_description": "Item with multiple sub-items",
- "new_sub": {"new_sub_name": "Main Sub"},
- "new_multi": [
- {"new_sub_name": "Sub1"},
- {"new_sub_name": "Sub2"},
- {"new_sub_name": "Sub3"},
- ],
- }
- )
-
-
-def test_v2_to_v1_with_multi_items():
- response = client.post(
- "/v2-to-v1/item",
- json={
- "new_title": "Complex New Item",
- "new_size": 400,
- "new_description": "New item with multiple sub-items",
- "new_sub": {"new_sub_name": "Main New Sub"},
- "new_multi": [{"new_sub_name": "NewSub1"}, {"new_sub_name": "NewSub2"}],
- },
- )
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "title": "Complex New Item",
- "size": 400,
- "description": "New item with multiple sub-items",
- "sub": {"name": "Main New Sub"},
- "multi": [{"name": "NewSub1"}, {"name": "NewSub2"}],
- }
- )
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/v1-to-v2/": {
- "post": {
- "summary": "Handle V1 Item To V2",
- "operationId": "handle_v1_item_to_v2_v1_to_v2__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "anyOf": [
- {
- "$ref": "#/components/schemas/NewItem"
- },
- {"type": "null"},
- ],
- "title": "Response Handle V1 Item To V2 V1 To V2 Post",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/NewItem"}
- ),
- )
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v1-to-v2/item-filter": {
- "post": {
- "summary": "Handle V1 Item To V2 Filter",
- "operationId": "handle_v1_item_to_v2_filter_v1_to_v2_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Item"
- }
- ],
- "title": "Data",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/Item"}
- ),
- )
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "anyOf": [
- {
- "$ref": "#/components/schemas/NewItem"
- },
- {"type": "null"},
- ],
- "title": "Response Handle V1 Item To V2 Filter V1 To V2 Item Filter Post",
- }
- ),
- v1=snapshot(
- {"$ref": "#/components/schemas/NewItem"}
- ),
- )
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item": {
- "post": {
- "summary": "Handle V2 Item To V1",
- "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/NewItem"}
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/v2-to-v1/item-filter": {
- "post": {
- "summary": "Handle V2 Item To V1 Filter",
- "operationId": "handle_v2_item_to_v1_filter_v2_to_v1_item_filter_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/NewItem"}
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "title": {"type": "string", "title": "Title"},
- "size": {"type": "integer", "title": "Size"},
- "description": {"type": "string", "title": "Description"},
- "sub": {"$ref": "#/components/schemas/SubItem"},
- "multi": {
- "items": {"$ref": "#/components/schemas/SubItem"},
- "type": "array",
- "title": "Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["title", "size", "sub"],
- "title": "Item",
- },
- "NewItem": {
- "properties": {
- "new_title": {"type": "string", "title": "New Title"},
- "new_size": {"type": "integer", "title": "New Size"},
- "new_description": pydantic_snapshot(
- v2=snapshot(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "New Description",
- }
- ),
- v1=snapshot(
- {"type": "string", "title": "New Description"}
- ),
- ),
- "new_sub": {"$ref": "#/components/schemas/NewSubItem"},
- "new_multi": {
- "items": {"$ref": "#/components/schemas/NewSubItem"},
- "type": "array",
- "title": "New Multi",
- "default": [],
- },
- },
- "type": "object",
- "required": ["new_title", "new_size", "new_sub"],
- "title": "NewItem",
- },
- "NewSubItem": {
- "properties": {
- "new_sub_name": {"type": "string", "title": "New Sub Name"}
- },
- "type": "object",
- "required": ["new_sub_name"],
- "title": "NewSubItem",
- },
- "SubItem": {
- "properties": {"name": {"type": "string", "title": "Name"}},
- "type": "object",
- "required": ["name"],
- "title": "SubItem",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py b/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py
new file mode 100644
index 0000000000..b72b0518a1
--- /dev/null
+++ b/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py
@@ -0,0 +1,52 @@
+from __future__ import annotations
+
+import uuid
+from dataclasses import dataclass, field
+from typing import Union
+
+from dirty_equals import IsUUID
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+
+
+@dataclass
+class Item:
+ id: uuid.UUID
+ name: str
+ price: float
+ tags: list[str] = field(default_factory=list)
+ description: Union[str, None] = None
+ tax: Union[float, None] = None
+
+
+app = FastAPI()
+
+
+@app.get("/item", response_model=Item)
+async def read_item():
+ return {
+ "id": uuid.uuid4(),
+ "name": "Island In The Moon",
+ "price": 12.99,
+ "description": "A place to be be playin' and havin' fun",
+ "tags": ["breater"],
+ }
+
+
+client = TestClient(app)
+
+
+def test_annotations():
+ response = client.get("/item")
+ assert response.status_code == 200, response.text
+ assert response.json() == snapshot(
+ {
+ "id": IsUUID(),
+ "name": "Island In The Moon",
+ "price": 12.99,
+ "tags": ["breater"],
+ "description": "A place to be be playin' and havin' fun",
+ "tax": None,
+ }
+ )
diff --git a/tests/test_query.py b/tests/test_query.py
index 57f551d2ab..c25960caca 100644
--- a/tests/test_query.py
+++ b/tests/test_query.py
@@ -1,4 +1,3 @@
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from .main import app
@@ -9,29 +8,16 @@ client = TestClient(app)
def test_query():
response = client.get("/query")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_query_baz():
@@ -43,29 +29,16 @@ def test_query_query_baz():
def test_query_not_declared_baz():
response = client.get("/query?not_declared=baz")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_optional():
@@ -89,29 +62,16 @@ def test_query_optional_not_declared_baz():
def test_query_int():
response = client.get("/query/int")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_int_query_42():
@@ -123,85 +83,46 @@ def test_query_int_query_42():
def test_query_int_query_42_5():
response = client.get("/query/int?query=42.5")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "query"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "42.5",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "query"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "42.5",
+ }
+ ]
+ }
def test_query_int_query_baz():
response = client.get("/query/int?query=baz")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "query"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "baz",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "query"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "baz",
+ }
+ ]
+ }
def test_query_int_not_declared_baz():
response = client.get("/query/int?not_declared=baz")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_int_optional():
@@ -219,29 +140,16 @@ def test_query_int_optional_query_50():
def test_query_int_optional_query_foo():
response = client.get("/query/int/optional?query=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "query"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foo",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "query"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo",
+ }
+ ]
+ }
def test_query_int_default():
@@ -259,29 +167,16 @@ def test_query_int_default_query_50():
def test_query_int_default_query_foo():
response = client.get("/query/int/default?query=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "query"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foo",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "query"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo",
+ }
+ ]
+ }
def test_query_param():
@@ -299,29 +194,16 @@ def test_query_param_query_50():
def test_query_param_required():
response = client.get("/query/param-required")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_param_required_query_50():
@@ -333,29 +215,16 @@ def test_query_param_required_query_50():
def test_query_param_required_int():
response = client.get("/query/param-required/int")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "query"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "query"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_query_param_required_int_query_50():
@@ -367,29 +236,16 @@ def test_query_param_required_int_query_50():
def test_query_param_required_int_query_foo():
response = client.get("/query/param-required/int?query=foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["query", "query"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foo",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "query"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["query", "query"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo",
+ }
+ ]
+ }
def test_query_frozenset_query_1_query_1_query_2():
diff --git a/tests/test_query_cookie_header_model_extra_params.py b/tests/test_query_cookie_header_model_extra_params.py
new file mode 100644
index 0000000000..d361e1e533
--- /dev/null
+++ b/tests/test_query_cookie_header_model_extra_params.py
@@ -0,0 +1,105 @@
+from fastapi import Cookie, FastAPI, Header, Query
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Model(BaseModel):
+ param: str
+
+ model_config = {"extra": "allow"}
+
+
+@app.get("/query")
+async def query_model_with_extra(data: Model = Query()):
+ return data
+
+
+@app.get("/header")
+async def header_model_with_extra(data: Model = Header()):
+ return data
+
+
+@app.get("/cookie")
+async def cookies_model_with_extra(data: Model = Cookie()):
+ return data
+
+
+def test_query_pass_extra_list():
+ client = TestClient(app)
+ resp = client.get(
+ "/query",
+ params={
+ "param": "123",
+ "param2": ["456", "789"], # Pass a list of values as extra parameter
+ },
+ )
+ assert resp.status_code == 200
+ assert resp.json() == {
+ "param": "123",
+ "param2": ["456", "789"],
+ }
+
+
+def test_query_pass_extra_single():
+ client = TestClient(app)
+ resp = client.get(
+ "/query",
+ params={
+ "param": "123",
+ "param2": "456",
+ },
+ )
+ assert resp.status_code == 200
+ assert resp.json() == {
+ "param": "123",
+ "param2": "456",
+ }
+
+
+def test_header_pass_extra_list():
+ client = TestClient(app)
+
+ resp = client.get(
+ "/header",
+ headers=[
+ ("param", "123"),
+ ("param2", "456"), # Pass a list of values as extra parameter
+ ("param2", "789"),
+ ],
+ )
+ assert resp.status_code == 200
+ resp_json = resp.json()
+ assert "param2" in resp_json
+ assert resp_json["param2"] == ["456", "789"]
+
+
+def test_header_pass_extra_single():
+ client = TestClient(app)
+
+ resp = client.get(
+ "/header",
+ headers=[
+ ("param", "123"),
+ ("param2", "456"),
+ ],
+ )
+ assert resp.status_code == 200
+ resp_json = resp.json()
+ assert "param2" in resp_json
+ assert resp_json["param2"] == "456"
+
+
+def test_cookie_pass_extra_list():
+ client = TestClient(app)
+ client.cookies = [
+ ("param", "123"),
+ ("param2", "456"), # Pass a list of values as extra parameter
+ ("param2", "789"),
+ ]
+ resp = client.get("/cookie")
+ assert resp.status_code == 200
+ resp_json = resp.json()
+ assert "param2" in resp_json
+ assert resp_json["param2"] == "789" # Cookies only keep the last value
diff --git a/tests/test_read_with_orm_mode.py b/tests/test_read_with_orm_mode.py
index b359874439..cd7389252a 100644
--- a/tests/test_read_with_orm_mode.py
+++ b/tests/test_read_with_orm_mode.py
@@ -4,10 +4,7 @@ from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
-from .utils import needs_pydanticv1, needs_pydanticv2
-
-@needs_pydanticv2
def test_read_with_orm_mode() -> None:
class PersonBase(BaseModel):
name: str
@@ -44,45 +41,3 @@ def test_read_with_orm_mode() -> None:
assert data["name"] == person_data["name"]
assert data["lastname"] == person_data["lastname"]
assert data["full_name"] == person_data["name"] + " " + person_data["lastname"]
-
-
-@needs_pydanticv1
-def test_read_with_orm_mode_pv1() -> None:
- class PersonBase(BaseModel):
- name: str
- lastname: str
-
- class Person(PersonBase):
- @property
- def full_name(self) -> str:
- return f"{self.name} {self.lastname}"
-
- class Config:
- orm_mode = True
- read_with_orm_mode = True
-
- class PersonCreate(PersonBase):
- pass
-
- class PersonRead(PersonBase):
- full_name: str
-
- class Config:
- orm_mode = True
-
- app = FastAPI()
-
- @app.post("/people/", response_model=PersonRead)
- def create_person(person: PersonCreate) -> Any:
- db_person = Person.from_orm(person)
- return db_person
-
- client = TestClient(app)
-
- person_data = {"name": "Dive", "lastname": "Wilson"}
- response = client.post("/people/", json=person_data)
- data = response.json()
- assert response.status_code == 200, response.text
- assert data["name"] == person_data["name"]
- assert data["lastname"] == person_data["lastname"]
- assert data["full_name"] == person_data["name"] + " " + person_data["lastname"]
diff --git a/tests/test_regex_deprecated_body.py b/tests/test_regex_deprecated_body.py
index 74654ff3ce..5b4daa450f 100644
--- a/tests/test_regex_deprecated_body.py
+++ b/tests/test_regex_deprecated_body.py
@@ -1,15 +1,17 @@
+from typing import Annotated
+
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, Form
+from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
+from inline_snapshot import snapshot
from .utils import needs_py310
def get_client():
app = FastAPI()
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.post("/items/")
async def read_items(
@@ -45,31 +47,17 @@ def test_query_nonregexquery():
client = get_client()
response = client.post("/items/", data={"q": "nonregexquery"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["body", "q"],
- "msg": "String should match pattern '^fixedquery$'",
- "input": "nonregexquery",
- "ctx": {"pattern": "^fixedquery$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"pattern": "^fixedquery$"},
- "loc": ["body", "q"],
- "msg": 'string does not match regex "^fixedquery$"',
- "type": "value_error.str.regex",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["body", "q"],
+ "msg": "String should match pattern '^fixedquery$'",
+ "input": "nonregexquery",
+ "ctx": {"pattern": "^fixedquery$"},
+ }
+ ]
+ }
@needs_py310
@@ -77,104 +65,88 @@ def test_openapi_schema():
client = get_client()
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- # insert_assert(response.json())
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "post": {
- "summary": "Read Items",
- "operationId": "read_items_items__post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_read_items_items__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
"$ref": "#/components/schemas/Body_read_items_items__post"
}
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
}
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
},
},
- },
+ }
}
- }
- },
- "components": {
- "schemas": {
- "Body_read_items_items__post": {
- "properties": {
- "q": IsDict(
- {
+ },
+ "components": {
+ "schemas": {
+ "Body_read_items_items__post": {
+ "properties": {
+ "q": {
"anyOf": [
{"type": "string", "pattern": "^fixedquery$"},
{"type": "null"},
],
"title": "Q",
}
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"type": "string", "pattern": "^fixedquery$", "title": "Q"}
- )
- },
- "type": "object",
- "title": "Body_read_items_items__post",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
},
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
+ "type": "object",
+ "title": "Body_read_items_items__post",
},
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
+ )
diff --git a/tests/test_regex_deprecated_params.py b/tests/test_regex_deprecated_params.py
index 2ce64c6862..d6eaa45fb1 100644
--- a/tests/test_regex_deprecated_params.py
+++ b/tests/test_regex_deprecated_params.py
@@ -1,15 +1,16 @@
+from typing import Annotated
+
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI, Query
+from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
from .utils import needs_py310
def get_client():
app = FastAPI()
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/items/")
async def read_items(
@@ -45,31 +46,17 @@ def test_query_params_str_validations_item_query_nonregexquery():
client = get_client()
response = client.get("/items/", params={"q": "nonregexquery"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["query", "q"],
- "msg": "String should match pattern '^fixedquery$'",
- "input": "nonregexquery",
- "ctx": {"pattern": "^fixedquery$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"pattern": "^fixedquery$"},
- "loc": ["query", "q"],
- "msg": 'string does not match regex "^fixedquery$"',
- "type": "value_error.str.regex",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["query", "q"],
+ "msg": "String should match pattern '^fixedquery$'",
+ "input": "nonregexquery",
+ "ctx": {"pattern": "^fixedquery$"},
+ }
+ ]
+ }
@needs_py310
@@ -91,23 +78,13 @@ def test_openapi_schema():
"name": "q",
"in": "query",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [
- {"type": "string", "pattern": "^fixedquery$"},
- {"type": "null"},
- ],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "pattern": "^fixedquery$",
- "title": "Q",
- }
- ),
+ "schema": {
+ "anyOf": [
+ {"type": "string", "pattern": "^fixedquery$"},
+ {"type": "null"},
+ ],
+ "title": "Q",
+ },
}
],
"responses": {
diff --git a/tests/test_request_body_parameters_media_type.py b/tests/test_request_body_parameters_media_type.py
index 8c72fee54a..d1bff9ddf4 100644
--- a/tests/test_request_body_parameters_media_type.py
+++ b/tests/test_request_body_parameters_media_type.py
@@ -1,5 +1,3 @@
-import typing
-
from fastapi import Body, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -28,7 +26,7 @@ async def create_product(data: Product = Body(media_type=media_type, embed=True)
@app.post("/shops")
async def create_shop(
data: Shop = Body(media_type=media_type),
- included: typing.List[Product] = Body(default=[], media_type=media_type),
+ included: list[Product] = Body(default=[], media_type=media_type),
):
pass # pragma: no cover
diff --git a/tests/test_request_param_model_by_alias.py b/tests/test_request_param_model_by_alias.py
new file mode 100644
index 0000000000..c29130d7a6
--- /dev/null
+++ b/tests/test_request_param_model_by_alias.py
@@ -0,0 +1,72 @@
+from dirty_equals import IsPartialDict
+from fastapi import Cookie, FastAPI, Header, Query
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+
+class Model(BaseModel):
+ param: str = Field(alias="param_alias")
+
+
+@app.get("/query")
+async def query_model(data: Model = Query()):
+ return {"param": data.param}
+
+
+@app.get("/header")
+async def header_model(data: Model = Header()):
+ return {"param": data.param}
+
+
+@app.get("/cookie")
+async def cookie_model(data: Model = Cookie()):
+ return {"param": data.param}
+
+
+def test_query_model_with_alias():
+ client = TestClient(app)
+ response = client.get("/query", params={"param_alias": "value"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"param": "value"}
+
+
+def test_header_model_with_alias():
+ client = TestClient(app)
+ response = client.get("/header", headers={"param_alias": "value"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"param": "value"}
+
+
+def test_cookie_model_with_alias():
+ client = TestClient(app)
+ client.cookies.set("param_alias", "value")
+ response = client.get("/cookie")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"param": "value"}
+
+
+def test_query_model_with_alias_by_name():
+ client = TestClient(app)
+ response = client.get("/query", params={"param": "value"})
+ assert response.status_code == 422, response.text
+ details = response.json()
+ assert details["detail"][0]["input"] == {"param": "value"}
+
+
+def test_header_model_with_alias_by_name():
+ client = TestClient(app)
+ response = client.get("/header", headers={"param": "value"})
+ assert response.status_code == 422, response.text
+ details = response.json()
+ assert details["detail"][0]["input"] == IsPartialDict({"param": "value"})
+
+
+def test_cookie_model_with_alias_by_name():
+ client = TestClient(app)
+ client.cookies.set("param", "value")
+ response = client.get("/cookie")
+ assert response.status_code == 422, response.text
+ details = response.json()
+ assert details["detail"][0]["input"] == {"param": "value"}
diff --git a/tests/test_request_params/__init__.py b/tests/test_request_params/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_request_params/test_body/__init__.py b/tests/test_request_params/test_body/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_request_params/test_body/test_list.py b/tests/test_request_params/test_body/test_list.py
new file mode 100644
index 0000000000..970e6a6607
--- /dev/null
+++ b/tests/test_request_params/test_body/test_list.py
@@ -0,0 +1,433 @@
+from typing import Annotated, Union
+
+import pytest
+from dirty_equals import IsOneOf, IsPartialDict
+from fastapi import Body, FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/required-list-str", operation_id="required_list_str")
+async def read_required_list_str(p: Annotated[list[str], Body(embed=True)]):
+ return {"p": p}
+
+
+class BodyModelRequiredListStr(BaseModel):
+ p: list[str]
+
+
+@app.post("/model-required-list-str", operation_id="model_required_list_str")
+def read_model_required_list_str(p: BodyModelRequiredListStr):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "items": {"type": "string"},
+ "title": "P",
+ "type": "array",
+ },
+ },
+ "required": ["p"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize("json", [None, {}])
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str_missing(path: str, json: Union[dict, None]):
+ client = TestClient(app)
+ response = client.post(path, json=json)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body", "p"], ["body"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/required-list-alias", operation_id="required_list_alias")
+async def read_required_list_alias(
+ p: Annotated[list[str], Body(embed=True, alias="p_alias")],
+):
+ return {"p": p}
+
+
+class BodyModelRequiredListAlias(BaseModel):
+ p: list[str] = Field(alias="p_alias")
+
+
+@app.post("/model-required-list-alias", operation_id="model_required_list_alias")
+async def read_model_required_list_alias(p: BodyModelRequiredListAlias):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias",
+ "/model-required-list-alias",
+ ],
+)
+def test_required_list_str_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "items": {"type": "string"},
+ "title": "P Alias",
+ "type": "array",
+ },
+ },
+ "required": ["p_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize("json", [None, {}])
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-alias", "/model-required-list-alias"],
+)
+def test_required_list_alias_missing(path: str, json: Union[dict, None]):
+ client = TestClient(app)
+ response = client.post(path, json=json)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body", "p_alias"], ["body"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-alias", "/model-required-list-alias"],
+)
+def test_required_list_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": ["hello", "world"]})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": ["hello", "world"]}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-alias", "/model-required-list-alias"],
+)
+def test_required_list_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_alias": ["hello", "world"]})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post(
+ "/required-list-validation-alias", operation_id="required_list_validation_alias"
+)
+def read_required_list_validation_alias(
+ p: Annotated[list[str], Body(embed=True, validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class BodyModelRequiredListValidationAlias(BaseModel):
+ p: list[str] = Field(validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-required-list-validation-alias",
+ operation_id="model_required_list_validation_alias",
+)
+async def read_model_required_list_validation_alias(
+ p: BodyModelRequiredListValidationAlias,
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-validation-alias", "/model-required-list-validation-alias"],
+)
+def test_required_list_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "items": {"type": "string"},
+ "title": "P Val Alias",
+ "type": "array",
+ },
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize("json", [None, {}])
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-validation-alias",
+ "/model-required-list-validation-alias",
+ ],
+)
+def test_required_list_validation_alias_missing(path: str, json: Union[dict, None]):
+ client = TestClient(app)
+ response = client.post(path, json=json)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body"], ["body", "p_val_alias"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-validation-alias",
+ "/model-required-list-validation-alias",
+ ],
+)
+def test_required_list_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": ["hello", "world"]})
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-validation-alias",
+ "/model-required-list-validation-alias",
+ ],
+)
+def test_required_list_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_val_alias": ["hello", "world"]})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post(
+ "/required-list-alias-and-validation-alias",
+ operation_id="required_list_alias_and_validation_alias",
+)
+def read_required_list_alias_and_validation_alias(
+ p: Annotated[
+ list[str], Body(embed=True, alias="p_alias", validation_alias="p_val_alias")
+ ],
+):
+ return {"p": p}
+
+
+class BodyModelRequiredListAliasAndValidationAlias(BaseModel):
+ p: list[str] = Field(alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-required-list-alias-and-validation-alias",
+ operation_id="model_required_list_alias_and_validation_alias",
+)
+def read_model_required_list_alias_and_validation_alias(
+ p: BodyModelRequiredListAliasAndValidationAlias,
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "items": {"type": "string"},
+ "title": "P Val Alias",
+ "type": "array",
+ },
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize("json", [None, {}])
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_missing(path: str, json):
+ client = TestClient(app)
+ response = client.post(path, json=json)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body"], ["body", "p_val_alias"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": ["hello", "world"]})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": ["hello", "world"]}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_alias": ["hello", "world"]})
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p_alias": ["hello", "world"]}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_val_alias": ["hello", "world"]})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
diff --git a/tests/test_request_params/test_body/test_optional_list.py b/tests/test_request_params/test_body/test_optional_list.py
new file mode 100644
index 0000000000..ba8ba9092e
--- /dev/null
+++ b/tests/test_request_params/test_body/test_optional_list.py
@@ -0,0 +1,456 @@
+from typing import Annotated, Optional
+
+import pytest
+from fastapi import Body, FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/optional-list-str", operation_id="optional_list_str")
+async def read_optional_list_str(
+ p: Annotated[Optional[list[str]], Body(embed=True)] = None,
+):
+ return {"p": p}
+
+
+class BodyModelOptionalListStr(BaseModel):
+ p: Optional[list[str]] = None
+
+
+@app.post("/model-optional-list-str", operation_id="model_optional_list_str")
+async def read_model_optional_list_str(p: BodyModelOptionalListStr):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+def test_optional_list_str_missing():
+ client = TestClient(app)
+ response = client.post("/optional-list-str")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+def test_model_optional_list_str_missing():
+ client = TestClient(app)
+ response = client.post("/model-optional-list-str")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str_missing_empty_dict(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/optional-list-alias", operation_id="optional_list_alias")
+async def read_optional_list_alias(
+ p: Annotated[Optional[list[str]], Body(embed=True, alias="p_alias")] = None,
+):
+ return {"p": p}
+
+
+class BodyModelOptionalListAlias(BaseModel):
+ p: Optional[list[str]] = Field(None, alias="p_alias")
+
+
+@app.post("/model-optional-list-alias", operation_id="model_optional_list_alias")
+async def read_model_optional_list_alias(p: BodyModelOptionalListAlias):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias",
+ "/model-optional-list-alias",
+ ],
+)
+def test_optional_list_str_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+def test_optional_list_alias_missing():
+ client = TestClient(app)
+ response = client.post("/optional-list-alias")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+def test_model_optional_list_alias_missing():
+ client = TestClient(app)
+ response = client.post("/model-optional-list-alias")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_alias_missing_empty_dict(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_alias": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post(
+ "/optional-list-validation-alias", operation_id="optional_list_validation_alias"
+)
+def read_optional_list_validation_alias(
+ p: Annotated[
+ Optional[list[str]], Body(embed=True, validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"p": p}
+
+
+class BodyModelOptionalListValidationAlias(BaseModel):
+ p: Optional[list[str]] = Field(None, validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-optional-list-validation-alias",
+ operation_id="model_optional_list_validation_alias",
+)
+def read_model_optional_list_validation_alias(
+ p: BodyModelOptionalListValidationAlias,
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
+)
+def test_optional_list_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+def test_optional_list_validation_alias_missing():
+ client = TestClient(app)
+ response = client.post("/optional-list-validation-alias")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+def test_model_optional_list_validation_alias_missing():
+ client = TestClient(app)
+ response = client.post("/model-optional-list-validation-alias")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
+)
+def test_optional_list_validation_alias_missing_empty_dict(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-validation-alias",
+ "/model-optional-list-validation-alias",
+ ],
+)
+def test_optional_list_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-validation-alias",
+ "/model-optional-list-validation-alias",
+ ],
+)
+def test_optional_list_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_val_alias": ["hello", "world"]})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post(
+ "/optional-list-alias-and-validation-alias",
+ operation_id="optional_list_alias_and_validation_alias",
+)
+def read_optional_list_alias_and_validation_alias(
+ p: Annotated[
+ Optional[list[str]],
+ Body(embed=True, alias="p_alias", validation_alias="p_val_alias"),
+ ] = None,
+):
+ return {"p": p}
+
+
+class BodyModelOptionalListAliasAndValidationAlias(BaseModel):
+ p: Optional[list[str]] = Field(
+ None, alias="p_alias", validation_alias="p_val_alias"
+ )
+
+
+@app.post(
+ "/model-optional-list-alias-and-validation-alias",
+ operation_id="model_optional_list_alias_and_validation_alias",
+)
+def read_model_optional_list_alias_and_validation_alias(
+ p: BodyModelOptionalListAliasAndValidationAlias,
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+def test_optional_list_alias_and_validation_alias_missing():
+ client = TestClient(app)
+ response = client.post("/optional-list-alias-and-validation-alias")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+def test_model_optional_list_alias_and_validation_alias_missing():
+ client = TestClient(app)
+ response = client.post("/model-optional-list-alias-and-validation-alias")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_missing_empty_dict(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_alias": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_val_alias": ["hello", "world"]})
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "p": [
+ "hello",
+ "world",
+ ]
+ }
diff --git a/tests/test_request_params/test_body/test_optional_str.py b/tests/test_request_params/test_body/test_optional_str.py
new file mode 100644
index 0000000000..b9c18034da
--- /dev/null
+++ b/tests/test_request_params/test_body/test_optional_str.py
@@ -0,0 +1,431 @@
+from typing import Annotated, Optional
+
+import pytest
+from fastapi import Body, FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/optional-str", operation_id="optional_str")
+async def read_optional_str(p: Annotated[Optional[str], Body(embed=True)] = None):
+ return {"p": p}
+
+
+class BodyModelOptionalStr(BaseModel):
+ p: Optional[str] = None
+
+
+@app.post("/model-optional-str", operation_id="model_optional_str")
+async def read_model_optional_str(p: BodyModelOptionalStr):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+def test_optional_str_missing():
+ client = TestClient(app)
+ response = client.post("/optional-str")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+def test_model_optional_str_missing():
+ client = TestClient(app)
+ response = client.post("/model-optional-str")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str_missing_empty_dict(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/optional-alias", operation_id="optional_alias")
+async def read_optional_alias(
+ p: Annotated[Optional[str], Body(embed=True, alias="p_alias")] = None,
+):
+ return {"p": p}
+
+
+class BodyModelOptionalAlias(BaseModel):
+ p: Optional[str] = Field(None, alias="p_alias")
+
+
+@app.post("/model-optional-alias", operation_id="model_optional_alias")
+async def read_model_optional_alias(p: BodyModelOptionalAlias):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias",
+ "/model-optional-alias",
+ ],
+)
+def test_optional_str_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+def test_optional_alias_missing():
+ client = TestClient(app)
+ response = client.post("/optional-alias")
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+def test_model_optional_alias_missing():
+ client = TestClient(app)
+ response = client.post("/model-optional-alias")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_model_optional_alias_missing_empty_dict(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post("/optional-validation-alias", operation_id="optional_validation_alias")
+def read_optional_validation_alias(
+ p: Annotated[
+ Optional[str], Body(embed=True, validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"p": p}
+
+
+class BodyModelOptionalValidationAlias(BaseModel):
+ p: Optional[str] = Field(None, validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-optional-validation-alias", operation_id="model_optional_validation_alias"
+)
+def read_model_optional_validation_alias(
+ p: BodyModelOptionalValidationAlias,
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-validation-alias", "/model-optional-validation-alias"],
+)
+def test_optional_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+def test_optional_validation_alias_missing():
+ client = TestClient(app)
+ response = client.post("/optional-validation-alias")
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+def test_model_optional_validation_alias_missing():
+ client = TestClient(app)
+ response = client.post("/model-optional-validation-alias")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-validation-alias", "/model-optional-validation-alias"],
+)
+def test_model_optional_validation_alias_missing_empty_dict(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-validation-alias",
+ "/model-optional-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-validation-alias",
+ "/model-optional-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_val_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post(
+ "/optional-alias-and-validation-alias",
+ operation_id="optional_alias_and_validation_alias",
+)
+def read_optional_alias_and_validation_alias(
+ p: Annotated[
+ Optional[str], Body(embed=True, alias="p_alias", validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"p": p}
+
+
+class BodyModelOptionalAliasAndValidationAlias(BaseModel):
+ p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-optional-alias-and-validation-alias",
+ operation_id="model_optional_alias_and_validation_alias",
+)
+def read_model_optional_alias_and_validation_alias(
+ p: BodyModelOptionalAliasAndValidationAlias,
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+def test_optional_alias_and_validation_alias_missing():
+ client = TestClient(app)
+ response = client.post("/optional-alias-and-validation-alias")
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+def test_model_optional_alias_and_validation_alias_missing():
+ client = TestClient(app)
+ response = client.post("/model-optional-alias-and-validation-alias")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": ["body"],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_model_optional_alias_and_validation_alias_missing_empty_dict(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_val_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
diff --git a/tests/test_request_params/test_body/test_required_str.py b/tests/test_request_params/test_body/test_required_str.py
new file mode 100644
index 0000000000..5b434fa1db
--- /dev/null
+++ b/tests/test_request_params/test_body/test_required_str.py
@@ -0,0 +1,421 @@
+from typing import Annotated, Any, Union
+
+import pytest
+from dirty_equals import IsOneOf
+from fastapi import Body, FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/required-str", operation_id="required_str")
+async def read_required_str(p: Annotated[str, Body(embed=True)]):
+ return {"p": p}
+
+
+class BodyModelRequiredStr(BaseModel):
+ p: str
+
+
+@app.post("/model-required-str", operation_id="model_required_str")
+async def read_model_required_str(p: BodyModelRequiredStr):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {"title": "P", "type": "string"},
+ },
+ "required": ["p"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize("json", [None, {}])
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str_missing(path: str, json: Union[dict[str, Any], None]):
+ client = TestClient(app)
+ response = client.post(path, json=json)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body"], ["body", "p"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/required-alias", operation_id="required_alias")
+async def read_required_alias(
+ p: Annotated[str, Body(embed=True, alias="p_alias")],
+):
+ return {"p": p}
+
+
+class BodyModelRequiredAlias(BaseModel):
+ p: str = Field(alias="p_alias")
+
+
+@app.post("/model-required-alias", operation_id="model_required_alias")
+async def read_model_required_alias(p: BodyModelRequiredAlias):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias",
+ "/model-required-alias",
+ ],
+)
+def test_required_str_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {"title": "P Alias", "type": "string"},
+ },
+ "required": ["p_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize("json", [None, {}])
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_alias_missing(path: str, json: Union[dict[str, Any], None]):
+ client = TestClient(app)
+ response = client.post(path, json=json)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body", "p_alias"], ["body"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": "hello"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": "hello"}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_alias": "hello"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post("/required-validation-alias", operation_id="required_validation_alias")
+def read_required_validation_alias(
+ p: Annotated[str, Body(embed=True, validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class BodyModelRequiredValidationAlias(BaseModel):
+ p: str = Field(validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-required-validation-alias", operation_id="model_required_validation_alias"
+)
+def read_model_required_validation_alias(
+ p: BodyModelRequiredValidationAlias,
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-validation-alias", "/model-required-validation-alias"],
+)
+def test_required_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {"title": "P Val Alias", "type": "string"},
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize("json", [None, {}])
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_missing(
+ path: str, json: Union[dict[str, Any], None]
+):
+ client = TestClient(app)
+ response = client.post(path, json=json)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body", "p_val_alias"], ["body"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": "hello"})
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": "hello"}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_val_alias": "hello"})
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post(
+ "/required-alias-and-validation-alias",
+ operation_id="required_alias_and_validation_alias",
+)
+def read_required_alias_and_validation_alias(
+ p: Annotated[
+ str, Body(embed=True, alias="p_alias", validation_alias="p_val_alias")
+ ],
+):
+ return {"p": p}
+
+
+class BodyModelRequiredAliasAndValidationAlias(BaseModel):
+ p: str = Field(alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-required-alias-and-validation-alias",
+ operation_id="model_required_alias_and_validation_alias",
+)
+def read_model_required_alias_and_validation_alias(
+ p: BodyModelRequiredAliasAndValidationAlias,
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {"title": "P Val Alias", "type": "string"},
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize("json", [None, {}])
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_missing(
+ path: str, json: Union[dict[str, Any], None]
+):
+ client = TestClient(app)
+ response = client.post(path, json=json)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": IsOneOf(["body"], ["body", "p_val_alias"]),
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p": "hello"})
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": "hello"}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_alias": "hello"})
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p_alias": "hello"}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, json={"p_val_alias": "hello"})
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": "hello"}
diff --git a/tests/test_request_params/test_body/utils.py b/tests/test_request_params/test_body/utils.py
new file mode 100644
index 0000000000..bf07394f90
--- /dev/null
+++ b/tests/test_request_params/test_body/utils.py
@@ -0,0 +1,7 @@
+from typing import Any
+
+
+def get_body_model_name(openapi: dict[str, Any], path: str) -> str:
+ body = openapi["paths"][path]["post"]["requestBody"]
+ body_schema = body["content"]["application/json"]["schema"]
+ return body_schema.get("$ref", "").split("/")[-1]
diff --git a/tests/test_request_params/test_cookie/__init__.py b/tests/test_request_params/test_cookie/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_request_params/test_cookie/test_list.py b/tests/test_request_params/test_cookie/test_list.py
new file mode 100644
index 0000000000..4ae80e0015
--- /dev/null
+++ b/tests/test_request_params/test_cookie/test_list.py
@@ -0,0 +1,3 @@
+# Currently, there is no way to pass multiple cookies with the same name.
+# The only way to pass multiple values for cookie params is to serialize them using
+# a comma as a delimiter, but this is not currently supported by Starlette.
diff --git a/tests/test_request_params/test_cookie/test_optional_list.py b/tests/test_request_params/test_cookie/test_optional_list.py
new file mode 100644
index 0000000000..4ae80e0015
--- /dev/null
+++ b/tests/test_request_params/test_cookie/test_optional_list.py
@@ -0,0 +1,3 @@
+# Currently, there is no way to pass multiple cookies with the same name.
+# The only way to pass multiple values for cookie params is to serialize them using
+# a comma as a delimiter, but this is not currently supported by Starlette.
diff --git a/tests/test_request_params/test_cookie/test_optional_str.py b/tests/test_request_params/test_cookie/test_optional_str.py
new file mode 100644
index 0000000000..6f381c8b86
--- /dev/null
+++ b/tests/test_request_params/test_cookie/test_optional_str.py
@@ -0,0 +1,327 @@
+from typing import Annotated, Optional
+
+import pytest
+from fastapi import Cookie, FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.get("/optional-str")
+async def read_optional_str(p: Annotated[Optional[str], Cookie()] = None):
+ return {"p": p}
+
+
+class CookieModelOptionalStr(BaseModel):
+ p: Optional[str] = None
+
+
+@app.get("/model-optional-str")
+async def read_model_optional_str(p: Annotated[CookieModelOptionalStr, Cookie()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P",
+ },
+ "name": "p",
+ "in": "cookie",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str(path: str):
+ client = TestClient(app)
+ client.cookies.set("p", "hello")
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.get("/optional-alias")
+async def read_optional_alias(
+ p: Annotated[Optional[str], Cookie(alias="p_alias")] = None,
+):
+ return {"p": p}
+
+
+class CookieModelOptionalAlias(BaseModel):
+ p: Optional[str] = Field(None, alias="p_alias")
+
+
+@app.get("/model-optional-alias")
+async def read_model_optional_alias(p: Annotated[CookieModelOptionalAlias, Cookie()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_str_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Alias",
+ },
+ "name": "p_alias",
+ "in": "cookie",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_alias_by_name(path: str):
+ client = TestClient(app)
+ client.cookies.set("p", "hello")
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias",
+ "/model-optional-alias",
+ ],
+)
+def test_optional_alias_by_alias(path: str):
+ client = TestClient(app)
+ client.cookies.set("p_alias", "hello")
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.get("/optional-validation-alias")
+def read_optional_validation_alias(
+ p: Annotated[Optional[str], Cookie(validation_alias="p_val_alias")] = None,
+):
+ return {"p": p}
+
+
+class CookieModelOptionalValidationAlias(BaseModel):
+ p: Optional[str] = Field(None, validation_alias="p_val_alias")
+
+
+@app.get("/model-optional-validation-alias")
+def read_model_optional_validation_alias(
+ p: Annotated[CookieModelOptionalValidationAlias, Cookie()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-validation-alias", "/model-optional-validation-alias"],
+)
+def test_optional_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
+ },
+ "name": "p_val_alias",
+ "in": "cookie",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-validation-alias", "/model-optional-validation-alias"],
+)
+def test_optional_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-validation-alias",
+ "/model-optional-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ client.cookies.set("p", "hello")
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-validation-alias",
+ "/model-optional-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ client.cookies.set("p_val_alias", "hello")
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.get("/optional-alias-and-validation-alias")
+def read_optional_alias_and_validation_alias(
+ p: Annotated[
+ Optional[str], Cookie(alias="p_alias", validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"p": p}
+
+
+class CookieModelOptionalAliasAndValidationAlias(BaseModel):
+ p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.get("/model-optional-alias-and-validation-alias")
+def read_model_optional_alias_and_validation_alias(
+ p: Annotated[CookieModelOptionalAliasAndValidationAlias, Cookie()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
+ },
+ "name": "p_val_alias",
+ "in": "cookie",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ client.cookies.set("p", "hello")
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ client.cookies.set("p_alias", "hello")
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ client.cookies.set("p_val_alias", "hello")
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
diff --git a/tests/test_request_params/test_cookie/test_required_str.py b/tests/test_request_params/test_cookie/test_required_str.py
new file mode 100644
index 0000000000..3e877b3e3d
--- /dev/null
+++ b/tests/test_request_params/test_cookie/test_required_str.py
@@ -0,0 +1,413 @@
+from typing import Annotated
+
+import pytest
+from dirty_equals import IsOneOf
+from fastapi import Cookie, FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.get("/required-str")
+async def read_required_str(p: Annotated[str, Cookie()]):
+ return {"p": p}
+
+
+class CookieModelRequiredStr(BaseModel):
+ p: str
+
+
+@app.get("/model-required-str")
+async def read_model_required_str(p: Annotated[CookieModelRequiredStr, Cookie()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P", "type": "string"},
+ "name": "p",
+ "in": "cookie",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["cookie", "p"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str(path: str):
+ client = TestClient(app)
+ client.cookies.set("p", "hello")
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.get("/required-alias")
+async def read_required_alias(p: Annotated[str, Cookie(alias="p_alias")]):
+ return {"p": p}
+
+
+class CookieModelRequiredAlias(BaseModel):
+ p: str = Field(alias="p_alias")
+
+
+@app.get("/model-required-alias")
+async def read_model_required_alias(p: Annotated[CookieModelRequiredAlias, Cookie()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_str_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P Alias", "type": "string"},
+ "name": "p_alias",
+ "in": "cookie",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["cookie", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias",
+ "/model-required-alias",
+ ],
+)
+def test_required_alias_by_name(path: str):
+ client = TestClient(app)
+ client.cookies.set("p", "hello")
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["cookie", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ {"p": "hello"},
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias",
+ "/model-required-alias",
+ ],
+)
+def test_required_alias_by_alias(path: str):
+ client = TestClient(app)
+ client.cookies.set("p_alias", "hello")
+ response = client.get(path)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.get("/required-validation-alias")
+def read_required_validation_alias(
+ p: Annotated[str, Cookie(validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class CookieModelRequiredValidationAlias(BaseModel):
+ p: str = Field(validation_alias="p_val_alias")
+
+
+@app.get("/model-required-validation-alias")
+def read_model_required_validation_alias(
+ p: Annotated[CookieModelRequiredValidationAlias, Cookie()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-validation-alias", "/model-required-validation-alias"],
+)
+def test_required_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P Val Alias", "type": "string"},
+ "name": "p_val_alias",
+ "in": "cookie",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "cookie",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ client.cookies.set("p", "hello")
+ response = client.get(path)
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["cookie", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": "hello"}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ client.cookies.set("p_val_alias", "hello")
+ response = client.get(path)
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.get("/required-alias-and-validation-alias")
+def read_required_alias_and_validation_alias(
+ p: Annotated[str, Cookie(alias="p_alias", validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class CookieModelRequiredAliasAndValidationAlias(BaseModel):
+ p: str = Field(alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.get("/model-required-alias-and-validation-alias")
+def read_model_required_alias_and_validation_alias(
+ p: Annotated[CookieModelRequiredAliasAndValidationAlias, Cookie()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P Val Alias", "type": "string"},
+ "name": "p_val_alias",
+ "in": "cookie",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "cookie",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ client.cookies.set("p", "hello")
+ response = client.get(path)
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "cookie",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ {"p": "hello"},
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ client.cookies.set("p_alias", "hello")
+ response = client.get(path)
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["cookie", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ {"p_alias": "hello"},
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ client.cookies.set("p_val_alias", "hello")
+ response = client.get(path)
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": "hello"}
diff --git a/tests/test_request_params/test_file/__init__.py b/tests/test_request_params/test_file/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_request_params/test_file/test_list.py b/tests/test_request_params/test_file/test_list.py
new file mode 100644
index 0000000000..68280fcf32
--- /dev/null
+++ b/tests/test_request_params/test_file/test_list.py
@@ -0,0 +1,441 @@
+from typing import Annotated
+
+import pytest
+from fastapi import FastAPI, File, UploadFile
+from fastapi.testclient import TestClient
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/list-bytes", operation_id="list_bytes")
+async def read_list_bytes(p: Annotated[list[bytes], File()]):
+ return {"file_size": [len(file) for file in p]}
+
+
+@app.post("/list-uploadfile", operation_id="list_uploadfile")
+async def read_list_uploadfile(p: Annotated[list[UploadFile], File()]):
+ return {"file_size": [file.size for file in p]}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes",
+ "/list-uploadfile",
+ ],
+)
+def test_list_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ "title": "P",
+ },
+ },
+ "required": ["p"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes",
+ "/list-uploadfile",
+ ],
+)
+def test_list_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes",
+ "/list-uploadfile",
+ ],
+)
+def test_list(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello"), ("p", b"world")])
+ assert response.status_code == 200
+ assert response.json() == {"file_size": [5, 5]}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/list-bytes-alias", operation_id="list_bytes_alias")
+async def read_list_bytes_alias(p: Annotated[list[bytes], File(alias="p_alias")]):
+ return {"file_size": [len(file) for file in p]}
+
+
+@app.post("/list-uploadfile-alias", operation_id="list_uploadfile_alias")
+async def read_list_uploadfile_alias(
+ p: Annotated[list[UploadFile], File(alias="p_alias")],
+):
+ return {"file_size": [file.size for file in p]}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-alias",
+ "/list-uploadfile-alias",
+ ],
+)
+def test_list_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ "title": "P Alias",
+ },
+ },
+ "required": ["p_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-alias",
+ "/list-uploadfile-alias",
+ ],
+)
+def test_list_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-alias",
+ "/list-uploadfile-alias",
+ ],
+)
+def test_list_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello"), ("p", b"world")])
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-alias",
+ "/list-uploadfile-alias",
+ ],
+)
+def test_list_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": [5, 5]}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post("/list-bytes-validation-alias", operation_id="list_bytes_validation_alias")
+def read_list_bytes_validation_alias(
+ p: Annotated[list[bytes], File(validation_alias="p_val_alias")],
+):
+ return {"file_size": [len(file) for file in p]}
+
+
+@app.post(
+ "/list-uploadfile-validation-alias",
+ operation_id="list_uploadfile_validation_alias",
+)
+def read_list_uploadfile_validation_alias(
+ p: Annotated[list[UploadFile], File(validation_alias="p_val_alias")],
+):
+ return {"file_size": [file.size for file in p]}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-validation-alias",
+ "/list-uploadfile-validation-alias",
+ ],
+)
+def test_list_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ "title": "P Val Alias",
+ },
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-validation-alias",
+ "/list-uploadfile-validation-alias",
+ ],
+)
+def test_list_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-validation-alias",
+ "/list-uploadfile-validation-alias",
+ ],
+)
+def test_list_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello"), ("p", b"world")])
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-validation-alias",
+ "/list-uploadfile-validation-alias",
+ ],
+)
+def test_list_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(
+ path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": [5, 5]}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post(
+ "/list-bytes-alias-and-validation-alias",
+ operation_id="list_bytes_alias_and_validation_alias",
+)
+def read_list_bytes_alias_and_validation_alias(
+ p: Annotated[list[bytes], File(alias="p_alias", validation_alias="p_val_alias")],
+):
+ return {"file_size": [len(file) for file in p]}
+
+
+@app.post(
+ "/list-uploadfile-alias-and-validation-alias",
+ operation_id="list_uploadfile_alias_and_validation_alias",
+)
+def read_list_uploadfile_alias_and_validation_alias(
+ p: Annotated[
+ list[UploadFile], File(alias="p_alias", validation_alias="p_val_alias")
+ ],
+):
+ return {"file_size": [file.size for file in p]}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-alias-and-validation-alias",
+ "/list-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_list_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ "title": "P Val Alias",
+ },
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-alias-and-validation-alias",
+ "/list-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_list_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-alias-and-validation-alias",
+ "/list-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_list_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", "hello"), ("p", "world")])
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-alias-and-validation-alias",
+ "/list-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_list_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")])
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/list-bytes-alias-and-validation-alias",
+ "/list-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_list_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(
+ path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": [5, 5]}
diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py
new file mode 100644
index 0000000000..45ef7bdec4
--- /dev/null
+++ b/tests/test_request_params/test_file/test_optional.py
@@ -0,0 +1,363 @@
+from typing import Annotated, Optional
+
+import pytest
+from fastapi import FastAPI, File, UploadFile
+from fastapi.testclient import TestClient
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/optional-bytes", operation_id="optional_bytes")
+async def read_optional_bytes(p: Annotated[Optional[bytes], File()] = None):
+ return {"file_size": len(p) if p else None}
+
+
+@app.post("/optional-uploadfile", operation_id="optional_uploadfile")
+async def read_optional_uploadfile(p: Annotated[Optional[UploadFile], File()] = None):
+ return {"file_size": p.size if p else None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes",
+ "/optional-uploadfile",
+ ],
+)
+def test_optional_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "anyOf": [
+ {"type": "string", "format": "binary"},
+ {"type": "null"},
+ ],
+ "title": "P",
+ }
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes",
+ "/optional-uploadfile",
+ ],
+)
+def test_optional_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes",
+ "/optional-uploadfile",
+ ],
+)
+def test_optional(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello")])
+ assert response.status_code == 200
+ assert response.json() == {"file_size": 5}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/optional-bytes-alias", operation_id="optional_bytes_alias")
+async def read_optional_bytes_alias(
+ p: Annotated[Optional[bytes], File(alias="p_alias")] = None,
+):
+ return {"file_size": len(p) if p else None}
+
+
+@app.post("/optional-uploadfile-alias", operation_id="optional_uploadfile_alias")
+async def read_optional_uploadfile_alias(
+ p: Annotated[Optional[UploadFile], File(alias="p_alias")] = None,
+):
+ return {"file_size": p.size if p else None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-alias",
+ "/optional-uploadfile-alias",
+ ],
+)
+def test_optional_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "anyOf": [
+ {"type": "string", "format": "binary"},
+ {"type": "null"},
+ ],
+ "title": "P Alias",
+ }
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-alias",
+ "/optional-uploadfile-alias",
+ ],
+)
+def test_optional_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-alias",
+ "/optional-uploadfile-alias",
+ ],
+)
+def test_optional_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello")])
+ assert response.status_code == 200
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-alias",
+ "/optional-uploadfile-alias",
+ ],
+)
+def test_optional_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_alias", b"hello")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": 5}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post(
+ "/optional-bytes-validation-alias", operation_id="optional_bytes_validation_alias"
+)
+def read_optional_bytes_validation_alias(
+ p: Annotated[Optional[bytes], File(validation_alias="p_val_alias")] = None,
+):
+ return {"file_size": len(p) if p else None}
+
+
+@app.post(
+ "/optional-uploadfile-validation-alias",
+ operation_id="optional_uploadfile_validation_alias",
+)
+def read_optional_uploadfile_validation_alias(
+ p: Annotated[Optional[UploadFile], File(validation_alias="p_val_alias")] = None,
+):
+ return {"file_size": p.size if p else None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-validation-alias",
+ "/optional-uploadfile-validation-alias",
+ ],
+)
+def test_optional_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {"type": "string", "format": "binary"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ }
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-validation-alias",
+ "/optional-uploadfile-validation-alias",
+ ],
+)
+def test_optional_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-validation-alias",
+ "/optional-uploadfile-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-validation-alias",
+ "/optional-uploadfile-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_val_alias", b"hello")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": 5}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post(
+ "/optional-bytes-alias-and-validation-alias",
+ operation_id="optional_bytes_alias_and_validation_alias",
+)
+def read_optional_bytes_alias_and_validation_alias(
+ p: Annotated[
+ Optional[bytes], File(alias="p_alias", validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"file_size": len(p) if p else None}
+
+
+@app.post(
+ "/optional-uploadfile-alias-and-validation-alias",
+ operation_id="optional_uploadfile_alias_and_validation_alias",
+)
+def read_optional_uploadfile_alias_and_validation_alias(
+ p: Annotated[
+ Optional[UploadFile], File(alias="p_alias", validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"file_size": p.size if p else None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-alias-and-validation-alias",
+ "/optional-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {"type": "string", "format": "binary"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ }
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-alias-and-validation-alias",
+ "/optional-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-alias-and-validation-alias",
+ "/optional-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-alias-and-validation-alias",
+ "/optional-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_alias", b"hello")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-bytes-alias-and-validation-alias",
+ "/optional-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_val_alias", b"hello")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": 5}
diff --git a/tests/test_request_params/test_file/test_optional_list.py b/tests/test_request_params/test_file/test_optional_list.py
new file mode 100644
index 0000000000..162fbe08ae
--- /dev/null
+++ b/tests/test_request_params/test_file/test_optional_list.py
@@ -0,0 +1,373 @@
+from typing import Annotated, Optional
+
+import pytest
+from fastapi import FastAPI, File, UploadFile
+from fastapi.testclient import TestClient
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/optional-list-bytes")
+async def read_optional_list_bytes(p: Annotated[Optional[list[bytes]], File()] = None):
+ return {"file_size": [len(file) for file in p] if p else None}
+
+
+@app.post("/optional-list-uploadfile")
+async def read_optional_list_uploadfile(
+ p: Annotated[Optional[list[UploadFile]], File()] = None,
+):
+ return {"file_size": [file.size for file in p] if p else None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes",
+ "/optional-list-uploadfile",
+ ],
+)
+def test_optional_list_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "anyOf": [
+ {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ },
+ {"type": "null"},
+ ],
+ "title": "P",
+ }
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes",
+ "/optional-list-uploadfile",
+ ],
+)
+def test_optional_list_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes",
+ "/optional-list-uploadfile",
+ ],
+)
+def test_optional_list(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello"), ("p", b"world")])
+ assert response.status_code == 200
+ assert response.json() == {"file_size": [5, 5]}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/optional-list-bytes-alias")
+async def read_optional_list_bytes_alias(
+ p: Annotated[Optional[list[bytes]], File(alias="p_alias")] = None,
+):
+ return {"file_size": [len(file) for file in p] if p else None}
+
+
+@app.post("/optional-list-uploadfile-alias")
+async def read_optional_list_uploadfile_alias(
+ p: Annotated[Optional[list[UploadFile]], File(alias="p_alias")] = None,
+):
+ return {"file_size": [file.size for file in p] if p else None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-alias",
+ "/optional-list-uploadfile-alias",
+ ],
+)
+def test_optional_list_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "anyOf": [
+ {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ },
+ {"type": "null"},
+ ],
+ "title": "P Alias",
+ }
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-alias",
+ "/optional-list-uploadfile-alias",
+ ],
+)
+def test_optional_list_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-alias",
+ "/optional-list-uploadfile-alias",
+ ],
+)
+def test_optional_list_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello"), ("p", b"world")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-alias",
+ "/optional-list-uploadfile-alias",
+ ],
+)
+def test_optional_list_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": [5, 5]}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post("/optional-list-bytes-validation-alias")
+def read_optional_list_bytes_validation_alias(
+ p: Annotated[Optional[list[bytes]], File(validation_alias="p_val_alias")] = None,
+):
+ return {"file_size": [len(file) for file in p] if p else None}
+
+
+@app.post("/optional-list-uploadfile-validation-alias")
+def read_optional_list_uploadfile_validation_alias(
+ p: Annotated[
+ Optional[list[UploadFile]], File(validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"file_size": [file.size for file in p] if p else None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-validation-alias",
+ "/optional-list-uploadfile-validation-alias",
+ ],
+)
+def test_optional_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ },
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ }
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-validation-alias",
+ "/optional-list-uploadfile-validation-alias",
+ ],
+)
+def test_optional_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-validation-alias",
+ "/optional-list-uploadfile-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello"), ("p", b"world")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-validation-alias",
+ "/optional-list-uploadfile-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(
+ path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": [5, 5]}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post("/optional-list-bytes-alias-and-validation-alias")
+def read_optional_list_bytes_alias_and_validation_alias(
+ p: Annotated[
+ Optional[list[bytes]], File(alias="p_alias", validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"file_size": [len(file) for file in p] if p else None}
+
+
+@app.post("/optional-list-uploadfile-alias-and-validation-alias")
+def read_optional_list_uploadfile_alias_and_validation_alias(
+ p: Annotated[
+ Optional[list[UploadFile]],
+ File(alias="p_alias", validation_alias="p_val_alias"),
+ ] = None,
+):
+ return {"file_size": [file.size for file in p] if p else None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-alias-and-validation-alias",
+ "/optional-list-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {
+ "type": "array",
+ "items": {"type": "string", "format": "binary"},
+ },
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ }
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-alias-and-validation-alias",
+ "/optional-list-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-alias-and-validation-alias",
+ "/optional-list-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-alias-and-validation-alias",
+ "/optional-list-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-bytes-alias-and-validation-alias",
+ "/optional-list-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(
+ path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")]
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": [5, 5]}
diff --git a/tests/test_request_params/test_file/test_required.py b/tests/test_request_params/test_file/test_required.py
new file mode 100644
index 0000000000..a0f9d23a6b
--- /dev/null
+++ b/tests/test_request_params/test_file/test_required.py
@@ -0,0 +1,429 @@
+from typing import Annotated
+
+import pytest
+from fastapi import FastAPI, File, UploadFile
+from fastapi.testclient import TestClient
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/required-bytes", operation_id="required_bytes")
+async def read_required_bytes(p: Annotated[bytes, File()]):
+ return {"file_size": len(p)}
+
+
+@app.post("/required-uploadfile", operation_id="required_uploadfile")
+async def read_required_uploadfile(p: Annotated[UploadFile, File()]):
+ return {"file_size": p.size}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes",
+ "/required-uploadfile",
+ ],
+)
+def test_required_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {"title": "P", "type": "string", "format": "binary"},
+ },
+ "required": ["p"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes",
+ "/required-uploadfile",
+ ],
+)
+def test_required_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes",
+ "/required-uploadfile",
+ ],
+)
+def test_required(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello")])
+ assert response.status_code == 200
+ assert response.json() == {"file_size": 5}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/required-bytes-alias", operation_id="required_bytes_alias")
+async def read_required_bytes_alias(p: Annotated[bytes, File(alias="p_alias")]):
+ return {"file_size": len(p)}
+
+
+@app.post("/required-uploadfile-alias", operation_id="required_uploadfile_alias")
+async def read_required_uploadfile_alias(
+ p: Annotated[UploadFile, File(alias="p_alias")],
+):
+ return {"file_size": p.size}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-alias",
+ "/required-uploadfile-alias",
+ ],
+)
+def test_required_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {"title": "P Alias", "type": "string", "format": "binary"},
+ },
+ "required": ["p_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-alias",
+ "/required-uploadfile-alias",
+ ],
+)
+def test_required_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-alias",
+ "/required-uploadfile-alias",
+ ],
+)
+def test_required_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello")])
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-alias",
+ "/required-uploadfile-alias",
+ ],
+)
+def test_required_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_alias", b"hello")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": 5}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post(
+ "/required-bytes-validation-alias", operation_id="required_bytes_validation_alias"
+)
+def read_required_bytes_validation_alias(
+ p: Annotated[bytes, File(validation_alias="p_val_alias")],
+):
+ return {"file_size": len(p)}
+
+
+@app.post(
+ "/required-uploadfile-validation-alias",
+ operation_id="required_uploadfile_validation_alias",
+)
+def read_required_uploadfile_validation_alias(
+ p: Annotated[UploadFile, File(validation_alias="p_val_alias")],
+):
+ return {"file_size": p.size}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-validation-alias",
+ "/required-uploadfile-validation-alias",
+ ],
+)
+def test_required_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "title": "P Val Alias",
+ "type": "string",
+ "format": "binary",
+ },
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-validation-alias",
+ "/required-uploadfile-validation-alias",
+ ],
+)
+def test_required_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-validation-alias",
+ "/required-uploadfile-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p", b"hello")])
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-validation-alias",
+ "/required-uploadfile-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_val_alias", b"hello")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": 5}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post(
+ "/required-bytes-alias-and-validation-alias",
+ operation_id="required_bytes_alias_and_validation_alias",
+)
+def read_required_bytes_alias_and_validation_alias(
+ p: Annotated[bytes, File(alias="p_alias", validation_alias="p_val_alias")],
+):
+ return {"file_size": len(p)}
+
+
+@app.post(
+ "/required-uploadfile-alias-and-validation-alias",
+ operation_id="required_uploadfile_alias_and_validation_alias",
+)
+def read_required_uploadfile_alias_and_validation_alias(
+ p: Annotated[UploadFile, File(alias="p_alias", validation_alias="p_val_alias")],
+):
+ return {"file_size": p.size}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-alias-and-validation-alias",
+ "/required-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "title": "P Val Alias",
+ "type": "string",
+ "format": "binary",
+ },
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-alias-and-validation-alias",
+ "/required-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-alias-and-validation-alias",
+ "/required-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, files={"p": "hello"})
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-alias-and-validation-alias",
+ "/required-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_alias", b"hello")])
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-bytes-alias-and-validation-alias",
+ "/required-uploadfile-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, files=[("p_val_alias", b"hello")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"file_size": 5}
diff --git a/tests/test_request_params/test_file/utils.py b/tests/test_request_params/test_file/utils.py
new file mode 100644
index 0000000000..e2a97ccd9b
--- /dev/null
+++ b/tests/test_request_params/test_file/utils.py
@@ -0,0 +1,7 @@
+from typing import Any
+
+
+def get_body_model_name(openapi: dict[str, Any], path: str) -> str:
+ body = openapi["paths"][path]["post"]["requestBody"]
+ body_schema = body["content"]["multipart/form-data"]["schema"]
+ return body_schema.get("$ref", "").split("/")[-1]
diff --git a/tests/test_request_params/test_form/__init__.py b/tests/test_request_params/test_form/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_request_params/test_form/test_list.py b/tests/test_request_params/test_form/test_list.py
new file mode 100644
index 0000000000..abe781c945
--- /dev/null
+++ b/tests/test_request_params/test_form/test_list.py
@@ -0,0 +1,436 @@
+from typing import Annotated
+
+import pytest
+from dirty_equals import IsOneOf, IsPartialDict
+from fastapi import FastAPI, Form
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/required-list-str", operation_id="required_list_str")
+async def read_required_list_str(p: Annotated[list[str], Form()]):
+ return {"p": p}
+
+
+class FormModelRequiredListStr(BaseModel):
+ p: list[str]
+
+
+@app.post("/model-required-list-str", operation_id="model_required_list_str")
+def read_model_required_list_str(p: Annotated[FormModelRequiredListStr, Form()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "items": {"type": "string"},
+ "title": "P",
+ "type": "array",
+ },
+ },
+ "required": ["p"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/required-list-alias", operation_id="required_list_alias")
+async def read_required_list_alias(p: Annotated[list[str], Form(alias="p_alias")]):
+ return {"p": p}
+
+
+class FormModelRequiredListAlias(BaseModel):
+ p: list[str] = Field(alias="p_alias")
+
+
+@app.post("/model-required-list-alias", operation_id="model_required_list_alias")
+async def read_model_required_list_alias(
+ p: Annotated[FormModelRequiredListAlias, Form()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias",
+ "/model-required-list-alias",
+ ],
+)
+def test_required_list_str_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "items": {"type": "string"},
+ "title": "P Alias",
+ "type": "array",
+ },
+ },
+ "required": ["p_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-alias", "/model-required-list-alias"],
+)
+def test_required_list_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias",
+ "/model-required-list-alias",
+ ],
+)
+def test_required_list_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": ["hello", "world"]})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": ["hello", "world"]}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-alias", "/model-required-list-alias"],
+)
+def test_required_list_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_alias": ["hello", "world"]})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post(
+ "/required-list-validation-alias", operation_id="required_list_validation_alias"
+)
+def read_required_list_validation_alias(
+ p: Annotated[list[str], Form(validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class FormModelRequiredListValidationAlias(BaseModel):
+ p: list[str] = Field(validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-required-list-validation-alias",
+ operation_id="model_required_list_validation_alias",
+)
+async def read_model_required_list_validation_alias(
+ p: Annotated[FormModelRequiredListValidationAlias, Form()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-validation-alias", "/model-required-list-validation-alias"],
+)
+def test_required_list_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "items": {"type": "string"},
+ "title": "P Val Alias",
+ "type": "array",
+ },
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-validation-alias",
+ "/model-required-list-validation-alias",
+ ],
+)
+def test_required_list_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-validation-alias",
+ "/model-required-list-validation-alias",
+ ],
+)
+def test_required_list_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": ["hello", "world"]})
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-validation-alias", "/model-required-list-validation-alias"],
+)
+def test_required_list_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_val_alias": ["hello", "world"]})
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post(
+ "/required-list-alias-and-validation-alias",
+ operation_id="required_list_alias_and_validation_alias",
+)
+def read_required_list_alias_and_validation_alias(
+ p: Annotated[list[str], Form(alias="p_alias", validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class FormModelRequiredListAliasAndValidationAlias(BaseModel):
+ p: list[str] = Field(alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-required-list-alias-and-validation-alias",
+ operation_id="model_required_list_alias_and_validation_alias",
+)
+def read_model_required_list_alias_and_validation_alias(
+ p: Annotated[FormModelRequiredListAliasAndValidationAlias, Form()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "items": {"type": "string"},
+ "title": "P Val Alias",
+ "type": "array",
+ },
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": ["hello", "world"]})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ {"p": ["hello", "world"]},
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_alias": ["hello", "world"]})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p_alias": ["hello", "world"]}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_val_alias": ["hello", "world"]})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
diff --git a/tests/test_request_params/test_form/test_optional_list.py b/tests/test_request_params/test_form/test_optional_list.py
new file mode 100644
index 0000000000..6d1957a18c
--- /dev/null
+++ b/tests/test_request_params/test_form/test_optional_list.py
@@ -0,0 +1,360 @@
+from typing import Annotated, Optional
+
+import pytest
+from fastapi import FastAPI, Form
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/optional-list-str", operation_id="optional_list_str")
+async def read_optional_list_str(
+ p: Annotated[Optional[list[str]], Form()] = None,
+):
+ return {"p": p}
+
+
+class FormModelOptionalListStr(BaseModel):
+ p: Optional[list[str]] = None
+
+
+@app.post("/model-optional-list-str", operation_id="model_optional_list_str")
+async def read_model_optional_list_str(p: Annotated[FormModelOptionalListStr, Form()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/optional-list-alias", operation_id="optional_list_alias")
+async def read_optional_list_alias(
+ p: Annotated[Optional[list[str]], Form(alias="p_alias")] = None,
+):
+ return {"p": p}
+
+
+class FormModelOptionalListAlias(BaseModel):
+ p: Optional[list[str]] = Field(None, alias="p_alias")
+
+
+@app.post("/model-optional-list-alias", operation_id="model_optional_list_alias")
+async def read_model_optional_list_alias(
+ p: Annotated[FormModelOptionalListAlias, Form()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias",
+ "/model-optional-list-alias",
+ ],
+)
+def test_optional_list_str_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_alias": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post(
+ "/optional-list-validation-alias", operation_id="optional_list_validation_alias"
+)
+def read_optional_list_validation_alias(
+ p: Annotated[Optional[list[str]], Form(validation_alias="p_val_alias")] = None,
+):
+ return {"p": p}
+
+
+class FormModelOptionalListValidationAlias(BaseModel):
+ p: Optional[list[str]] = Field(None, validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-optional-list-validation-alias",
+ operation_id="model_optional_list_validation_alias",
+)
+def read_model_optional_list_validation_alias(
+ p: Annotated[FormModelOptionalListValidationAlias, Form()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
+)
+def test_optional_list_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
+)
+def test_optional_list_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-validation-alias",
+ "/model-optional-list-validation-alias",
+ ],
+)
+def test_optional_list_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
+)
+def test_optional_list_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_val_alias": ["hello", "world"]})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post(
+ "/optional-list-alias-and-validation-alias",
+ operation_id="optional_list_alias_and_validation_alias",
+)
+def read_optional_list_alias_and_validation_alias(
+ p: Annotated[
+ Optional[list[str]], Form(alias="p_alias", validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"p": p}
+
+
+class FormModelOptionalListAliasAndValidationAlias(BaseModel):
+ p: Optional[list[str]] = Field(
+ None, alias="p_alias", validation_alias="p_val_alias"
+ )
+
+
+@app.post(
+ "/model-optional-list-alias-and-validation-alias",
+ operation_id="model_optional_list_alias_and_validation_alias",
+)
+def read_model_optional_list_alias_and_validation_alias(
+ p: Annotated[FormModelOptionalListAliasAndValidationAlias, Form()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_alias": ["hello", "world"]})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_val_alias": ["hello", "world"]})
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "p": [
+ "hello",
+ "world",
+ ]
+ }
diff --git a/tests/test_request_params/test_form/test_optional_str.py b/tests/test_request_params/test_form/test_optional_str.py
new file mode 100644
index 0000000000..810e83caa3
--- /dev/null
+++ b/tests/test_request_params/test_form/test_optional_str.py
@@ -0,0 +1,337 @@
+from typing import Annotated, Optional
+
+import pytest
+from fastapi import FastAPI, Form
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/optional-str", operation_id="optional_str")
+async def read_optional_str(p: Annotated[Optional[str], Form()] = None):
+ return {"p": p}
+
+
+class FormModelOptionalStr(BaseModel):
+ p: Optional[str] = None
+
+
+@app.post("/model-optional-str", operation_id="model_optional_str")
+async def read_model_optional_str(p: Annotated[FormModelOptionalStr, Form()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/optional-alias", operation_id="optional_alias")
+async def read_optional_alias(
+ p: Annotated[Optional[str], Form(alias="p_alias")] = None,
+):
+ return {"p": p}
+
+
+class FormModelOptionalAlias(BaseModel):
+ p: Optional[str] = Field(None, alias="p_alias")
+
+
+@app.post("/model-optional-alias", operation_id="model_optional_alias")
+async def read_model_optional_alias(p: Annotated[FormModelOptionalAlias, Form()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias",
+ "/model-optional-alias",
+ ],
+)
+def test_optional_str_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post("/optional-validation-alias", operation_id="optional_validation_alias")
+def read_optional_validation_alias(
+ p: Annotated[Optional[str], Form(validation_alias="p_val_alias")] = None,
+):
+ return {"p": p}
+
+
+class FormModelOptionalValidationAlias(BaseModel):
+ p: Optional[str] = Field(None, validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-optional-validation-alias", operation_id="model_optional_validation_alias"
+)
+def read_model_optional_validation_alias(
+ p: Annotated[FormModelOptionalValidationAlias, Form()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-validation-alias", "/model-optional-validation-alias"],
+)
+def test_optional_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-validation-alias", "/model-optional-validation-alias"],
+)
+def test_optional_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-validation-alias",
+ "/model-optional-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-validation-alias",
+ "/model-optional-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_val_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post(
+ "/optional-alias-and-validation-alias",
+ operation_id="optional_alias_and_validation_alias",
+)
+def read_optional_alias_and_validation_alias(
+ p: Annotated[
+ Optional[str], Form(alias="p_alias", validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"p": p}
+
+
+class FormModelOptionalAliasAndValidationAlias(BaseModel):
+ p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-optional-alias-and-validation-alias",
+ operation_id="model_optional_alias_and_validation_alias",
+)
+def read_model_optional_alias_and_validation_alias(
+ p: Annotated[FormModelOptionalAliasAndValidationAlias, Form()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
+ },
+ },
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_val_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
diff --git a/tests/test_request_params/test_form/test_required_str.py b/tests/test_request_params/test_form/test_required_str.py
new file mode 100644
index 0000000000..7c9523b308
--- /dev/null
+++ b/tests/test_request_params/test_form/test_required_str.py
@@ -0,0 +1,415 @@
+from typing import Annotated
+
+import pytest
+from dirty_equals import IsOneOf
+from fastapi import FastAPI, Form
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+from .utils import get_body_model_name
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.post("/required-str", operation_id="required_str")
+async def read_required_str(p: Annotated[str, Form()]):
+ return {"p": p}
+
+
+class FormModelRequiredStr(BaseModel):
+ p: str
+
+
+@app.post("/model-required-str", operation_id="model_required_str")
+async def read_model_required_str(p: Annotated[FormModelRequiredStr, Form()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p": {"title": "P", "type": "string"},
+ },
+ "required": ["p"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.post("/required-alias", operation_id="required_alias")
+async def read_required_alias(p: Annotated[str, Form(alias="p_alias")]):
+ return {"p": p}
+
+
+class FormModelRequiredAlias(BaseModel):
+ p: str = Field(alias="p_alias")
+
+
+@app.post("/model-required-alias", operation_id="model_required_alias")
+async def read_model_required_alias(p: Annotated[FormModelRequiredAlias, Form()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias",
+ "/model-required-alias",
+ ],
+)
+def test_required_str_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_alias": {"title": "P Alias", "type": "string"},
+ },
+ "required": ["p_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": "hello"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": "hello"}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_alias": "hello"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.post("/required-validation-alias", operation_id="required_validation_alias")
+def read_required_validation_alias(
+ p: Annotated[str, Form(validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class FormModelRequiredValidationAlias(BaseModel):
+ p: str = Field(validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-required-validation-alias", operation_id="model_required_validation_alias"
+)
+def read_model_required_validation_alias(
+ p: Annotated[FormModelRequiredValidationAlias, Form()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-validation-alias", "/model-required-validation-alias"],
+)
+def test_required_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {"title": "P Val Alias", "type": "string"},
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": "hello"})
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": "hello"}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_val_alias": "hello"})
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.post(
+ "/required-alias-and-validation-alias",
+ operation_id="required_alias_and_validation_alias",
+)
+def read_required_alias_and_validation_alias(
+ p: Annotated[str, Form(alias="p_alias", validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class FormModelRequiredAliasAndValidationAlias(BaseModel):
+ p: str = Field(alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.post(
+ "/model-required-alias-and-validation-alias",
+ operation_id="model_required_alias_and_validation_alias",
+)
+def read_model_required_alias_and_validation_alias(
+ p: Annotated[FormModelRequiredAliasAndValidationAlias, Form()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_schema(path: str):
+ openapi = app.openapi()
+ body_model_name = get_body_model_name(openapi, path)
+
+ assert app.openapi()["components"]["schemas"][body_model_name] == {
+ "properties": {
+ "p_val_alias": {"title": "P Val Alias", "type": "string"},
+ },
+ "required": ["p_val_alias"],
+ "title": body_model_name,
+ "type": "object",
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.post(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p": "hello"})
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "body",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": "hello"}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_alias": "hello"})
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p_alias": "hello"}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.post(path, data={"p_val_alias": "hello"})
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": "hello"}
diff --git a/tests/test_request_params/test_form/utils.py b/tests/test_request_params/test_form/utils.py
new file mode 100644
index 0000000000..9132173111
--- /dev/null
+++ b/tests/test_request_params/test_form/utils.py
@@ -0,0 +1,7 @@
+from typing import Any
+
+
+def get_body_model_name(openapi: dict[str, Any], path: str) -> str:
+ body = openapi["paths"][path]["post"]["requestBody"]
+ body_schema = body["content"]["application/x-www-form-urlencoded"]["schema"]
+ return body_schema.get("$ref", "").split("/")[-1]
diff --git a/tests/test_request_params/test_header/__init__.py b/tests/test_request_params/test_header/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_request_params/test_header/test_list.py b/tests/test_request_params/test_header/test_list.py
new file mode 100644
index 0000000000..489a6b3e7d
--- /dev/null
+++ b/tests/test_request_params/test_header/test_list.py
@@ -0,0 +1,418 @@
+from typing import Annotated
+
+import pytest
+from dirty_equals import AnyThing, IsOneOf, IsPartialDict
+from fastapi import FastAPI, Header
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.get("/required-list-str")
+async def read_required_list_str(p: Annotated[list[str], Header()]):
+ return {"p": p}
+
+
+class HeaderModelRequiredListStr(BaseModel):
+ p: list[str]
+
+
+@app.get("/model-required-list-str")
+def read_model_required_list_str(p: Annotated[HeaderModelRequiredListStr, Header()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {
+ "title": "P",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "p",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p"],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p", "hello"), ("p", "world")])
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.get("/required-list-alias")
+async def read_required_list_alias(p: Annotated[list[str], Header(alias="p_alias")]):
+ return {"p": p}
+
+
+class HeaderModelRequiredListAlias(BaseModel):
+ p: list[str] = Field(alias="p_alias")
+
+
+@app.get("/model-required-list-alias")
+async def read_model_required_list_alias(
+ p: Annotated[HeaderModelRequiredListAlias, Header()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-alias", "/model-required-list-alias"],
+)
+def test_required_list_str_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {
+ "title": "P Alias",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "p_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-alias", "/model-required-list-alias"],
+)
+def test_required_list_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_alias"],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias",
+ "/model-required-list-alias",
+ ],
+)
+def test_required_list_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p", "hello"), ("p", "world")])
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias",
+ "/model-required-list-alias",
+ ],
+)
+def test_required_list_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")])
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.get("/required-list-validation-alias")
+def read_required_list_validation_alias(
+ p: Annotated[list[str], Header(validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class HeaderModelRequiredListValidationAlias(BaseModel):
+ p: list[str] = Field(validation_alias="p_val_alias")
+
+
+@app.get("/model-required-list-validation-alias")
+async def read_model_required_list_validation_alias(
+ p: Annotated[HeaderModelRequiredListValidationAlias, Header()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-validation-alias", "/model-required-list-validation-alias"],
+)
+def test_required_list_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {
+ "title": "P Val Alias",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "p_val_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-validation-alias",
+ "/model-required-list-validation-alias",
+ ],
+)
+def test_required_list_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "header",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-validation-alias",
+ "/model-required-list-validation-alias",
+ ],
+)
+def test_required_list_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p", "hello"), ("p", "world")])
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-validation-alias", "/model-required-list-validation-alias"],
+)
+def test_required_list_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(
+ path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")]
+ )
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.get("/required-list-alias-and-validation-alias")
+def read_required_list_alias_and_validation_alias(
+ p: Annotated[list[str], Header(alias="p_alias", validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class HeaderModelRequiredListAliasAndValidationAlias(BaseModel):
+ p: list[str] = Field(alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.get("/model-required-list-alias-and-validation-alias")
+def read_model_required_list_alias_and_validation_alias(
+ p: Annotated[HeaderModelRequiredListAliasAndValidationAlias, Header()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {
+ "title": "P Val Alias",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "p_val_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "header",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p", "hello"), ("p", "world")])
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "header",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ IsPartialDict({"p": ["hello", "world"]}),
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")])
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ IsPartialDict({"p_alias": ["hello", "world"]}),
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(
+ path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")]
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
diff --git a/tests/test_request_params/test_header/test_optional_list.py b/tests/test_request_params/test_header/test_optional_list.py
new file mode 100644
index 0000000000..5dd4ea9ade
--- /dev/null
+++ b/tests/test_request_params/test_header/test_optional_list.py
@@ -0,0 +1,345 @@
+from typing import Annotated, Optional
+
+import pytest
+from fastapi import FastAPI, Header
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.get("/optional-list-str")
+async def read_optional_list_str(
+ p: Annotated[Optional[list[str]], Header()] = None,
+):
+ return {"p": p}
+
+
+class HeaderModelOptionalListStr(BaseModel):
+ p: Optional[list[str]] = None
+
+
+@app.get("/model-optional-list-str")
+async def read_model_optional_list_str(
+ p: Annotated[HeaderModelOptionalListStr, Header()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P",
+ },
+ "name": "p",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p", "hello"), ("p", "world")])
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.get("/optional-list-alias")
+async def read_optional_list_alias(
+ p: Annotated[Optional[list[str]], Header(alias="p_alias")] = None,
+):
+ return {"p": p}
+
+
+class HeaderModelOptionalListAlias(BaseModel):
+ p: Optional[list[str]] = Field(None, alias="p_alias")
+
+
+@app.get("/model-optional-list-alias")
+async def read_model_optional_list_alias(
+ p: Annotated[HeaderModelOptionalListAlias, Header()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_str_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Alias",
+ },
+ "name": "p_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p", "hello"), ("p", "world")])
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias",
+ "/model-optional-list-alias",
+ ],
+)
+def test_optional_list_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")])
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.get("/optional-list-validation-alias")
+def read_optional_list_validation_alias(
+ p: Annotated[Optional[list[str]], Header(validation_alias="p_val_alias")] = None,
+):
+ return {"p": p}
+
+
+class HeaderModelOptionalListValidationAlias(BaseModel):
+ p: Optional[list[str]] = Field(None, validation_alias="p_val_alias")
+
+
+@app.get("/model-optional-list-validation-alias")
+def read_model_optional_list_validation_alias(
+ p: Annotated[HeaderModelOptionalListValidationAlias, Header()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
+)
+def test_optional_list_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ },
+ "name": "p_val_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
+)
+def test_optional_list_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-validation-alias",
+ "/model-optional-list-validation-alias",
+ ],
+)
+def test_optional_list_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p", "hello"), ("p", "world")])
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
+)
+def test_optional_list_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(
+ path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")]
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.get("/optional-list-alias-and-validation-alias")
+def read_optional_list_alias_and_validation_alias(
+ p: Annotated[
+ Optional[list[str]], Header(alias="p_alias", validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"p": p}
+
+
+class HeaderModelOptionalListAliasAndValidationAlias(BaseModel):
+ p: Optional[list[str]] = Field(
+ None, alias="p_alias", validation_alias="p_val_alias"
+ )
+
+
+@app.get("/model-optional-list-alias-and-validation-alias")
+def read_model_optional_list_alias_and_validation_alias(
+ p: Annotated[HeaderModelOptionalListAliasAndValidationAlias, Header()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ },
+ "name": "p_val_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p", "hello"), ("p", "world")])
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")])
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(
+ path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")]
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "p": [
+ "hello",
+ "world",
+ ]
+ }
diff --git a/tests/test_request_params/test_header/test_optional_str.py b/tests/test_request_params/test_header/test_optional_str.py
new file mode 100644
index 0000000000..0bd0eddc1b
--- /dev/null
+++ b/tests/test_request_params/test_header/test_optional_str.py
@@ -0,0 +1,319 @@
+from typing import Annotated, Optional
+
+import pytest
+from fastapi import FastAPI, Header
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.get("/optional-str")
+async def read_optional_str(p: Annotated[Optional[str], Header()] = None):
+ return {"p": p}
+
+
+class HeaderModelOptionalStr(BaseModel):
+ p: Optional[str] = None
+
+
+@app.get("/model-optional-str")
+async def read_model_optional_str(p: Annotated[HeaderModelOptionalStr, Header()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P",
+ },
+ "name": "p",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.get("/optional-alias")
+async def read_optional_alias(
+ p: Annotated[Optional[str], Header(alias="p_alias")] = None,
+):
+ return {"p": p}
+
+
+class HeaderModelOptionalAlias(BaseModel):
+ p: Optional[str] = Field(None, alias="p_alias")
+
+
+@app.get("/model-optional-alias")
+async def read_model_optional_alias(p: Annotated[HeaderModelOptionalAlias, Header()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_str_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Alias",
+ },
+ "name": "p_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias",
+ "/model-optional-alias",
+ ],
+)
+def test_optional_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.get("/optional-validation-alias")
+def read_optional_validation_alias(
+ p: Annotated[Optional[str], Header(validation_alias="p_val_alias")] = None,
+):
+ return {"p": p}
+
+
+class HeaderModelOptionalValidationAlias(BaseModel):
+ p: Optional[str] = Field(None, validation_alias="p_val_alias")
+
+
+@app.get("/model-optional-validation-alias")
+def read_model_optional_validation_alias(
+ p: Annotated[HeaderModelOptionalValidationAlias, Header()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-validation-alias", "/model-optional-validation-alias"],
+)
+def test_optional_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
+ },
+ "name": "p_val_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-validation-alias", "/model-optional-validation-alias"],
+)
+def test_optional_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-validation-alias",
+ "/model-optional-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-validation-alias",
+ "/model-optional-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p_val_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.get("/optional-alias-and-validation-alias")
+def read_optional_alias_and_validation_alias(
+ p: Annotated[
+ Optional[str], Header(alias="p_alias", validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"p": p}
+
+
+class HeaderModelOptionalAliasAndValidationAlias(BaseModel):
+ p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.get("/model-optional-alias-and-validation-alias")
+def read_model_optional_alias_and_validation_alias(
+ p: Annotated[HeaderModelOptionalAliasAndValidationAlias, Header()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
+ },
+ "name": "p_val_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p_val_alias": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
diff --git a/tests/test_request_params/test_header/test_required_str.py b/tests/test_request_params/test_header/test_required_str.py
new file mode 100644
index 0000000000..20dd296570
--- /dev/null
+++ b/tests/test_request_params/test_header/test_required_str.py
@@ -0,0 +1,402 @@
+from typing import Annotated
+
+import pytest
+from dirty_equals import AnyThing, IsOneOf, IsPartialDict
+from fastapi import FastAPI, Header
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.get("/required-str")
+async def read_required_str(p: Annotated[str, Header()]):
+ return {"p": p}
+
+
+class HeaderModelRequiredStr(BaseModel):
+ p: str
+
+
+@app.get("/model-required-str")
+async def read_model_required_str(p: Annotated[HeaderModelRequiredStr, Header()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P", "type": "string"},
+ "name": "p",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p"],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p": "hello"})
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.get("/required-alias")
+async def read_required_alias(p: Annotated[str, Header(alias="p_alias")]):
+ return {"p": p}
+
+
+class HeaderModelRequiredAlias(BaseModel):
+ p: str = Field(alias="p_alias")
+
+
+@app.get("/model-required-alias")
+async def read_model_required_alias(p: Annotated[HeaderModelRequiredAlias, Header()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_str_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P Alias", "type": "string"},
+ "name": "p_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_alias"],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias",
+ "/model-required-alias",
+ ],
+)
+def test_required_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p": "hello"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, IsPartialDict({"p": "hello"})),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias",
+ "/model-required-alias",
+ ],
+)
+def test_required_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p_alias": "hello"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.get("/required-validation-alias")
+def read_required_validation_alias(
+ p: Annotated[str, Header(validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class HeaderModelRequiredValidationAlias(BaseModel):
+ p: str = Field(validation_alias="p_val_alias")
+
+
+@app.get("/model-required-validation-alias")
+def read_model_required_validation_alias(
+ p: Annotated[HeaderModelRequiredValidationAlias, Header()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-validation-alias", "/model-required-validation-alias"],
+)
+def test_required_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P Val Alias", "type": "string"},
+ "name": "p_val_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "header",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p": "hello"})
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, IsPartialDict({"p": "hello"})),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p_val_alias": "hello"})
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.get("/required-alias-and-validation-alias")
+def read_required_alias_and_validation_alias(
+ p: Annotated[str, Header(alias="p_alias", validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class HeaderModelRequiredAliasAndValidationAlias(BaseModel):
+ p: str = Field(alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.get("/model-required-alias-and-validation-alias")
+def read_model_required_alias_and_validation_alias(
+ p: Annotated[HeaderModelRequiredAliasAndValidationAlias, Header()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P Val Alias", "type": "string"},
+ "name": "p_val_alias",
+ "in": "header",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "header",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": AnyThing,
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p": "hello"})
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "header",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ IsPartialDict({"p": "hello"}),
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p_alias": "hello"})
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ IsPartialDict({"p_alias": "hello"}),
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(path, headers={"p_val_alias": "hello"})
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": "hello"}
diff --git a/tests/test_request_params/test_path/__init__.py b/tests/test_request_params/test_path/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_request_params/test_path/test_list.py b/tests/test_request_params/test_path/test_list.py
new file mode 100644
index 0000000000..bba055d9a9
--- /dev/null
+++ b/tests/test_request_params/test_path/test_list.py
@@ -0,0 +1 @@
+# FastAPI doesn't currently support non-scalar Path parameters
diff --git a/tests/test_request_params/test_path/test_optional_list.py b/tests/test_request_params/test_path/test_optional_list.py
new file mode 100644
index 0000000000..0719430ac7
--- /dev/null
+++ b/tests/test_request_params/test_path/test_optional_list.py
@@ -0,0 +1 @@
+# Optional Path parameters are not supported
diff --git a/tests/test_request_params/test_path/test_optional_str.py b/tests/test_request_params/test_path/test_optional_str.py
new file mode 100644
index 0000000000..0719430ac7
--- /dev/null
+++ b/tests/test_request_params/test_path/test_optional_str.py
@@ -0,0 +1 @@
+# Optional Path parameters are not supported
diff --git a/tests/test_request_params/test_path/test_required_str.py b/tests/test_request_params/test_path/test_required_str.py
new file mode 100644
index 0000000000..b2d63667e2
--- /dev/null
+++ b/tests/test_request_params/test_path/test_required_str.py
@@ -0,0 +1,85 @@
+from typing import Annotated
+
+import pytest
+from fastapi import FastAPI, Path
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.get("/required-str/{p}")
+async def read_required_str(p: Annotated[str, Path()]):
+ return {"p": p}
+
+
+@app.get("/required-alias/{p_alias}")
+async def read_required_alias(p: Annotated[str, Path(alias="p_alias")]):
+ return {"p": p}
+
+
+@app.get("/required-validation-alias/{p_val_alias}")
+def read_required_validation_alias(
+ p: Annotated[str, Path(validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+@app.get("/required-alias-and-validation-alias/{p_val_alias}")
+def read_required_alias_and_validation_alias(
+ p: Annotated[str, Path(alias="p_alias", validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+@pytest.mark.parametrize(
+ ("path", "expected_name", "expected_title"),
+ [
+ pytest.param("/required-str/{p}", "p", "P", id="required-str"),
+ pytest.param(
+ "/required-alias/{p_alias}", "p_alias", "P Alias", id="required-alias"
+ ),
+ pytest.param(
+ "/required-validation-alias/{p_val_alias}",
+ "p_val_alias",
+ "P Val Alias",
+ id="required-validation-alias",
+ ),
+ pytest.param(
+ "/required-alias-and-validation-alias/{p_val_alias}",
+ "p_val_alias",
+ "P Val Alias",
+ id="required-alias-and-validation-alias",
+ ),
+ ],
+)
+def test_schema(path: str, expected_name: str, expected_title: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": expected_title, "type": "string"},
+ "name": expected_name,
+ "in": "path",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ pytest.param("/required-str", id="required-str"),
+ pytest.param("/required-alias", id="required-alias"),
+ pytest.param(
+ "/required-validation-alias",
+ id="required-validation-alias",
+ ),
+ pytest.param(
+ "/required-alias-and-validation-alias",
+ id="required-alias-and-validation-alias",
+ ),
+ ],
+)
+def test_success(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}/hello")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": "hello"}
diff --git a/tests/test_request_params/test_query/__init__.py b/tests/test_request_params/test_query/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_request_params/test_query/test_list.py b/tests/test_request_params/test_query/test_list.py
new file mode 100644
index 0000000000..e933da214d
--- /dev/null
+++ b/tests/test_request_params/test_query/test_list.py
@@ -0,0 +1,419 @@
+from typing import Annotated
+
+import pytest
+from dirty_equals import IsOneOf
+from fastapi import FastAPI, Query
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.get("/required-list-str")
+async def read_required_list_str(p: Annotated[list[str], Query()]):
+ return {"p": p}
+
+
+class QueryModelRequiredListStr(BaseModel):
+ p: list[str]
+
+
+@app.get("/model-required-list-str")
+def read_model_required_list_str(p: Annotated[QueryModelRequiredListStr, Query()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {
+ "title": "P",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "p",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-str", "/model-required-list-str"],
+)
+def test_required_list_str(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello&p=world")
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.get("/required-list-alias")
+async def read_required_list_alias(p: Annotated[list[str], Query(alias="p_alias")]):
+ return {"p": p}
+
+
+class QueryModelRequiredListAlias(BaseModel):
+ p: list[str] = Field(alias="p_alias")
+
+
+@app.get("/model-required-list-alias")
+async def read_model_required_list_alias(
+ p: Annotated[QueryModelRequiredListAlias, Query()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-alias", "/model-required-list-alias"],
+)
+def test_required_list_str_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {
+ "title": "P Alias",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "p_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-alias", "/model-required-list-alias"],
+)
+def test_required_list_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias",
+ "/model-required-list-alias",
+ ],
+)
+def test_required_list_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello&p=world")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": ["hello", "world"]}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias",
+ "/model-required-list-alias",
+ ],
+)
+def test_required_list_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_alias=hello&p_alias=world")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.get("/required-list-validation-alias")
+def read_required_list_validation_alias(
+ p: Annotated[list[str], Query(validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class QueryModelRequiredListValidationAlias(BaseModel):
+ p: list[str] = Field(validation_alias="p_val_alias")
+
+
+@app.get("/model-required-list-validation-alias")
+async def read_model_required_list_validation_alias(
+ p: Annotated[QueryModelRequiredListValidationAlias, Query()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-validation-alias", "/model-required-list-validation-alias"],
+)
+def test_required_list_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {
+ "title": "P Val Alias",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "p_val_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-validation-alias",
+ "/model-required-list-validation-alias",
+ ],
+)
+def test_required_list_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "query",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-validation-alias",
+ "/model-required-list-validation-alias",
+ ],
+)
+def test_required_list_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello&p=world")
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": ["hello", "world"]}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-list-validation-alias", "/model-required-list-validation-alias"],
+)
+def test_required_list_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world")
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.get("/required-list-alias-and-validation-alias")
+def read_required_list_alias_and_validation_alias(
+ p: Annotated[list[str], Query(alias="p_alias", validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class QueryModelRequiredListAliasAndValidationAlias(BaseModel):
+ p: list[str] = Field(alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.get("/model-required-list-alias-and-validation-alias")
+def read_model_required_list_alias_and_validation_alias(
+ p: Annotated[QueryModelRequiredListAliasAndValidationAlias, Query()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {
+ "title": "P Val Alias",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "p_val_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "query",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello&p=world")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "query",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ {
+ "p": [
+ "hello",
+ "world",
+ ]
+ },
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_alias=hello&p_alias=world")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ {"p_alias": ["hello", "world"]},
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-list-alias-and-validation-alias",
+ "/model-required-list-alias-and-validation-alias",
+ ],
+)
+def test_required_list_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
diff --git a/tests/test_request_params/test_query/test_optional_list.py b/tests/test_request_params/test_query/test_optional_list.py
new file mode 100644
index 0000000000..351e03a713
--- /dev/null
+++ b/tests/test_request_params/test_query/test_optional_list.py
@@ -0,0 +1,341 @@
+from typing import Annotated, Optional
+
+import pytest
+from fastapi import FastAPI, Query
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.get("/optional-list-str")
+async def read_optional_list_str(
+ p: Annotated[Optional[list[str]], Query()] = None,
+):
+ return {"p": p}
+
+
+class QueryModelOptionalListStr(BaseModel):
+ p: Optional[list[str]] = None
+
+
+@app.get("/model-optional-list-str")
+async def read_model_optional_list_str(
+ p: Annotated[QueryModelOptionalListStr, Query()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P",
+ },
+ "name": "p",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-str", "/model-optional-list-str"],
+)
+def test_optional_list_str(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello&p=world")
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.get("/optional-list-alias")
+async def read_optional_list_alias(
+ p: Annotated[Optional[list[str]], Query(alias="p_alias")] = None,
+):
+ return {"p": p}
+
+
+class QueryModelOptionalListAlias(BaseModel):
+ p: Optional[list[str]] = Field(None, alias="p_alias")
+
+
+@app.get("/model-optional-list-alias")
+async def read_model_optional_list_alias(
+ p: Annotated[QueryModelOptionalListAlias, Query()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_str_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Alias",
+ },
+ "name": "p_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-alias", "/model-optional-list-alias"],
+)
+def test_optional_list_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello&p=world")
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias",
+ "/model-optional-list-alias",
+ ],
+)
+def test_optional_list_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_alias=hello&p_alias=world")
+ assert response.status_code == 200
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.get("/optional-list-validation-alias")
+def read_optional_list_validation_alias(
+ p: Annotated[Optional[list[str]], Query(validation_alias="p_val_alias")] = None,
+):
+ return {"p": p}
+
+
+class QueryModelOptionalListValidationAlias(BaseModel):
+ p: Optional[list[str]] = Field(None, validation_alias="p_val_alias")
+
+
+@app.get("/model-optional-list-validation-alias")
+def read_model_optional_list_validation_alias(
+ p: Annotated[QueryModelOptionalListValidationAlias, Query()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
+)
+def test_optional_list_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ },
+ "name": "p_val_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
+)
+def test_optional_list_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-validation-alias",
+ "/model-optional-list-validation-alias",
+ ],
+)
+def test_optional_list_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello&p=world")
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-list-validation-alias", "/model-optional-list-validation-alias"],
+)
+def test_optional_list_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": ["hello", "world"]}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.get("/optional-list-alias-and-validation-alias")
+def read_optional_list_alias_and_validation_alias(
+ p: Annotated[
+ Optional[list[str]], Query(alias="p_alias", validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"p": p}
+
+
+class QueryModelOptionalListAliasAndValidationAlias(BaseModel):
+ p: Optional[list[str]] = Field(
+ None, alias="p_alias", validation_alias="p_val_alias"
+ )
+
+
+@app.get("/model-optional-list-alias-and-validation-alias")
+def read_model_optional_list_alias_and_validation_alias(
+ p: Annotated[QueryModelOptionalListAliasAndValidationAlias, Query()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"items": {"type": "string"}, "type": "array"},
+ {"type": "null"},
+ ],
+ "title": "P Val Alias",
+ },
+ "name": "p_val_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello&p=world")
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_alias=hello&p_alias=world")
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-list-alias-and-validation-alias",
+ "/model-optional-list-alias-and-validation-alias",
+ ],
+)
+def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "p": [
+ "hello",
+ "world",
+ ]
+ }
diff --git a/tests/test_request_params/test_query/test_optional_str.py b/tests/test_request_params/test_query/test_optional_str.py
new file mode 100644
index 0000000000..12e1b465a7
--- /dev/null
+++ b/tests/test_request_params/test_query/test_optional_str.py
@@ -0,0 +1,319 @@
+from typing import Annotated, Optional
+
+import pytest
+from fastapi import FastAPI, Query
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.get("/optional-str")
+async def read_optional_str(p: Optional[str] = None):
+ return {"p": p}
+
+
+class QueryModelOptionalStr(BaseModel):
+ p: Optional[str] = None
+
+
+@app.get("/model-optional-str")
+async def read_model_optional_str(p: Annotated[QueryModelOptionalStr, Query()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P",
+ },
+ "name": "p",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-str", "/model-optional-str"],
+)
+def test_optional_str(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello")
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.get("/optional-alias")
+async def read_optional_alias(
+ p: Annotated[Optional[str], Query(alias="p_alias")] = None,
+):
+ return {"p": p}
+
+
+class QueryModelOptionalAlias(BaseModel):
+ p: Optional[str] = Field(None, alias="p_alias")
+
+
+@app.get("/model-optional-alias")
+async def read_model_optional_alias(p: Annotated[QueryModelOptionalAlias, Query()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_str_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Alias",
+ },
+ "name": "p_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-alias", "/model-optional-alias"],
+)
+def test_optional_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello")
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias",
+ "/model-optional-alias",
+ ],
+)
+def test_optional_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_alias=hello")
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.get("/optional-validation-alias")
+def read_optional_validation_alias(
+ p: Annotated[Optional[str], Query(validation_alias="p_val_alias")] = None,
+):
+ return {"p": p}
+
+
+class QueryModelOptionalValidationAlias(BaseModel):
+ p: Optional[str] = Field(None, validation_alias="p_val_alias")
+
+
+@app.get("/model-optional-validation-alias")
+def read_model_optional_validation_alias(
+ p: Annotated[QueryModelOptionalValidationAlias, Query()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-validation-alias", "/model-optional-validation-alias"],
+)
+def test_optional_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
+ },
+ "name": "p_val_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/optional-validation-alias", "/model-optional-validation-alias"],
+)
+def test_optional_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-validation-alias",
+ "/model-optional-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello")
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-validation-alias",
+ "/model-optional-validation-alias",
+ ],
+)
+def test_optional_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_val_alias=hello")
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.get("/optional-alias-and-validation-alias")
+def read_optional_alias_and_validation_alias(
+ p: Annotated[
+ Optional[str], Query(alias="p_alias", validation_alias="p_val_alias")
+ ] = None,
+):
+ return {"p": p}
+
+
+class QueryModelOptionalAliasAndValidationAlias(BaseModel):
+ p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.get("/model-optional-alias-and-validation-alias")
+def read_model_optional_alias_and_validation_alias(
+ p: Annotated[QueryModelOptionalAliasAndValidationAlias, Query()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "P Val Alias",
+ },
+ "name": "p_val_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello")
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_alias=hello")
+ assert response.status_code == 200
+ assert response.json() == {"p": None}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/optional-alias-and-validation-alias",
+ "/model-optional-alias-and-validation-alias",
+ ],
+)
+def test_optional_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_val_alias=hello")
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
diff --git a/tests/test_request_params/test_query/test_required_str.py b/tests/test_request_params/test_query/test_required_str.py
new file mode 100644
index 0000000000..9e7b961453
--- /dev/null
+++ b/tests/test_request_params/test_query/test_required_str.py
@@ -0,0 +1,405 @@
+from typing import Annotated
+
+import pytest
+from dirty_equals import IsOneOf
+from fastapi import FastAPI, Query
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+# =====================================================================================
+# Without aliases
+
+
+@app.get("/required-str")
+async def read_required_str(p: str):
+ return {"p": p}
+
+
+class QueryModelRequiredStr(BaseModel):
+ p: str
+
+
+@app.get("/model-required-str")
+async def read_model_required_str(p: Annotated[QueryModelRequiredStr, Query()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P", "type": "string"},
+ "name": "p",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-str", "/model-required-str"],
+)
+def test_required_str(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello")
+ assert response.status_code == 200
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias
+
+
+@app.get("/required-alias")
+async def read_required_alias(p: Annotated[str, Query(alias="p_alias")]):
+ return {"p": p}
+
+
+class QueryModelRequiredAlias(BaseModel):
+ p: str = Field(alias="p_alias")
+
+
+@app.get("/model-required-alias")
+async def read_model_required_alias(p: Annotated[QueryModelRequiredAlias, Query()]):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_str_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P Alias", "type": "string"},
+ "name": "p_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-alias", "/model-required-alias"],
+)
+def test_required_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias",
+ "/model-required-alias",
+ ],
+)
+def test_required_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ {"p": "hello"},
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias",
+ "/model-required-alias",
+ ],
+)
+def test_required_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_alias=hello")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Validation alias
+
+
+@app.get("/required-validation-alias")
+def read_required_validation_alias(
+ p: Annotated[str, Query(validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class QueryModelRequiredValidationAlias(BaseModel):
+ p: str = Field(validation_alias="p_val_alias")
+
+
+@app.get("/model-required-validation-alias")
+def read_model_required_validation_alias(
+ p: Annotated[QueryModelRequiredValidationAlias, Query()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ ["/required-validation-alias", "/model-required-validation-alias"],
+)
+def test_required_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P Val Alias", "type": "string"},
+ "name": "p_val_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "query",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello")
+ assert response.status_code == 422, response.text
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(None, {"p": "hello"}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-validation-alias",
+ "/model-required-validation-alias",
+ ],
+)
+def test_required_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_val_alias=hello")
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": "hello"}
+
+
+# =====================================================================================
+# Alias and validation alias
+
+
+@app.get("/required-alias-and-validation-alias")
+def read_required_alias_and_validation_alias(
+ p: Annotated[str, Query(alias="p_alias", validation_alias="p_val_alias")],
+):
+ return {"p": p}
+
+
+class QueryModelRequiredAliasAndValidationAlias(BaseModel):
+ p: str = Field(alias="p_alias", validation_alias="p_val_alias")
+
+
+@app.get("/model-required-alias-and-validation-alias")
+def read_model_required_alias_and_validation_alias(
+ p: Annotated[QueryModelRequiredAliasAndValidationAlias, Query()],
+):
+ return {"p": p.p}
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_schema(path: str):
+ assert app.openapi()["paths"][path]["get"]["parameters"] == [
+ {
+ "required": True,
+ "schema": {"title": "P Val Alias", "type": "string"},
+ "name": "p_val_alias",
+ "in": "query",
+ }
+ ]
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_missing(path: str):
+ client = TestClient(app)
+ response = client.get(path)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "query",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(None, {}),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_name(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p=hello")
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": [
+ "query",
+ "p_val_alias",
+ ],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ {"p": "hello"},
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_alias=hello")
+ assert response.status_code == 422
+
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "p_val_alias"],
+ "msg": "Field required",
+ "input": IsOneOf(
+ None,
+ {"p_alias": "hello"},
+ ),
+ }
+ ]
+ }
+
+
+@pytest.mark.parametrize(
+ "path",
+ [
+ "/required-alias-and-validation-alias",
+ "/model-required-alias-and-validation-alias",
+ ],
+)
+def test_required_alias_and_validation_alias_by_validation_alias(path: str):
+ client = TestClient(app)
+ response = client.get(f"{path}?p_val_alias=hello")
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {"p": "hello"}
diff --git a/tests/test_response_by_alias.py b/tests/test_response_by_alias.py
index e162cd39b5..807d2600b9 100644
--- a/tests/test_response_by_alias.py
+++ b/tests/test_response_by_alias.py
@@ -1,7 +1,4 @@
-from typing import List
-
from fastapi import FastAPI
-from fastapi._compat import PYDANTIC_V2
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict, Field
@@ -15,24 +12,14 @@ class Model(BaseModel):
class ModelNoAlias(BaseModel):
name: str
- if PYDANTIC_V2:
- model_config = ConfigDict(
- json_schema_extra={
- "description": (
- "response_model_by_alias=False is basically a quick hack, to support "
- "proper OpenAPI use another model with the correct field names"
- )
- }
- )
- else:
-
- class Config:
- schema_extra = {
- "description": (
- "response_model_by_alias=False is basically a quick hack, to support "
- "proper OpenAPI use another model with the correct field names"
- )
- }
+ model_config = ConfigDict(
+ json_schema_extra={
+ "description": (
+ "response_model_by_alias=False is basically a quick hack, to support "
+ "proper OpenAPI use another model with the correct field names"
+ )
+ }
+ )
@app.get("/dict", response_model=Model, response_model_by_alias=False)
@@ -45,7 +32,7 @@ def read_model():
return Model(alias="Foo")
-@app.get("/list", response_model=List[Model], response_model_by_alias=False)
+@app.get("/list", response_model=list[Model], response_model_by_alias=False)
def read_list():
return [{"alias": "Foo"}, {"alias": "Bar"}]
@@ -60,7 +47,7 @@ def by_alias_model():
return Model(alias="Foo")
-@app.get("/by-alias/list", response_model=List[Model])
+@app.get("/by-alias/list", response_model=list[Model])
def by_alias_list():
return [{"alias": "Foo"}, {"alias": "Bar"}]
@@ -75,7 +62,7 @@ def no_alias_model():
return ModelNoAlias(name="Foo")
-@app.get("/no-alias/list", response_model=List[ModelNoAlias])
+@app.get("/no-alias/list", response_model=list[ModelNoAlias])
def no_alias_list():
return [{"name": "Foo"}, {"name": "Bar"}]
diff --git a/tests/test_response_class_no_mediatype.py b/tests/test_response_class_no_mediatype.py
index 706929ac36..4dc164bf92 100644
--- a/tests/test_response_class_no_mediatype.py
+++ b/tests/test_response_class_no_mediatype.py
@@ -1,5 +1,3 @@
-import typing
-
from fastapi import FastAPI, Response
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
@@ -18,7 +16,7 @@ class Error(BaseModel):
class JsonApiError(BaseModel):
- errors: typing.List[Error]
+ errors: list[Error]
@app.get(
diff --git a/tests/test_response_code_no_body.py b/tests/test_response_code_no_body.py
index 3ca8708f12..70456a7462 100644
--- a/tests/test_response_code_no_body.py
+++ b/tests/test_response_code_no_body.py
@@ -1,5 +1,3 @@
-import typing
-
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
@@ -18,7 +16,7 @@ class Error(BaseModel):
class JsonApiError(BaseModel):
- errors: typing.List[Error]
+ errors: list[Error]
@app.get(
diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py
index 1745c69b60..58fba89f1a 100644
--- a/tests/test_response_model_as_return_annotation.py
+++ b/tests/test_response_model_as_return_annotation.py
@@ -1,4 +1,4 @@
-from typing import List, Union
+from typing import Union
import pytest
from fastapi import FastAPI
@@ -7,8 +7,6 @@ from fastapi.responses import JSONResponse, Response
from fastapi.testclient import TestClient
from pydantic import BaseModel
-from tests.utils import needs_pydanticv1
-
class BaseUser(BaseModel):
name: str
@@ -191,7 +189,7 @@ def response_model_filtering_model_annotation_submodel_return_submodel() -> DBUs
return DBUser(name="John", surname="Doe", password_hash="secret")
-@app.get("/response_model_list_of_model-no_annotation", response_model=List[User])
+@app.get("/response_model_list_of_model-no_annotation", response_model=list[User])
def response_model_list_of_model_no_annotation():
return [
DBUser(name="John", surname="Doe", password_hash="secret"),
@@ -200,7 +198,7 @@ def response_model_list_of_model_no_annotation():
@app.get("/no_response_model-annotation_list_of_model")
-def no_response_model_annotation_list_of_model() -> List[User]:
+def no_response_model_annotation_list_of_model() -> list[User]:
return [
DBUser(name="John", surname="Doe", password_hash="secret"),
DBUser(name="Jane", surname="Does", password_hash="secret2"),
@@ -208,7 +206,7 @@ def no_response_model_annotation_list_of_model() -> List[User]:
@app.get("/no_response_model-annotation_forward_ref_list_of_model")
-def no_response_model_annotation_forward_ref_list_of_model() -> "List[User]":
+def no_response_model_annotation_forward_ref_list_of_model() -> "list[User]":
return [
DBUser(name="John", surname="Doe", password_hash="secret"),
DBUser(name="Jane", surname="Does", password_hash="secret2"),
@@ -511,26 +509,6 @@ def test_invalid_response_model_field():
assert "parameter response_model=None" in e.value.args[0]
-# TODO: remove when dropping Pydantic v1 support
-@needs_pydanticv1
-def test_invalid_response_model_field_pv1():
- from fastapi._compat import v1
-
- app = FastAPI()
-
- class Model(v1.BaseModel):
- foo: str
-
- with pytest.raises(FastAPIError) as e:
-
- @app.get("/")
- def read_root() -> Union[Response, Model, None]:
- return Response(content="Foo") # pragma: no cover
-
- assert "valid Pydantic field type" in e.value.args[0]
- assert "parameter response_model=None" in e.value.args[0]
-
-
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
diff --git a/tests/test_response_model_data_filter.py b/tests/test_response_model_data_filter.py
index a3e0f95f04..358697d6df 100644
--- a/tests/test_response_model_data_filter.py
+++ b/tests/test_response_model_data_filter.py
@@ -1,5 +1,3 @@
-from typing import List
-
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -44,7 +42,7 @@ async def read_pet(pet_id: int):
return pet
-@app.get("/pets/", response_model=List[PetOut])
+@app.get("/pets/", response_model=list[PetOut])
async def read_pets():
user = UserDB(
email="johndoe@example.com",
diff --git a/tests/test_response_model_data_filter_no_inheritance.py b/tests/test_response_model_data_filter_no_inheritance.py
index 64003a8417..c0c2f3a9dc 100644
--- a/tests/test_response_model_data_filter_no_inheritance.py
+++ b/tests/test_response_model_data_filter_no_inheritance.py
@@ -1,5 +1,3 @@
-from typing import List
-
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -46,7 +44,7 @@ async def read_pet(pet_id: int):
return pet
-@app.get("/pets/", response_model=List[PetOut])
+@app.get("/pets/", response_model=list[PetOut])
async def read_pets():
user = UserDB(
email="johndoe@example.com",
diff --git a/tests/test_response_model_invalid.py b/tests/test_response_model_invalid.py
index 88b55a436d..f884b5c74a 100644
--- a/tests/test_response_model_invalid.py
+++ b/tests/test_response_model_invalid.py
@@ -1,5 +1,3 @@
-from typing import List
-
import pytest
from fastapi import FastAPI
from fastapi.exceptions import FastAPIError
@@ -22,7 +20,7 @@ def test_invalid_response_model_sub_type_raises():
with pytest.raises(FastAPIError):
app = FastAPI()
- @app.get("/", response_model=List[NonPydanticModel])
+ @app.get("/", response_model=list[NonPydanticModel])
def read_root():
pass # pragma: nocover
@@ -40,6 +38,6 @@ def test_invalid_response_model_sub_type_in_responses_raises():
with pytest.raises(FastAPIError):
app = FastAPI()
- @app.get("/", responses={"500": {"model": List[NonPydanticModel]}})
+ @app.get("/", responses={"500": {"model": list[NonPydanticModel]}})
def read_root():
pass # pragma: nocover
diff --git a/tests/test_response_model_sub_types.py b/tests/test_response_model_sub_types.py
index 660bcee1bb..8036bcb07d 100644
--- a/tests/test_response_model_sub_types.py
+++ b/tests/test_response_model_sub_types.py
@@ -1,5 +1,3 @@
-from typing import List
-
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -17,7 +15,7 @@ def valid1():
pass
-@app.get("/valid2", responses={"500": {"model": List[int]}})
+@app.get("/valid2", responses={"500": {"model": list[int]}})
def valid2():
pass
@@ -27,7 +25,7 @@ def valid3():
pass
-@app.get("/valid4", responses={"500": {"model": List[Model]}})
+@app.get("/valid4", responses={"500": {"model": list[Model]}})
def valid4():
pass
diff --git a/tests/test_router_events.py b/tests/test_router_events.py
index dd7ff3314b..9df299cdaa 100644
--- a/tests/test_router_events.py
+++ b/tests/test_router_events.py
@@ -1,5 +1,6 @@
+from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
-from typing import AsyncGenerator, Dict, Union
+from typing import Union
import pytest
from fastapi import APIRouter, FastAPI, Request
@@ -28,7 +29,7 @@ def test_router_events(state: State) -> None:
app = FastAPI()
@app.get("/")
- def main() -> Dict[str, str]:
+ def main() -> dict[str, str]:
return {"message": "Hello World"}
@app.on_event("startup")
@@ -96,7 +97,7 @@ def test_app_lifespan_state(state: State) -> None:
app = FastAPI(lifespan=lifespan)
@app.get("/")
- def main() -> Dict[str, str]:
+ def main() -> dict[str, str]:
return {"message": "Hello World"}
assert state.app_startup is False
@@ -113,19 +114,19 @@ def test_app_lifespan_state(state: State) -> None:
def test_router_nested_lifespan_state(state: State) -> None:
@asynccontextmanager
- async def lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]:
+ async def lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]:
state.app_startup = True
yield {"app": True}
state.app_shutdown = True
@asynccontextmanager
- async def router_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]:
+ async def router_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]:
state.router_startup = True
yield {"router": True}
state.router_shutdown = True
@asynccontextmanager
- async def subrouter_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]:
+ async def subrouter_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]:
state.sub_router_startup = True
yield {"sub_router": True}
state.sub_router_shutdown = True
@@ -139,7 +140,7 @@ def test_router_nested_lifespan_state(state: State) -> None:
app.include_router(router)
@app.get("/")
- def main(request: Request) -> Dict[str, str]:
+ def main(request: Request) -> dict[str, str]:
assert request.state.app
assert request.state.router
assert request.state.sub_router
@@ -175,7 +176,7 @@ def test_router_nested_lifespan_state_overriding_by_parent() -> None:
@asynccontextmanager
async def lifespan(
app: FastAPI,
- ) -> AsyncGenerator[Dict[str, Union[str, bool]], None]:
+ ) -> AsyncGenerator[dict[str, Union[str, bool]], None]:
yield {
"app_specific": True,
"overridden": "app",
@@ -184,7 +185,7 @@ def test_router_nested_lifespan_state_overriding_by_parent() -> None:
@asynccontextmanager
async def router_lifespan(
app: FastAPI,
- ) -> AsyncGenerator[Dict[str, Union[str, bool]], None]:
+ ) -> AsyncGenerator[dict[str, Union[str, bool]], None]:
yield {
"router_specific": True,
"overridden": "router", # should override parent
@@ -225,7 +226,7 @@ def test_merged_mixed_state_lifespans() -> None:
yield
@asynccontextmanager
- async def router_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]:
+ async def router_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]:
yield {"router": True}
@asynccontextmanager
diff --git a/tests/test_schema_compat_pydantic_v2.py b/tests/test_schema_compat_pydantic_v2.py
new file mode 100644
index 0000000000..737687f256
--- /dev/null
+++ b/tests/test_schema_compat_pydantic_v2.py
@@ -0,0 +1,90 @@
+import pytest
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+from pydantic import BaseModel
+
+from tests.utils import needs_py310
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from enum import Enum
+
+ app = FastAPI()
+
+ class PlatformRole(str, Enum):
+ admin = "admin"
+ user = "user"
+
+ class OtherRole(str, Enum): ...
+
+ class User(BaseModel):
+ username: str
+ role: PlatformRole | OtherRole
+
+ @app.get("/users")
+ async def get_user() -> User:
+ return {"username": "alice", "role": "admin"}
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_get(client: TestClient):
+ response = client.get("/users")
+ assert response.json() == {"username": "alice", "role": "admin"}
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("openapi.json")
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/users": {
+ "get": {
+ "summary": "Get User",
+ "operationId": "get_user_users_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PlatformRole": {
+ "type": "string",
+ "enum": ["admin", "user"],
+ "title": "PlatformRole",
+ },
+ "User": {
+ "properties": {
+ "username": {"type": "string", "title": "Username"},
+ "role": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/PlatformRole"},
+ {"enum": [], "title": "OtherRole"},
+ ],
+ "title": "Role",
+ },
+ },
+ "type": "object",
+ "required": ["username", "role"],
+ "title": "User",
+ },
+ }
+ },
+ }
+ )
diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py
index b313f47e90..ac8999c90a 100644
--- a/tests/test_schema_extra_examples.py
+++ b/tests/test_schema_extra_examples.py
@@ -1,9 +1,8 @@
from typing import Union
import pytest
-from dirty_equals import IsDict
from fastapi import Body, Cookie, FastAPI, Header, Path, Query
-from fastapi._compat import PYDANTIC_V2
+from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
@@ -14,20 +13,15 @@ def create_app():
class Item(BaseModel):
data: str
- if PYDANTIC_V2:
- model_config = ConfigDict(
- json_schema_extra={"example": {"data": "Data in schema_extra"}}
- )
- else:
-
- class Config:
- schema_extra = {"example": {"data": "Data in schema_extra"}}
+ model_config = ConfigDict(
+ json_schema_extra={"example": {"data": "Data in schema_extra"}}
+ )
@app.post("/schema_extra/")
def schema_extra(item: Item):
return item
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.post("/example/")
def example(item: Item = Body(example={"data": "Data in Body example"})):
@@ -44,7 +38,7 @@ def create_app():
):
return item
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.post("/example_examples/")
def example_examples(
@@ -89,7 +83,7 @@ def create_app():
# ):
# return lastname
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/path_example/{item_id}")
def path_example(
@@ -107,7 +101,7 @@ def create_app():
):
return item_id
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/path_example_examples/{item_id}")
def path_example_examples(
@@ -118,7 +112,7 @@ def create_app():
):
return item_id
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/query_example/")
def query_example(
@@ -138,7 +132,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/query_example_examples/")
def query_example_examples(
@@ -150,7 +144,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/header_example/")
def header_example(
@@ -173,7 +167,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/header_example_examples/")
def header_example_examples(
@@ -185,7 +179,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/cookie_example/")
def cookie_example(
@@ -205,7 +199,7 @@ def create_app():
):
return data
- with pytest.warns(DeprecationWarning):
+ with pytest.warns(FastAPIDeprecationWarning):
@app.get("/cookie_example_examples/")
def cookie_example_examples(
@@ -341,28 +335,13 @@ def test_openapi_schema():
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "$ref": "#/components/schemas/Item",
- "examples": [
- {"data": "Data in Body examples, example1"},
- {"data": "Data in Body examples, example2"},
- ],
- }
- )
- | IsDict(
- # TODO: remove this when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- "examples": [
- {"data": "Data in Body examples, example1"},
- {"data": "Data in Body examples, example2"},
- ],
- }
- )
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ "examples": [
+ {"data": "Data in Body examples, example1"},
+ {"data": "Data in Body examples, example2"},
+ ],
+ }
}
},
"required": True,
@@ -392,28 +371,13 @@ def test_openapi_schema():
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "$ref": "#/components/schemas/Item",
- "examples": [
- {"data": "examples example_examples 1"},
- {"data": "examples example_examples 2"},
- ],
- }
- )
- | IsDict(
- # TODO: remove this when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- "examples": [
- {"data": "examples example_examples 1"},
- {"data": "examples example_examples 2"},
- ],
- },
- ),
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ "examples": [
+ {"data": "examples example_examples 1"},
+ {"data": "examples example_examples 2"},
+ ],
+ },
"example": {"data": "Overridden example"},
}
},
@@ -544,16 +508,10 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {"title": "Data", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ },
"example": "query1",
"name": "data",
"in": "query",
@@ -584,21 +542,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["query1", "query2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Data",
- "examples": ["query1", "query2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["query1", "query2"],
+ },
"name": "data",
"in": "query",
}
@@ -628,21 +576,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["query1", "query2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Data",
- "examples": ["query1", "query2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["query1", "query2"],
+ },
"example": "query_overridden",
"name": "data",
"in": "query",
@@ -673,16 +611,10 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {"title": "Data", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ },
"example": "header1",
"name": "data",
"in": "header",
@@ -713,21 +645,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["header1", "header2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Data",
- "examples": ["header1", "header2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["header1", "header2"],
+ },
"name": "data",
"in": "header",
}
@@ -757,21 +679,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["header1", "header2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "title": "Data",
- "type": "string",
- "examples": ["header1", "header2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["header1", "header2"],
+ },
"example": "header_overridden",
"name": "data",
"in": "header",
@@ -802,16 +714,10 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {"title": "Data", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ },
"example": "cookie1",
"name": "data",
"in": "cookie",
@@ -842,21 +748,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["cookie1", "cookie2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "title": "Data",
- "type": "string",
- "examples": ["cookie1", "cookie2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["cookie1", "cookie2"],
+ },
"name": "data",
"in": "cookie",
}
@@ -886,21 +782,11 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Data",
- "examples": ["cookie1", "cookie2"],
- }
- )
- | IsDict(
- # TODO: Remove this when deprecating Pydantic v1
- {
- "title": "Data",
- "type": "string",
- "examples": ["cookie1", "cookie2"],
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Data",
+ "examples": ["cookie1", "cookie2"],
+ },
"example": "cookie_overridden",
"name": "data",
"in": "cookie",
diff --git a/tests/test_schema_ref_pydantic_v2.py b/tests/test_schema_ref_pydantic_v2.py
new file mode 100644
index 0000000000..69cb82a356
--- /dev/null
+++ b/tests/test_schema_ref_pydantic_v2.py
@@ -0,0 +1,68 @@
+from typing import Any
+
+import pytest
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+from pydantic import BaseModel, ConfigDict, Field
+
+
+@pytest.fixture(name="client")
+def get_client():
+ app = FastAPI()
+
+ class ModelWithRef(BaseModel):
+ ref: str = Field(validation_alias="$ref", serialization_alias="$ref")
+ model_config = ConfigDict(validate_by_alias=True, serialize_by_alias=True)
+
+ @app.get("/", response_model=ModelWithRef)
+ async def read_root() -> Any:
+ return {"$ref": "some-ref"}
+
+ client = TestClient(app)
+ return client
+
+
+def test_get(client: TestClient):
+ response = client.get("/")
+ assert response.json() == {"$ref": "some-ref"}
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("openapi.json")
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "get": {
+ "summary": "Read Root",
+ "operationId": "read_root__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ModelWithRef"
+ }
+ }
+ },
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ModelWithRef": {
+ "properties": {"$ref": {"type": "string", "title": "$Ref"}},
+ "type": "object",
+ "required": ["$ref"],
+ "title": "ModelWithRef",
+ }
+ }
+ },
+ }
+ )
diff --git a/tests/test_security_api_key_cookie.py b/tests/test_security_api_key_cookie.py
index 4ddb8e2eeb..9bacfc56ec 100644
--- a/tests/test_security_api_key_cookie.py
+++ b/tests/test_security_api_key_cookie.py
@@ -32,8 +32,9 @@ def test_security_api_key():
def test_security_api_key_no_key():
client = TestClient(app)
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "APIKey"
def test_openapi_schema():
diff --git a/tests/test_security_api_key_cookie_description.py b/tests/test_security_api_key_cookie_description.py
index d99d616e06..d0cab324eb 100644
--- a/tests/test_security_api_key_cookie_description.py
+++ b/tests/test_security_api_key_cookie_description.py
@@ -32,8 +32,9 @@ def test_security_api_key():
def test_security_api_key_no_key():
client = TestClient(app)
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "APIKey"
def test_openapi_schema():
diff --git a/tests/test_security_api_key_header.py b/tests/test_security_api_key_header.py
index 1ff883703d..3e761b150b 100644
--- a/tests/test_security_api_key_header.py
+++ b/tests/test_security_api_key_header.py
@@ -33,8 +33,9 @@ def test_security_api_key():
def test_security_api_key_no_key():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "APIKey"
def test_openapi_schema():
diff --git a/tests/test_security_api_key_header_description.py b/tests/test_security_api_key_header_description.py
index 27f9d0f29e..38a1a88814 100644
--- a/tests/test_security_api_key_header_description.py
+++ b/tests/test_security_api_key_header_description.py
@@ -33,8 +33,9 @@ def test_security_api_key():
def test_security_api_key_no_key():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "APIKey"
def test_openapi_schema():
diff --git a/tests/test_security_api_key_query.py b/tests/test_security_api_key_query.py
index dc7a0a621a..11ed194689 100644
--- a/tests/test_security_api_key_query.py
+++ b/tests/test_security_api_key_query.py
@@ -33,8 +33,9 @@ def test_security_api_key():
def test_security_api_key_no_key():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "APIKey"
def test_openapi_schema():
diff --git a/tests/test_security_api_key_query_description.py b/tests/test_security_api_key_query_description.py
index 35dc7743a2..6587983261 100644
--- a/tests/test_security_api_key_query_description.py
+++ b/tests/test_security_api_key_query_description.py
@@ -33,8 +33,9 @@ def test_security_api_key():
def test_security_api_key_no_key():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "APIKey"
def test_openapi_schema():
diff --git a/tests/test_security_http_base.py b/tests/test_security_http_base.py
index 51928bafd5..8cf259a750 100644
--- a/tests/test_security_http_base.py
+++ b/tests/test_security_http_base.py
@@ -23,8 +23,9 @@ def test_security_http_base():
def test_security_http_base_no_credentials():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Other"
def test_openapi_schema():
diff --git a/tests/test_security_http_base_description.py b/tests/test_security_http_base_description.py
index bc79f32424..791ea59f4d 100644
--- a/tests/test_security_http_base_description.py
+++ b/tests/test_security_http_base_description.py
@@ -23,8 +23,9 @@ def test_security_http_base():
def test_security_http_base_no_credentials():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Other"
def test_openapi_schema():
diff --git a/tests/test_security_http_basic_optional.py b/tests/test_security_http_basic_optional.py
index 9b6cb6c455..7071f381a1 100644
--- a/tests/test_security_http_basic_optional.py
+++ b/tests/test_security_http_basic_optional.py
@@ -38,7 +38,7 @@ def test_security_http_basic_invalid_credentials():
)
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == "Basic"
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.json() == {"detail": "Not authenticated"}
def test_security_http_basic_non_basic_credentials():
@@ -47,7 +47,7 @@ def test_security_http_basic_non_basic_credentials():
response = client.get("/users/me", headers={"Authorization": auth_header})
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == "Basic"
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.json() == {"detail": "Not authenticated"}
def test_openapi_schema():
diff --git a/tests/test_security_http_basic_realm.py b/tests/test_security_http_basic_realm.py
index 9fc33971ae..ec7371f90f 100644
--- a/tests/test_security_http_basic_realm.py
+++ b/tests/test_security_http_basic_realm.py
@@ -36,7 +36,7 @@ def test_security_http_basic_invalid_credentials():
)
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"'
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.json() == {"detail": "Not authenticated"}
def test_security_http_basic_non_basic_credentials():
@@ -45,7 +45,7 @@ def test_security_http_basic_non_basic_credentials():
response = client.get("/users/me", headers={"Authorization": auth_header})
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"'
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.json() == {"detail": "Not authenticated"}
def test_openapi_schema():
diff --git a/tests/test_security_http_basic_realm_description.py b/tests/test_security_http_basic_realm_description.py
index 02122442eb..a93d5fc86b 100644
--- a/tests/test_security_http_basic_realm_description.py
+++ b/tests/test_security_http_basic_realm_description.py
@@ -36,7 +36,7 @@ def test_security_http_basic_invalid_credentials():
)
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"'
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.json() == {"detail": "Not authenticated"}
def test_security_http_basic_non_basic_credentials():
@@ -45,7 +45,7 @@ def test_security_http_basic_non_basic_credentials():
response = client.get("/users/me", headers={"Authorization": auth_header})
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"'
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.json() == {"detail": "Not authenticated"}
def test_openapi_schema():
diff --git a/tests/test_security_http_bearer.py b/tests/test_security_http_bearer.py
index 5b9e2d6919..961b42f4db 100644
--- a/tests/test_security_http_bearer.py
+++ b/tests/test_security_http_bearer.py
@@ -23,14 +23,16 @@ def test_security_http_bearer():
def test_security_http_bearer_no_credentials():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
def test_security_http_bearer_incorrect_scheme_credentials():
response = client.get("/users/me", headers={"Authorization": "Basic notreally"})
- assert response.status_code == 403, response.text
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
def test_openapi_schema():
diff --git a/tests/test_security_http_bearer_description.py b/tests/test_security_http_bearer_description.py
index 2f11c3a148..e16994abce 100644
--- a/tests/test_security_http_bearer_description.py
+++ b/tests/test_security_http_bearer_description.py
@@ -23,14 +23,16 @@ def test_security_http_bearer():
def test_security_http_bearer_no_credentials():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
def test_security_http_bearer_incorrect_scheme_credentials():
response = client.get("/users/me", headers={"Authorization": "Basic notreally"})
- assert response.status_code == 403, response.text
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
def test_openapi_schema():
diff --git a/tests/test_security_http_digest.py b/tests/test_security_http_digest.py
index 133d35763c..3fad4c7a56 100644
--- a/tests/test_security_http_digest.py
+++ b/tests/test_security_http_digest.py
@@ -23,16 +23,18 @@ def test_security_http_digest():
def test_security_http_digest_no_credentials():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Digest"
def test_security_http_digest_incorrect_scheme_credentials():
response = client.get(
"/users/me", headers={"Authorization": "Other invalidauthorization"}
)
- assert response.status_code == 403, response.text
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Digest"
def test_openapi_schema():
diff --git a/tests/test_security_http_digest_description.py b/tests/test_security_http_digest_description.py
index 4e31a0c008..319416a071 100644
--- a/tests/test_security_http_digest_description.py
+++ b/tests/test_security_http_digest_description.py
@@ -23,16 +23,18 @@ def test_security_http_digest():
def test_security_http_digest_no_credentials():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Digest"
def test_security_http_digest_incorrect_scheme_credentials():
response = client.get(
"/users/me", headers={"Authorization": "Other invalidauthorization"}
)
- assert response.status_code == 403, response.text
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Digest"
def test_openapi_schema():
diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py
index 2b7e3457a9..7ad9369956 100644
--- a/tests/test_security_oauth2.py
+++ b/tests/test_security_oauth2.py
@@ -1,5 +1,4 @@
import pytest
-from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
from fastapi.testclient import TestClient
@@ -56,86 +55,51 @@ def test_security_oauth2_password_other_header():
def test_security_oauth2_password_bearer_no_header():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
def test_strict_login_no_data():
response = client.post("/login")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_strict_login_no_grant_type():
response = client.post("/login", data={"username": "johndoe", "password": "secret"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -152,31 +116,17 @@ def test_strict_login_incorrect_grant_type(grant_type: str):
data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["body", "grant_type"],
- "msg": "String should match pattern '^password$'",
- "input": grant_type,
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": 'string does not match regex "^password$"',
- "type": "value_error.str.regex",
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["body", "grant_type"],
+ "msg": "String should match pattern '^password$'",
+ "input": grant_type,
+ "ctx": {"pattern": "^password$"},
+ }
+ ]
+ }
def test_strict_login_correct_grant_type():
@@ -263,26 +213,14 @@ def test_openapi_schema():
"username": {"title": "Username", "type": "string"},
"password": {"title": "Password", "type": "string"},
"scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
+ "client_id": {
+ "title": "Client Id",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "client_secret": {
+ "title": "Client Secret",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py
new file mode 100644
index 0000000000..583007c8b7
--- /dev/null
+++ b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py
@@ -0,0 +1,197 @@
+# Ref: https://github.com/fastapi/fastapi/issues/14454
+
+from typing import Annotated, Optional
+
+from fastapi import APIRouter, Depends, FastAPI, Security
+from fastapi.security import OAuth2AuthorizationCodeBearer
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+
+oauth2_scheme = OAuth2AuthorizationCodeBearer(
+ authorizationUrl="authorize",
+ tokenUrl="token",
+ auto_error=True,
+ scopes={"read": "Read access", "write": "Write access"},
+)
+
+
+async def get_token(token: Annotated[str, Depends(oauth2_scheme)]) -> str:
+ return token
+
+
+app = FastAPI(dependencies=[Depends(get_token)])
+
+
+@app.get("/")
+async def root():
+ return {"message": "Hello World"}
+
+
+@app.get(
+ "/with-oauth2-scheme",
+ dependencies=[Security(oauth2_scheme, scopes=["read", "write"])],
+)
+async def read_with_oauth2_scheme():
+ return {"message": "Admin Access"}
+
+
+@app.get(
+ "/with-get-token", dependencies=[Security(get_token, scopes=["read", "write"])]
+)
+async def read_with_get_token():
+ return {"message": "Admin Access"}
+
+
+router = APIRouter(dependencies=[Security(oauth2_scheme, scopes=["read"])])
+
+
+@router.get("/items/")
+async def read_items(token: Optional[str] = Depends(oauth2_scheme)):
+ return {"token": token}
+
+
+@router.post("/items/")
+async def create_item(
+ token: Optional[str] = Security(oauth2_scheme, scopes=["read", "write"]),
+):
+ return {"token": token}
+
+
+app.include_router(router)
+
+client = TestClient(app)
+
+
+def test_root():
+ response = client.get("/", headers={"Authorization": "Bearer testtoken"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Hello World"}
+
+
+def test_read_with_oauth2_scheme():
+ response = client.get(
+ "/with-oauth2-scheme", headers={"Authorization": "Bearer testtoken"}
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Admin Access"}
+
+
+def test_read_with_get_token():
+ response = client.get(
+ "/with-get-token", headers={"Authorization": "Bearer testtoken"}
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Admin Access"}
+
+
+def test_read_token():
+ response = client.get("/items/", headers={"Authorization": "Bearer testtoken"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"token": "testtoken"}
+
+
+def test_create_token():
+ response = client.post("/items/", headers={"Authorization": "Bearer testtoken"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"token": "testtoken"}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "get": {
+ "summary": "Root",
+ "operationId": "root__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "security": [{"OAuth2AuthorizationCodeBearer": []}],
+ }
+ },
+ "/with-oauth2-scheme": {
+ "get": {
+ "summary": "Read With Oauth2 Scheme",
+ "operationId": "read_with_oauth2_scheme_with_oauth2_scheme_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "security": [
+ {"OAuth2AuthorizationCodeBearer": ["read", "write"]}
+ ],
+ }
+ },
+ "/with-get-token": {
+ "get": {
+ "summary": "Read With Get Token",
+ "operationId": "read_with_get_token_with_get_token_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "security": [
+ {"OAuth2AuthorizationCodeBearer": ["read", "write"]}
+ ],
+ }
+ },
+ "/items/": {
+ "get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "security": [
+ {"OAuth2AuthorizationCodeBearer": ["read"]},
+ ],
+ },
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "security": [
+ {"OAuth2AuthorizationCodeBearer": ["read", "write"]},
+ ],
+ },
+ },
+ },
+ "components": {
+ "securitySchemes": {
+ "OAuth2AuthorizationCodeBearer": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "scopes": {
+ "read": "Read access",
+ "write": "Write access",
+ },
+ "authorizationUrl": "authorize",
+ "tokenUrl": "token",
+ }
+ },
+ }
+ }
+ },
+ }
+ )
diff --git a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py
new file mode 100644
index 0000000000..1c21369d33
--- /dev/null
+++ b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py
@@ -0,0 +1,80 @@
+# Ref: https://github.com/fastapi/fastapi/issues/14454
+
+from typing import Annotated
+
+from fastapi import Depends, FastAPI, Security
+from fastapi.security import OAuth2AuthorizationCodeBearer
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+
+oauth2_scheme = OAuth2AuthorizationCodeBearer(
+ authorizationUrl="api/oauth/authorize",
+ tokenUrl="/api/oauth/token",
+ scopes={"read": "Read access", "write": "Write access"},
+)
+
+
+async def get_token(token: Annotated[str, Depends(oauth2_scheme)]) -> str:
+ return token
+
+
+app = FastAPI(dependencies=[Depends(get_token)])
+
+
+@app.get("/admin", dependencies=[Security(get_token, scopes=["read", "write"])])
+async def read_admin():
+ return {"message": "Admin Access"}
+
+
+client = TestClient(app)
+
+
+def test_read_admin():
+ response = client.get("/admin", headers={"Authorization": "Bearer faketoken"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Admin Access"}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/admin": {
+ "get": {
+ "summary": "Read Admin",
+ "operationId": "read_admin_admin_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "security": [
+ {"OAuth2AuthorizationCodeBearer": ["read", "write"]}
+ ],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "OAuth2AuthorizationCodeBearer": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "scopes": {
+ "read": "Read access",
+ "write": "Write access",
+ },
+ "authorizationUrl": "api/oauth/authorize",
+ "tokenUrl": "/api/oauth/token",
+ }
+ },
+ }
+ }
+ },
+ }
+ )
diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py
index 046ac57637..57c16058af 100644
--- a/tests/test_security_oauth2_optional.py
+++ b/tests/test_security_oauth2_optional.py
@@ -1,7 +1,6 @@
from typing import Optional
import pytest
-from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
from fastapi.testclient import TestClient
@@ -67,79 +66,43 @@ def test_security_oauth2_password_bearer_no_header():
def test_strict_login_no_data():
response = client.post("/login")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_strict_login_no_grant_type():
response = client.post("/login", data={"username": "johndoe", "password": "secret"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -156,31 +119,17 @@ def test_strict_login_incorrect_grant_type(grant_type: str):
data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["body", "grant_type"],
- "msg": "String should match pattern '^password$'",
- "input": grant_type,
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": 'string does not match regex "^password$"',
- "type": "value_error.str.regex",
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["body", "grant_type"],
+ "msg": "String should match pattern '^password$'",
+ "input": grant_type,
+ "ctx": {"pattern": "^password$"},
+ }
+ ]
+ }
def test_strict_login_correct_data():
@@ -267,26 +216,14 @@ def test_openapi_schema():
"username": {"title": "Username", "type": "string"},
"password": {"title": "Password", "type": "string"},
"scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
+ "client_id": {
+ "title": "Client Id",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "client_secret": {
+ "title": "Client Secret",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py
index 629cddca2f..60c6c242e0 100644
--- a/tests/test_security_oauth2_optional_description.py
+++ b/tests/test_security_oauth2_optional_description.py
@@ -1,7 +1,6 @@
from typing import Optional
import pytest
-from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
from fastapi.testclient import TestClient
@@ -68,79 +67,43 @@ def test_security_oauth2_password_bearer_no_header():
def test_strict_login_None():
response = client.post("/login", data=None)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_strict_login_no_grant_type():
response = client.post("/login", data={"username": "johndoe", "password": "secret"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "grant_type"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "grant_type"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
@pytest.mark.parametrize(
@@ -157,31 +120,17 @@ def test_strict_login_incorrect_grant_type(grant_type: str):
data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["body", "grant_type"],
- "msg": "String should match pattern '^password$'",
- "input": grant_type,
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "grant_type"],
- "msg": 'string does not match regex "^password$"',
- "type": "value_error.str.regex",
- "ctx": {"pattern": "^password$"},
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["body", "grant_type"],
+ "msg": "String should match pattern '^password$'",
+ "input": grant_type,
+ "ctx": {"pattern": "^password$"},
+ }
+ ]
+ }
def test_strict_login_correct_correct_grant_type():
@@ -268,26 +217,14 @@ def test_openapi_schema():
"username": {"title": "Username", "type": "string"},
"password": {"title": "Password", "type": "string"},
"scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
+ "client_id": {
+ "title": "Client Id",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "client_secret": {
+ "title": "Client Secret",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_security_openid_connect.py b/tests/test_security_openid_connect.py
index 1e322e640e..c9a0a8db76 100644
--- a/tests/test_security_openid_connect.py
+++ b/tests/test_security_openid_connect.py
@@ -39,8 +39,9 @@ def test_security_oauth2_password_other_header():
def test_security_oauth2_password_bearer_no_header():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
def test_openapi_schema():
diff --git a/tests/test_security_openid_connect_description.py b/tests/test_security_openid_connect_description.py
index 44cf57f862..d008cbc630 100644
--- a/tests/test_security_openid_connect_description.py
+++ b/tests/test_security_openid_connect_description.py
@@ -41,8 +41,9 @@ def test_security_oauth2_password_other_header():
def test_security_oauth2_password_bearer_no_header():
response = client.get("/users/me")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
def test_openapi_schema():
diff --git a/tests/test_security_scopes.py b/tests/test_security_scopes.py
new file mode 100644
index 0000000000..fccb026fe2
--- /dev/null
+++ b/tests/test_security_scopes.py
@@ -0,0 +1,45 @@
+from typing import Annotated
+
+import pytest
+from fastapi import Depends, FastAPI, Security
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(name="call_counter")
+def call_counter_fixture():
+ return {"count": 0}
+
+
+@pytest.fixture(name="app")
+def app_fixture(call_counter: dict[str, int]):
+ def get_db():
+ call_counter["count"] += 1
+ return f"db_{call_counter['count']}"
+
+ def get_user(db: Annotated[str, Depends(get_db)]):
+ return "user"
+
+ app = FastAPI()
+
+ @app.get("/")
+ def endpoint(
+ db: Annotated[str, Depends(get_db)],
+ user: Annotated[str, Security(get_user, scopes=["read"])],
+ ):
+ return {"db": db}
+
+ return app
+
+
+@pytest.fixture(name="client")
+def client_fixture(app: FastAPI):
+ return TestClient(app)
+
+
+def test_security_scopes_dependency_called_once(
+ client: TestClient, call_counter: dict[str, int]
+):
+ response = client.get("/")
+
+ assert response.status_code == 200
+ assert call_counter["count"] == 1
diff --git a/tests/test_security_scopes_dont_propagate.py b/tests/test_security_scopes_dont_propagate.py
new file mode 100644
index 0000000000..c306ed0590
--- /dev/null
+++ b/tests/test_security_scopes_dont_propagate.py
@@ -0,0 +1,44 @@
+# Ref: https://github.com/tiangolo/fastapi/issues/5623
+
+from typing import Annotated, Any
+
+from fastapi import FastAPI, Security
+from fastapi.security import SecurityScopes
+from fastapi.testclient import TestClient
+
+
+async def security1(scopes: SecurityScopes):
+ return scopes.scopes
+
+
+async def security2(scopes: SecurityScopes):
+ return scopes.scopes
+
+
+async def dep3(
+ dep1: Annotated[list[str], Security(security1, scopes=["scope1"])],
+ dep2: Annotated[list[str], Security(security2, scopes=["scope2"])],
+):
+ return {"dep1": dep1, "dep2": dep2}
+
+
+app = FastAPI()
+
+
+@app.get("/scopes")
+def get_scopes(
+ dep3: Annotated[dict[str, Any], Security(dep3, scopes=["scope3"])],
+):
+ return dep3
+
+
+client = TestClient(app)
+
+
+def test_security_scopes_dont_propagate():
+ response = client.get("/scopes")
+ assert response.status_code == 200
+ assert response.json() == {
+ "dep1": ["scope3", "scope1"],
+ "dep2": ["scope3", "scope2"],
+ }
diff --git a/tests/test_security_scopes_sub_dependency.py b/tests/test_security_scopes_sub_dependency.py
new file mode 100644
index 0000000000..2c64d5f3d5
--- /dev/null
+++ b/tests/test_security_scopes_sub_dependency.py
@@ -0,0 +1,107 @@
+# Ref: https://github.com/fastapi/fastapi/discussions/6024#discussioncomment-8541913
+
+
+from typing import Annotated
+
+import pytest
+from fastapi import Depends, FastAPI, Security
+from fastapi.security import SecurityScopes
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(name="call_counts")
+def call_counts_fixture():
+ return {
+ "get_db_session": 0,
+ "get_current_user": 0,
+ "get_user_me": 0,
+ "get_user_items": 0,
+ }
+
+
+@pytest.fixture(name="app")
+def app_fixture(call_counts: dict[str, int]):
+ def get_db_session():
+ call_counts["get_db_session"] += 1
+ return f"db_session_{call_counts['get_db_session']}"
+
+ def get_current_user(
+ security_scopes: SecurityScopes,
+ db_session: Annotated[str, Depends(get_db_session)],
+ ):
+ call_counts["get_current_user"] += 1
+ return {
+ "user": f"user_{call_counts['get_current_user']}",
+ "scopes": security_scopes.scopes,
+ "db_session": db_session,
+ }
+
+ def get_user_me(
+ current_user: Annotated[dict, Security(get_current_user, scopes=["me"])],
+ ):
+ call_counts["get_user_me"] += 1
+ return {
+ "user_me": f"user_me_{call_counts['get_user_me']}",
+ "current_user": current_user,
+ }
+
+ def get_user_items(
+ user_me: Annotated[dict, Depends(get_user_me)],
+ ):
+ call_counts["get_user_items"] += 1
+ return {
+ "user_items": f"user_items_{call_counts['get_user_items']}",
+ "user_me": user_me,
+ }
+
+ app = FastAPI()
+
+ @app.get("/")
+ def path_operation(
+ user_me: Annotated[dict, Depends(get_user_me)],
+ user_items: Annotated[dict, Security(get_user_items, scopes=["items"])],
+ ):
+ return {
+ "user_me": user_me,
+ "user_items": user_items,
+ }
+
+ return app
+
+
+@pytest.fixture(name="client")
+def client_fixture(app: FastAPI):
+ return TestClient(app)
+
+
+def test_security_scopes_sub_dependency_caching(
+ client: TestClient, call_counts: dict[str, int]
+):
+ response = client.get("/")
+
+ assert response.status_code == 200
+ assert call_counts["get_db_session"] == 1
+ assert call_counts["get_current_user"] == 2
+ assert call_counts["get_user_me"] == 2
+ assert call_counts["get_user_items"] == 1
+ assert response.json() == {
+ "user_me": {
+ "user_me": "user_me_1",
+ "current_user": {
+ "user": "user_1",
+ "scopes": ["me"],
+ "db_session": "db_session_1",
+ },
+ },
+ "user_items": {
+ "user_items": "user_items_1",
+ "user_me": {
+ "user_me": "user_me_2",
+ "current_user": {
+ "user": "user_2",
+ "scopes": ["items", "me"],
+ "db_session": "db_session_1",
+ },
+ },
+ },
+ }
diff --git a/tests/test_serialize_response.py b/tests/test_serialize_response.py
index d823e5e04a..14f88dd931 100644
--- a/tests/test_serialize_response.py
+++ b/tests/test_serialize_response.py
@@ -1,4 +1,4 @@
-from typing import List, Optional
+from typing import Optional
from fastapi import FastAPI
from fastapi.testclient import TestClient
@@ -10,7 +10,7 @@ app = FastAPI()
class Item(BaseModel):
name: str
price: Optional[float] = None
- owner_ids: Optional[List[int]] = None
+ owner_ids: Optional[list[int]] = None
@app.get("/items/valid", response_model=Item)
@@ -23,7 +23,7 @@ def get_coerce():
return {"name": "coerce", "price": "1.0"}
-@app.get("/items/validlist", response_model=List[Item])
+@app.get("/items/validlist", response_model=list[Item])
def get_validlist():
return [
{"name": "foo"},
diff --git a/tests/test_serialize_response_dataclass.py b/tests/test_serialize_response_dataclass.py
index 1e3bf3b28b..ee695368b8 100644
--- a/tests/test_serialize_response_dataclass.py
+++ b/tests/test_serialize_response_dataclass.py
@@ -1,6 +1,6 @@
from dataclasses import dataclass
from datetime import datetime
-from typing import List, Optional
+from typing import Optional
from fastapi import FastAPI
from fastapi.testclient import TestClient
@@ -13,7 +13,7 @@ class Item:
name: str
date: datetime
price: Optional[float] = None
- owner_ids: Optional[List[int]] = None
+ owner_ids: Optional[list[int]] = None
@app.get("/items/valid", response_model=Item)
@@ -33,7 +33,7 @@ def get_coerce():
return {"name": "coerce", "date": datetime(2021, 7, 26).isoformat(), "price": "1.0"}
-@app.get("/items/validlist", response_model=List[Item])
+@app.get("/items/validlist", response_model=list[Item])
def get_validlist():
return [
{"name": "foo", "date": datetime(2021, 7, 26)},
@@ -47,7 +47,7 @@ def get_validlist():
]
-@app.get("/items/objectlist", response_model=List[Item])
+@app.get("/items/objectlist", response_model=list[Item])
def get_objectlist():
return [
Item(name="foo", date=datetime(2021, 7, 26)),
diff --git a/tests/test_serialize_response_model.py b/tests/test_serialize_response_model.py
index 3bb46b2e9b..79c90c9c29 100644
--- a/tests/test_serialize_response_model.py
+++ b/tests/test_serialize_response_model.py
@@ -1,4 +1,4 @@
-from typing import Dict, List, Optional
+from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel, Field
@@ -10,7 +10,7 @@ app = FastAPI()
class Item(BaseModel):
name: str = Field(alias="aliased_name")
price: Optional[float] = None
- owner_ids: Optional[List[int]] = None
+ owner_ids: Optional[list[int]] = None
@app.get("/items/valid", response_model=Item)
@@ -23,7 +23,7 @@ def get_coerce():
return Item(aliased_name="coerce", price="1.0")
-@app.get("/items/validlist", response_model=List[Item])
+@app.get("/items/validlist", response_model=list[Item])
def get_validlist():
return [
Item(aliased_name="foo"),
@@ -32,7 +32,7 @@ def get_validlist():
]
-@app.get("/items/validdict", response_model=Dict[str, Item])
+@app.get("/items/validdict", response_model=dict[str, Item])
def get_validdict():
return {
"k1": Item(aliased_name="foo"),
@@ -59,7 +59,7 @@ def get_coerce_exclude_unset():
@app.get(
"/items/validlist-exclude-unset",
- response_model=List[Item],
+ response_model=list[Item],
response_model_exclude_unset=True,
)
def get_validlist_exclude_unset():
@@ -72,7 +72,7 @@ def get_validlist_exclude_unset():
@app.get(
"/items/validdict-exclude-unset",
- response_model=Dict[str, Item],
+ response_model=dict[str, Item],
response_model_exclude_unset=True,
)
def get_validdict_exclude_unset():
diff --git a/tests/test_stringified_annotation_dependency.py b/tests/test_stringified_annotation_dependency.py
new file mode 100644
index 0000000000..ce88074957
--- /dev/null
+++ b/tests/test_stringified_annotation_dependency.py
@@ -0,0 +1,79 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Annotated
+
+import pytest
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+
+if TYPE_CHECKING: # pragma: no cover
+ from collections.abc import AsyncGenerator
+
+
+class DummyClient:
+ async def get_people(self) -> list:
+ return ["John Doe", "Jane Doe"]
+
+ async def close(self) -> None:
+ pass
+
+
+async def get_client() -> AsyncGenerator[DummyClient, None]:
+ client = DummyClient()
+ yield client
+ await client.close()
+
+
+Client = Annotated[DummyClient, Depends(get_client)]
+
+
+@pytest.fixture(name="client")
+def client_fixture() -> TestClient:
+ app = FastAPI()
+
+ @app.get("/")
+ async def get_people(client: Client) -> list:
+ return await client.get_people()
+
+ client = TestClient(app)
+ return client
+
+
+def test_get(client: TestClient):
+ response = client.get("/")
+ assert response.status_code == 200, response.text
+ assert response.json() == ["John Doe", "Jane Doe"]
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "get": {
+ "summary": "Get People",
+ "operationId": "get_people__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {},
+ "type": "array",
+ "title": "Response Get People Get",
+ }
+ }
+ },
+ }
+ },
+ }
+ }
+ },
+ }
+ )
diff --git a/tests/test_stringified_annotations_simple.py b/tests/test_stringified_annotations_simple.py
new file mode 100644
index 0000000000..6e037fb21f
--- /dev/null
+++ b/tests/test_stringified_annotations_simple.py
@@ -0,0 +1,27 @@
+from __future__ import annotations
+
+from typing import Annotated
+
+from fastapi import Depends, FastAPI, Request
+from fastapi.testclient import TestClient
+
+from .utils import needs_py310
+
+
+class Dep:
+ def __call__(self, request: Request):
+ return "test"
+
+
+@needs_py310
+def test_stringified_annotations():
+ app = FastAPI()
+
+ client = TestClient(app)
+
+ @app.get("/test/")
+ def call(test: Annotated[str, Depends(Dep())]):
+ return {"test": test}
+
+ response = client.get("/test")
+ assert response.status_code == 200
diff --git a/tests/test_sub_callbacks.py b/tests/test_sub_callbacks.py
index ed7f4efe8a..cc7e5f5c6a 100644
--- a/tests/test_sub_callbacks.py
+++ b/tests/test_sub_callbacks.py
@@ -1,6 +1,5 @@
from typing import Optional
-from dirty_equals import IsDict
from fastapi import APIRouter, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, HttpUrl
@@ -99,30 +98,18 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "title": "Callback Url",
- "anyOf": [
- {
- "type": "string",
- "format": "uri",
- "minLength": 1,
- "maxLength": 2083,
- },
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Callback Url",
- "maxLength": 2083,
- "minLength": 1,
- "type": "string",
- "format": "uri",
- }
- ),
+ "schema": {
+ "title": "Callback Url",
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uri",
+ "minLength": 1,
+ "maxLength": 2083,
+ },
+ {"type": "null"},
+ ],
+ },
"name": "callback_url",
"in": "query",
}
@@ -262,16 +249,10 @@ def test_openapi_schema():
"type": "object",
"properties": {
"id": {"title": "Id", "type": "string"},
- "title": IsDict(
- {
- "title": "Title",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Title", "type": "string"}
- ),
+ "title": {
+ "title": "Title",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
"customer": {"title": "Customer", "type": "string"},
"total": {"title": "Total", "type": "number"},
},
diff --git a/tests/test_top_level_security_scheme_in_openapi.py b/tests/test_top_level_security_scheme_in_openapi.py
index e2de31af53..a36c66d1ac 100644
--- a/tests/test_top_level_security_scheme_in_openapi.py
+++ b/tests/test_top_level_security_scheme_in_openapi.py
@@ -27,7 +27,7 @@ def test_get_root():
def test_get_root_no_token():
response = client.get("/")
- assert response.status_code == 403, response.text
+ assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
diff --git a/tests/test_tuples.py b/tests/test_tuples.py
index ca33d2580c..d3c89045b4 100644
--- a/tests/test_tuples.py
+++ b/tests/test_tuples.py
@@ -1,6 +1,3 @@
-from typing import List, Tuple
-
-from dirty_equals import IsDict
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -9,7 +6,7 @@ app = FastAPI()
class ItemGroup(BaseModel):
- items: List[Tuple[str, str]]
+ items: list[tuple[str, str]]
class Coordinate(BaseModel):
@@ -23,12 +20,12 @@ def post_model_with_tuple(item_group: ItemGroup):
@app.post("/tuple-of-models/")
-def post_tuple_of_models(square: Tuple[Coordinate, Coordinate]):
+def post_tuple_of_models(square: tuple[Coordinate, Coordinate]):
return square
@app.post("/tuple-form/")
-def hello(values: Tuple[int, int] = Form()):
+def hello(values: tuple[int, int] = Form()):
return values
@@ -127,31 +124,16 @@ def test_openapi_schema():
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "title": "Square",
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "prefixItems": [
- {"$ref": "#/components/schemas/Coordinate"},
- {"$ref": "#/components/schemas/Coordinate"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Square",
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "items": [
- {"$ref": "#/components/schemas/Coordinate"},
- {"$ref": "#/components/schemas/Coordinate"},
- ],
- }
- )
+ "schema": {
+ "title": "Square",
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "prefixItems": [
+ {"$ref": "#/components/schemas/Coordinate"},
+ {"$ref": "#/components/schemas/Coordinate"},
+ ],
+ }
}
},
"required": True,
@@ -214,28 +196,16 @@ def test_openapi_schema():
"required": ["values"],
"type": "object",
"properties": {
- "values": IsDict(
- {
- "title": "Values",
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "prefixItems": [
- {"type": "integer"},
- {"type": "integer"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Values",
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "items": [{"type": "integer"}, {"type": "integer"}],
- }
- )
+ "values": {
+ "title": "Values",
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "prefixItems": [
+ {"type": "integer"},
+ {"type": "integer"},
+ ],
+ }
},
},
"Coordinate": {
@@ -266,26 +236,15 @@ def test_openapi_schema():
"items": {
"title": "Items",
"type": "array",
- "items": IsDict(
- {
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "prefixItems": [
- {"type": "string"},
- {"type": "string"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "maxItems": 2,
- "minItems": 2,
- "type": "array",
- "items": [{"type": "string"}, {"type": "string"}],
- }
- ),
+ "items": {
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "prefixItems": [
+ {"type": "string"},
+ {"type": "string"},
+ ],
+ },
}
},
},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial001.py b/tests/test_tutorial/test_additional_responses/test_tutorial001.py
index 3afeaff840..1a18db75ce 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial001.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.additional_responses.tutorial001 import app
+from docs_src.additional_responses.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
index 588a3160a9..8208605956 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
@@ -1,21 +1,35 @@
+import importlib
import os
import shutil
-from dirty_equals import IsDict
+import pytest
from fastapi.testclient import TestClient
-from docs_src.additional_responses.tutorial002 import app
-
-client = TestClient(app)
+from tests.utils import needs_py310
-def test_path_operation():
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.additional_responses.{request.param}")
+
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
+
+
+def test_path_operation(client: TestClient):
response = client.get("/items/foo")
assert response.status_code == 200, response.text
assert response.json() == {"id": "foo", "value": "there goes my hero"}
-def test_path_operation_img():
+def test_path_operation_img(client: TestClient):
shutil.copy("./docs/en/docs/img/favicon.png", "./image.png")
response = client.get("/items/foo?img=1")
assert response.status_code == 200, response.text
@@ -24,7 +38,7 @@ def test_path_operation_img():
os.remove("./image.png")
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@@ -65,16 +79,10 @@ def test_openapi_schema():
},
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "boolean"}, {"type": "null"}],
- "title": "Img",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Img", "type": "boolean"}
- ),
+ "schema": {
+ "anyOf": [{"type": "boolean"}, {"type": "null"}],
+ "title": "Img",
+ },
"name": "img",
"in": "query",
},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial003.py b/tests/test_tutorial/test_additional_responses/test_tutorial003.py
index bd34d2938c..90dc4e371e 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial003.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial003.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.additional_responses.tutorial003 import app
+from docs_src.additional_responses.tutorial003_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
index 55b556d8e1..c6abf5e466 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
@@ -1,21 +1,35 @@
+import importlib
import os
import shutil
-from dirty_equals import IsDict
+import pytest
from fastapi.testclient import TestClient
-from docs_src.additional_responses.tutorial004 import app
-
-client = TestClient(app)
+from tests.utils import needs_py310
-def test_path_operation():
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial004_py39"),
+ pytest.param("tutorial004_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.additional_responses.{request.param}")
+
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
+
+
+def test_path_operation(client: TestClient):
response = client.get("/items/foo")
assert response.status_code == 200, response.text
assert response.json() == {"id": "foo", "value": "there goes my hero"}
-def test_path_operation_img():
+def test_path_operation_img(client: TestClient):
shutil.copy("./docs/en/docs/img/favicon.png", "./image.png")
response = client.get("/items/foo?img=1")
assert response.status_code == 200, response.text
@@ -24,7 +38,7 @@ def test_path_operation_img():
os.remove("./image.png")
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@@ -68,16 +82,10 @@ def test_openapi_schema():
},
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "boolean"}, {"type": "null"}],
- "title": "Img",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Img", "type": "boolean"}
- ),
+ "schema": {
+ "anyOf": [{"type": "boolean"}, {"type": "null"}],
+ "title": "Img",
+ },
"name": "img",
"in": "query",
},
diff --git a/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py b/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py
index b304f70153..bced1f6dfe 100644
--- a/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py
+++ b/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py
@@ -3,16 +3,15 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
+ "tutorial001_py39",
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ "tutorial001_an_py39",
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py
index 157fa5caf1..f17391956e 100644
--- a/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py
+++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.advanced_middleware.tutorial001 import app
+from docs_src.advanced_middleware.tutorial001_py39 import app
def test_middleware():
diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py
index 79be52f4db..bae915406e 100644
--- a/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py
+++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.advanced_middleware.tutorial002 import app
+from docs_src.advanced_middleware.tutorial002_py39 import app
def test_middleware():
diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py
index 04a922ff7b..66697997c8 100644
--- a/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py
+++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py
@@ -1,7 +1,7 @@
from fastapi.responses import PlainTextResponse
from fastapi.testclient import TestClient
-from docs_src.advanced_middleware.tutorial003 import app
+from docs_src.advanced_middleware.tutorial003_py39 import app
@app.get("/large")
diff --git a/tests/test_tutorial/test_async_tests/test_main.py b/tests/test_tutorial/test_async_tests/test_main_a.py
similarity index 58%
rename from tests/test_tutorial/test_async_tests/test_main.py
rename to tests/test_tutorial/test_async_tests/test_main_a.py
index 1f5d7186cc..f29acaa9a0 100644
--- a/tests/test_tutorial/test_async_tests/test_main.py
+++ b/tests/test_tutorial/test_async_tests/test_main_a.py
@@ -1,6 +1,6 @@
import pytest
-from docs_src.async_tests.test_main import test_root
+from docs_src.async_tests.app_a_py39.test_main import test_root
@pytest.mark.anyio
diff --git a/tests/test_tutorial/test_authentication_error_status_code/__init__.py b/tests/test_tutorial/test_authentication_error_status_code/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py b/tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py
new file mode 100644
index 0000000000..6f58116313
--- /dev/null
+++ b/tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py
@@ -0,0 +1,66 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial001_an_py39",
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.authentication_error_status_code.{request.param}"
+ )
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_get_me(client: TestClient):
+ response = client.get("/me", headers={"Authorization": "Bearer secrettoken"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "You are authenticated",
+ "token": "secrettoken",
+ }
+
+
+def test_get_me_no_credentials(client: TestClient):
+ response = client.get("/me")
+ assert response.status_code == 403
+ assert response.json() == {"detail": "Not authenticated"}
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/me": {
+ "get": {
+ "summary": "Read Me",
+ "operationId": "read_me_me_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "security": [{"HTTPBearer403": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "HTTPBearer403": {"type": "http", "scheme": "bearer"}
+ }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial001.py b/tests/test_tutorial/test_background_tasks/test_tutorial001.py
index 0602cd8aa4..c0ad27a6f5 100644
--- a/tests/test_tutorial/test_background_tasks/test_tutorial001.py
+++ b/tests/test_tutorial/test_background_tasks/test_tutorial001.py
@@ -3,7 +3,7 @@ from pathlib import Path
from fastapi.testclient import TestClient
-from docs_src.background_tasks.tutorial001 import app
+from docs_src.background_tasks.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002.py b/tests/test_tutorial/test_background_tasks/test_tutorial002.py
index d5ef51ee2b..288a1c244e 100644
--- a/tests/test_tutorial/test_background_tasks/test_tutorial002.py
+++ b/tests/test_tutorial/test_background_tasks/test_tutorial002.py
@@ -5,16 +5,15 @@ from pathlib import Path
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial002",
+ "tutorial002_py39",
pytest.param("tutorial002_py310", marks=needs_py310),
- "tutorial002_an",
- pytest.param("tutorial002_an_py39", marks=needs_py39),
+ "tutorial002_an_py39",
pytest.param("tutorial002_an_py310", marks=needs_py310),
],
)
diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py
index a070f850f7..00574b5b0f 100644
--- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py
+++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.behind_a_proxy.tutorial001 import app
+from docs_src.behind_a_proxy.tutorial001_py39 import app
client = TestClient(app, root_path="/api/v1")
diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py
index f13046e018..da4acb28cc 100644
--- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py
+++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.behind_a_proxy.tutorial001_01 import app
+from docs_src.behind_a_proxy.tutorial001_01_py39 import app
client = TestClient(
app,
diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py
index ce791e2157..1a49c0dfeb 100644
--- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py
+++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.behind_a_proxy.tutorial002 import app
+from docs_src.behind_a_proxy.tutorial002_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py
index ec17b41790..a164bb80b5 100644
--- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py
+++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py
@@ -1,7 +1,7 @@
-from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
-from docs_src.behind_a_proxy.tutorial003 import app
+from docs_src.behind_a_proxy.tutorial003_py39 import app
client = TestClient(app)
@@ -15,40 +15,34 @@ def test_main():
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "servers": [
- {"url": "/api/v1"},
- {
- "url": IsOneOf(
- "https://stag.example.com/",
- # TODO: remove when deprecating Pydantic v1
- "https://stag.example.com",
- ),
- "description": "Staging environment",
- },
- {
- "url": IsOneOf(
- "https://prod.example.com/",
- # TODO: remove when deprecating Pydantic v1
- "https://prod.example.com",
- ),
- "description": "Production environment",
- },
- ],
- "paths": {
- "/app": {
- "get": {
- "summary": "Read Main",
- "operationId": "read_main_app_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "servers": [
+ {"url": "/api/v1"},
+ {
+ "url": "https://stag.example.com",
+ "description": "Staging environment",
+ },
+ {
+ "url": "https://prod.example.com",
+ "description": "Production environment",
+ },
+ ],
+ "paths": {
+ "/app": {
+ "get": {
+ "summary": "Read Main",
+ "operationId": "read_main_app_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
}
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py
index 2f8eb46995..01bba9fedd 100644
--- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py
+++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py
@@ -1,7 +1,7 @@
-from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
-from docs_src.behind_a_proxy.tutorial004 import app
+from docs_src.behind_a_proxy.tutorial004_py39 import app
client = TestClient(app)
@@ -15,39 +15,33 @@ def test_main():
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "servers": [
- {
- "url": IsOneOf(
- "https://stag.example.com/",
- # TODO: remove when deprecating Pydantic v1
- "https://stag.example.com",
- ),
- "description": "Staging environment",
- },
- {
- "url": IsOneOf(
- "https://prod.example.com/",
- # TODO: remove when deprecating Pydantic v1
- "https://prod.example.com",
- ),
- "description": "Production environment",
- },
- ],
- "paths": {
- "/app": {
- "get": {
- "summary": "Read Main",
- "operationId": "read_main_app_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "servers": [
+ {
+ "url": "https://stag.example.com",
+ "description": "Staging environment",
+ },
+ {
+ "url": "https://prod.example.com",
+ "description": "Production environment",
+ },
+ ],
+ "paths": {
+ "/app": {
+ "get": {
+ "summary": "Read Main",
+ "operationId": "read_main_app_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
}
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py
index fe40fad7d0..f5e243b95a 100644
--- a/tests/test_tutorial/test_bigger_applications/test_main.py
+++ b/tests/test_tutorial/test_bigger_applications/test_main.py
@@ -1,18 +1,14 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "app_an.main",
- pytest.param("app_an_py39.main", marks=needs_py39),
- "app.main",
+ "app_py39.main",
+ "app_an_py39.main",
],
)
def get_client(request: pytest.FixtureRequest):
@@ -31,29 +27,16 @@ def test_users_token_jessica(client: TestClient):
def test_users_with_no_token(client: TestClient):
response = client.get("/users")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_users_foo_token_jessica(client: TestClient):
@@ -65,29 +48,16 @@ def test_users_foo_token_jessica(client: TestClient):
def test_users_foo_with_no_token(client: TestClient):
response = client.get("/users/foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_users_me_token_jessica(client: TestClient):
@@ -99,29 +69,16 @@ def test_users_me_token_jessica(client: TestClient):
def test_users_me_with_no_token(client: TestClient):
response = client.get("/users/me")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_users_token_monica_with_no_jessica(client: TestClient):
@@ -144,29 +101,16 @@ def test_items_token_jessica(client: TestClient):
def test_items_with_no_token_jessica(client: TestClient):
response = client.get("/items", headers={"X-Token": "fake-super-secret-token"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_items_plumbus_token_jessica(client: TestClient):
@@ -190,29 +134,16 @@ def test_items_plumbus_with_no_token(client: TestClient):
"/items/plumbus", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_items_with_invalid_token(client: TestClient):
@@ -230,57 +161,31 @@ def test_items_bar_with_invalid_token(client: TestClient):
def test_items_with_missing_x_token_header(client: TestClient):
response = client.get("/items?token=jessica")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "x-token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "x-token"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "x-token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_items_plumbus_with_missing_x_token_header(client: TestClient):
response = client.get("/items/plumbus?token=jessica")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "x-token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "x-token"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "x-token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_root_token_jessica(client: TestClient):
@@ -292,68 +197,37 @@ def test_root_token_jessica(client: TestClient):
def test_root_with_no_token(client: TestClient):
response = client.get("/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_put_no_header(client: TestClient):
response = client.put("/items/foo")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "token"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["header", "x-token"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["header", "x-token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "token"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["header", "x-token"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_put_invalid_header(client: TestClient):
diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py
index f8b5aee8d2..5a7cae1603 100644
--- a/tests/test_tutorial/test_body/test_tutorial001.py
+++ b/tests/test_tutorial/test_body/test_tutorial001.py
@@ -2,7 +2,6 @@ import importlib
from unittest.mock import patch
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -11,7 +10,7 @@ from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
+ "tutorial001_py39",
pytest.param("tutorial001_py310", marks=needs_py310),
],
)
@@ -74,124 +73,67 @@ def test_post_with_str_float_description_tax(client: TestClient):
def test_post_with_only_name(client: TestClient):
response = client.post("/items/", json={"name": "Foo"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "price"],
- "msg": "Field required",
- "input": {"name": "Foo"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "price"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "price"],
+ "msg": "Field required",
+ "input": {"name": "Foo"},
+ }
+ ]
+ }
def test_post_with_only_name_price(client: TestClient):
response = client.post("/items/", json={"name": "Foo", "price": "twenty"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "float_parsing",
- "loc": ["body", "price"],
- "msg": "Input should be a valid number, unable to parse string as a number",
- "input": "twenty",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "price"],
- "msg": "value is not a valid float",
- "type": "type_error.float",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "float_parsing",
+ "loc": ["body", "price"],
+ "msg": "Input should be a valid number, unable to parse string as a number",
+ "input": "twenty",
+ }
+ ]
+ }
def test_post_with_no_data(client: TestClient):
response = client.post("/items/", json={})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "name"],
- "msg": "Field required",
- "input": {},
- },
- {
- "type": "missing",
- "loc": ["body", "price"],
- "msg": "Field required",
- "input": {},
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "name"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "price"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "name"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "price"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
def test_post_with_none(client: TestClient):
response = client.post("/items/", json=None)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_post_broken_body(client: TestClient):
@@ -201,67 +143,32 @@ def test_post_broken_body(client: TestClient):
content="{some broken json}",
)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "json_invalid",
- "loc": ["body", 1],
- "msg": "JSON decode error",
- "input": {},
- "ctx": {
- "error": "Expecting property name enclosed in double quotes"
- },
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", 1],
- "msg": "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
- "type": "value_error.jsondecode",
- "ctx": {
- "msg": "Expecting property name enclosed in double quotes",
- "doc": "{some broken json}",
- "pos": 1,
- "lineno": 1,
- "colno": 2,
- },
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "json_invalid",
+ "loc": ["body", 1],
+ "msg": "JSON decode error",
+ "input": {},
+ "ctx": {"error": "Expecting property name enclosed in double quotes"},
+ }
+ ]
+ }
def test_post_form_for_json(client: TestClient):
response = client.post("/items/", data={"name": "Foo", "price": 50.5})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "model_attributes_type",
- "loc": ["body"],
- "msg": "Input should be a valid dictionary or object to extract fields from",
- "input": "name=Foo&price=50.5",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid dict",
- "type": "type_error.dict",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "model_attributes_type",
+ "loc": ["body"],
+ "msg": "Input should be a valid dictionary or object to extract fields from",
+ "input": "name=Foo&price=50.5",
+ }
+ ]
+ }
def test_explicit_content_type(client: TestClient):
@@ -302,84 +209,46 @@ def test_wrong_headers(client: TestClient):
"/items/", content=data, headers={"Content-Type": "text/plain"}
)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "model_attributes_type",
- "loc": ["body"],
- "msg": "Input should be a valid dictionary or object to extract fields from",
- "input": '{"name": "Foo", "price": 50.5}',
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid dict",
- "type": "type_error.dict",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "model_attributes_type",
+ "loc": ["body"],
+ "msg": "Input should be a valid dictionary or object to extract fields from",
+ "input": '{"name": "Foo", "price": 50.5}',
+ }
+ ]
+ }
response = client.post(
"/items/", content=data, headers={"Content-Type": "application/geo+json-seq"}
)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "model_attributes_type",
- "loc": ["body"],
- "msg": "Input should be a valid dictionary or object to extract fields from",
- "input": '{"name": "Foo", "price": 50.5}',
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid dict",
- "type": "type_error.dict",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "model_attributes_type",
+ "loc": ["body"],
+ "msg": "Input should be a valid dictionary or object to extract fields from",
+ "input": '{"name": "Foo", "price": 50.5}',
+ }
+ ]
+ }
+
response = client.post(
"/items/", content=data, headers={"Content-Type": "application/not-really-json"}
)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "model_attributes_type",
- "loc": ["body"],
- "msg": "Input should be a valid dictionary or object to extract fields from",
- "input": '{"name": "Foo", "price": 50.5}',
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body"],
- "msg": "value is not a valid dict",
- "type": "type_error.dict",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "model_attributes_type",
+ "loc": ["body"],
+ "msg": "Input should be a valid dictionary or object to extract fields from",
+ "input": '{"name": "Foo", "price": 50.5}',
+ }
+ ]
+ }
def test_other_exceptions(client: TestClient):
@@ -435,26 +304,14 @@ def test_openapi_schema(client: TestClient):
"properties": {
"name": {"title": "Name", "type": "string"},
"price": {"title": "Price", "type": "number"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_tutorial/test_body/test_tutorial002.py b/tests/test_tutorial/test_body/test_tutorial002.py
new file mode 100644
index 0000000000..b6d51d5235
--- /dev/null
+++ b/tests/test_tutorial/test_body/test_tutorial002.py
@@ -0,0 +1,161 @@
+import importlib
+from typing import Union
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+@pytest.mark.parametrize("price", ["50.5", 50.5])
+def test_post_with_tax(client: TestClient, price: Union[str, float]):
+ response = client.post(
+ "/items/",
+ json={"name": "Foo", "price": price, "description": "Some Foo", "tax": 0.3},
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "name": "Foo",
+ "price": 50.5,
+ "description": "Some Foo",
+ "tax": 0.3,
+ "price_with_tax": 50.8,
+ }
+
+
+@pytest.mark.parametrize("price", ["50.5", 50.5])
+def test_post_without_tax(client: TestClient, price: Union[str, float]):
+ response = client.post(
+ "/items/", json={"name": "Foo", "price": price, "description": "Some Foo"}
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "name": "Foo",
+ "price": 50.5,
+ "description": "Some Foo",
+ "tax": None,
+ }
+
+
+def test_post_with_no_data(client: TestClient):
+ response = client.post("/items/", json={})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "name"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "price"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body/test_tutorial003.py b/tests/test_tutorial/test_body/test_tutorial003.py
new file mode 100644
index 0000000000..227a125e78
--- /dev/null
+++ b/tests/test_tutorial/test_body/test_tutorial003.py
@@ -0,0 +1,171 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial003_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_put_all(client: TestClient):
+ response = client.put(
+ "/items/123",
+ json={"name": "Foo", "price": 50.1, "description": "Some Foo", "tax": 0.3},
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "item_id": 123,
+ "name": "Foo",
+ "price": 50.1,
+ "description": "Some Foo",
+ "tax": 0.3,
+ }
+
+
+def test_put_only_required(client: TestClient):
+ response = client.put(
+ "/items/123",
+ json={"name": "Foo", "price": 50.1},
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "item_id": 123,
+ "name": "Foo",
+ "price": 50.1,
+ "description": None,
+ "tax": None,
+ }
+
+
+def test_put_with_no_data(client: TestClient):
+ response = client.put("/items/123", json={})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "name"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "price"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "parameters": [
+ {
+ "in": "path",
+ "name": "item_id",
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "integer",
+ },
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body/test_tutorial004.py b/tests/test_tutorial/test_body/test_tutorial004.py
new file mode 100644
index 0000000000..10212843ee
--- /dev/null
+++ b/tests/test_tutorial/test_body/test_tutorial004.py
@@ -0,0 +1,182 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial004_py39"),
+ pytest.param("tutorial004_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_put_all(client: TestClient):
+ response = client.put(
+ "/items/123",
+ json={"name": "Foo", "price": 50.1, "description": "Some Foo", "tax": 0.3},
+ params={"q": "somequery"},
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "item_id": 123,
+ "name": "Foo",
+ "price": 50.1,
+ "description": "Some Foo",
+ "tax": 0.3,
+ "q": "somequery",
+ }
+
+
+def test_put_only_required(client: TestClient):
+ response = client.put(
+ "/items/123",
+ json={"name": "Foo", "price": 50.1},
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "item_id": 123,
+ "name": "Foo",
+ "price": 50.1,
+ "description": None,
+ "tax": None,
+ }
+
+
+def test_put_with_no_data(client: TestClient):
+ response = client.put("/items/123", json={})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "name"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "price"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "parameters": [
+ {
+ "in": "path",
+ "name": "item_id",
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "integer",
+ },
+ },
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001.py b/tests/test_tutorial/test_body_fields/test_tutorial001.py
index fb68f28689..0ecadbb660 100644
--- a/tests/test_tutorial/test_body_fields/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_fields/test_tutorial001.py
@@ -1,19 +1,17 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
+ "tutorial001_py39",
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ "tutorial001_an_py39",
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
@@ -60,31 +58,17 @@ def test_items_6(client: TestClient):
def test_invalid_price(client: TestClient):
response = client.put("/items/5", json={"item": {"name": "Foo", "price": -3.0}})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "greater_than",
- "loc": ["body", "item", "price"],
- "msg": "Input should be greater than 0",
- "input": -3.0,
- "ctx": {"gt": 0.0},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"limit_value": 0},
- "loc": ["body", "item", "price"],
- "msg": "ensure this value is greater than 0",
- "type": "value_error.number.not_gt",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "greater_than",
+ "loc": ["body", "item", "price"],
+ "msg": "Input should be greater than 0",
+ "input": -3.0,
+ "ctx": {"gt": 0.0},
+ }
+ ]
+ }
def test_openapi_schema(client: TestClient):
@@ -143,39 +127,23 @@ def test_openapi_schema(client: TestClient):
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "The description of the item",
- "anyOf": [
- {"maxLength": 300, "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "The description of the item",
- "maxLength": 300,
- "type": "string",
- }
- ),
+ "description": {
+ "title": "The description of the item",
+ "anyOf": [
+ {"maxLength": 300, "type": "string"},
+ {"type": "null"},
+ ],
+ },
"price": {
"title": "Price",
"exclusiveMinimum": 0.0,
"type": "number",
"description": "The price must be greater than zero",
},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"Body_update_item_items__item_id__put": {
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
index 1424055952..63c9c16d62 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
@@ -1,19 +1,17 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
+ "tutorial001_py39",
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ "tutorial001_an_py39",
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
@@ -54,29 +52,16 @@ def test_post_no_body(client: TestClient):
def test_post_id_foo(client: TestClient):
response = client.put("/items/foo", json=None)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foo",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo",
+ }
+ ]
+ }
def test_openapi_schema(client: TestClient):
@@ -120,16 +105,10 @@ def test_openapi_schema(client: TestClient):
},
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Q", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Q",
+ },
"name": "q",
"in": "query",
},
@@ -137,19 +116,13 @@ def test_openapi_schema(client: TestClient):
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "anyOf": [
- {"$ref": "#/components/schemas/Item"},
- {"type": "null"},
- ],
- "title": "Item",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"$ref": "#/components/schemas/Item"}
- )
+ "schema": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Item"},
+ {"type": "null"},
+ ],
+ "title": "Item",
+ }
}
}
},
@@ -164,27 +137,15 @@ def test_openapi_schema(client: TestClient):
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
"price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py
new file mode 100644
index 0000000000..e98d5860fe
--- /dev/null
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py
@@ -0,0 +1,361 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_post_all(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": "Some Foo",
+ "tax": 0.1,
+ },
+ "user": {"username": "johndoe", "full_name": "John Doe"},
+ },
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "item_id": 5,
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": "Some Foo",
+ "tax": 0.1,
+ },
+ "user": {"username": "johndoe", "full_name": "John Doe"},
+ }
+
+
+def test_post_required(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "item": {"name": "Foo", "price": 50.5},
+ "user": {"username": "johndoe"},
+ },
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "item_id": 5,
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": None,
+ "tax": None,
+ },
+ "user": {"username": "johndoe", "full_name": None},
+ }
+
+
+def test_post_no_body(client: TestClient):
+ response = client.put("/items/5", json=None)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": [
+ "body",
+ "item",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "input": None,
+ "loc": [
+ "body",
+ "user",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+def test_post_no_item(client: TestClient):
+ response = client.put("/items/5", json={"user": {"username": "johndoe"}})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": [
+ "body",
+ "item",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+def test_post_no_user(client: TestClient):
+ response = client.put("/items/5", json={"item": {"name": "Foo", "price": 50.5}})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": [
+ "body",
+ "user",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+def test_post_missing_required_field_in_item(client: TestClient):
+ response = client.put(
+ "/items/5", json={"item": {"name": "Foo"}, "user": {"username": "johndoe"}}
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "input": {"name": "Foo"},
+ "loc": [
+ "body",
+ "item",
+ "price",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+def test_post_missing_required_field_in_user(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={"item": {"name": "Foo", "price": 50.5}, "user": {"ful_name": "John Doe"}},
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "input": {"ful_name": "John Doe"},
+ "loc": [
+ "body",
+ "user",
+ "username",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+def test_post_id_foo(client: TestClient):
+ response = client.put(
+ "/items/foo",
+ json={
+ "item": {"name": "Foo", "price": 50.5},
+ "user": {"username": "johndoe"},
+ },
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo",
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "info": {
+ "title": "FastAPI",
+ "version": "0.1.0",
+ },
+ "openapi": "3.1.0",
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "item_id",
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "integer",
+ },
+ },
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_update_item_items__item_id__put",
+ },
+ },
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Update Item",
+ },
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_update_item_items__item_id__put": {
+ "properties": {
+ "item": {
+ "$ref": "#/components/schemas/Item",
+ },
+ "user": {
+ "$ref": "#/components/schemas/User",
+ },
+ },
+ "required": [
+ "item",
+ "user",
+ ],
+ "title": "Body_update_item_items__item_id__put",
+ "type": "object",
+ },
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "price": {"title": "Price", "type": "number"},
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ },
+ "required": [
+ "name",
+ "price",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "User": {
+ "properties": {
+ "username": {
+ "title": "Username",
+ "type": "string",
+ },
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ },
+ "required": [
+ "username",
+ ],
+ "title": "User",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
index d18ceae486..76b7ff7099 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
@@ -1,19 +1,17 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial003",
+ "tutorial003_py39",
pytest.param("tutorial003_py310", marks=needs_py310),
- "tutorial003_an",
- pytest.param("tutorial003_an_py39", marks=needs_py39),
+ "tutorial003_an_py39",
pytest.param("tutorial003_an_py310", marks=needs_py310),
],
)
@@ -50,101 +48,55 @@ def test_post_body_valid(client: TestClient):
def test_post_body_no_data(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "user"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "importance"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "item"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "user"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "importance"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_post_body_empty_list(client: TestClient):
response = client.put("/items/5", json=[])
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "user"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "importance"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "item"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "user"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "importance"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_openapi_schema(client: TestClient):
@@ -203,27 +155,15 @@ def test_openapi_schema(client: TestClient):
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
"price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"User": {
@@ -232,16 +172,10 @@ def test_openapi_schema(client: TestClient):
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
},
},
"Body_update_item_items__item_id__put": {
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py
new file mode 100644
index 0000000000..979c054cd0
--- /dev/null
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py
@@ -0,0 +1,290 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial004_py39"),
+ pytest.param("tutorial004_py310", marks=needs_py310),
+ pytest.param("tutorial004_an_py39"),
+ pytest.param("tutorial004_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_put_all(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "importance": 2,
+ "item": {"name": "Foo", "price": 50.5},
+ "user": {"username": "Dave"},
+ },
+ params={"q": "somequery"},
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "item_id": 5,
+ "importance": 2,
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": None,
+ "tax": None,
+ },
+ "user": {"username": "Dave", "full_name": None},
+ "q": "somequery",
+ }
+
+
+def test_put_only_required(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "importance": 2,
+ "item": {"name": "Foo", "price": 50.5},
+ "user": {"username": "Dave"},
+ },
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "item_id": 5,
+ "importance": 2,
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": None,
+ "tax": None,
+ },
+ "user": {"username": "Dave", "full_name": None},
+ }
+
+
+def test_put_missing_body(client: TestClient):
+ response = client.put("/items/5")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": [
+ "body",
+ "item",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "input": None,
+ "loc": [
+ "body",
+ "user",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "input": None,
+ "loc": [
+ "body",
+ "importance",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+def test_put_empty_body(client: TestClient):
+ response = client.put("/items/5", json={})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "item"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "user"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "importance"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
+
+
+def test_put_invalid_importance(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "importance": 0,
+ "item": {"name": "Foo", "price": 50.5},
+ "user": {"username": "Dave"},
+ },
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "importance"],
+ "msg": "Input should be greater than 0",
+ "type": "greater_than",
+ "input": 0,
+ "ctx": {"gt": 0},
+ },
+ ],
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "integer"},
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ },
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "price": {"title": "Price", "type": "number"},
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ },
+ },
+ "User": {
+ "title": "User",
+ "required": ["username"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ },
+ },
+ "Body_update_item_items__item_id__put": {
+ "title": "Body_update_item_items__item_id__put",
+ "required": ["item", "user", "importance"],
+ "type": "object",
+ "properties": {
+ "item": {"$ref": "#/components/schemas/Item"},
+ "user": {"$ref": "#/components/schemas/User"},
+ "importance": {
+ "title": "Importance",
+ "type": "integer",
+ "exclusiveMinimum": 0.0,
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py
new file mode 100644
index 0000000000..d47aa1b4f9
--- /dev/null
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py
@@ -0,0 +1,272 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial005_py39"),
+ pytest.param("tutorial005_py310", marks=needs_py310),
+ pytest.param("tutorial005_an_py39"),
+ pytest.param("tutorial005_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_post_all(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": "Some Foo",
+ "tax": 0.1,
+ },
+ },
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "item_id": 5,
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": "Some Foo",
+ "tax": 0.1,
+ },
+ }
+
+
+def test_post_required(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "item": {"name": "Foo", "price": 50.5},
+ },
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "item_id": 5,
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": None,
+ "tax": None,
+ },
+ }
+
+
+def test_post_no_body(client: TestClient):
+ response = client.put("/items/5", json=None)
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": [
+ "body",
+ "item",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+def test_post_like_not_embeded(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "name": "Foo",
+ "price": 50.5,
+ },
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "input": None,
+ "loc": [
+ "body",
+ "item",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+def test_post_missing_required_field_in_item(client: TestClient):
+ response = client.put(
+ "/items/5", json={"item": {"name": "Foo"}, "user": {"username": "johndoe"}}
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "input": {"name": "Foo"},
+ "loc": [
+ "body",
+ "item",
+ "price",
+ ],
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ],
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "info": {
+ "title": "FastAPI",
+ "version": "0.1.0",
+ },
+ "openapi": "3.1.0",
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "item_id",
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "integer",
+ },
+ },
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_update_item_items__item_id__put",
+ },
+ },
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Update Item",
+ },
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_update_item_items__item_id__put": {
+ "properties": {
+ "item": {
+ "$ref": "#/components/schemas/Item",
+ },
+ },
+ "required": ["item"],
+ "title": "Body_update_item_items__item_id__put",
+ "type": "object",
+ },
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "price": {"title": "Price", "type": "number"},
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ },
+ "required": [
+ "name",
+ "price",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py
new file mode 100644
index 0000000000..d452929c38
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py
@@ -0,0 +1,251 @@
+import importlib
+
+import pytest
+from dirty_equals import IsList
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+UNTYPED_LIST_SCHEMA = {"type": "array", "items": {}}
+
+LIST_OF_STR_SCHEMA = {"type": "array", "items": {"type": "string"}}
+
+SET_OF_STR_SCHEMA = {"type": "array", "items": {"type": "string"}, "uniqueItems": True}
+
+
+@pytest.fixture(
+ name="mod_name",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial003_py310", marks=needs_py310),
+ ],
+)
+def get_mod_name(request: pytest.FixtureRequest):
+ return request.param
+
+
+@pytest.fixture(name="client")
+def get_client(mod_name: str):
+ mod = importlib.import_module(f"docs_src.body_nested_models.{mod_name}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_put_all(client: TestClient, mod_name: str):
+ if mod_name.startswith("tutorial003"):
+ tags_expected = IsList("foo", "bar", check_order=False)
+ else:
+ tags_expected = ["foo", "bar", "foo"]
+
+ response = client.put(
+ "/items/123",
+ json={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ "tags": ["foo", "bar", "foo"],
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "item_id": 123,
+ "item": {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ "tags": tags_expected,
+ },
+ }
+
+
+def test_put_only_required(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={"name": "Foo", "price": 35.4},
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "item_id": 5,
+ "item": {
+ "name": "Foo",
+ "description": None,
+ "price": 35.4,
+ "tax": None,
+ "tags": [],
+ },
+ }
+
+
+def test_put_empty_body(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={},
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "name"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "loc": ["body", "price"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_put_missing_required(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={"description": "A very nice Item"},
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "name"],
+ "input": {"description": "A very nice Item"},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "loc": ["body", "price"],
+ "input": {"description": "A very nice Item"},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient, mod_name: str):
+ tags_schema = {"default": [], "title": "Tags"}
+ if mod_name.startswith("tutorial001"):
+ tags_schema.update(UNTYPED_LIST_SCHEMA)
+ elif mod_name.startswith("tutorial002"):
+ tags_schema.update(LIST_OF_STR_SCHEMA)
+ elif mod_name.startswith("tutorial003"):
+ tags_schema.update(SET_OF_STR_SCHEMA)
+
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "parameters": [
+ {
+ "in": "path",
+ "name": "item_id",
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "integer",
+ },
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "properties": {
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "price": {
+ "title": "Price",
+ "type": "number",
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ "tags": tags_schema,
+ },
+ "required": [
+ "name",
+ "price",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial004.py b/tests/test_tutorial/test_body_nested_models/test_tutorial004.py
new file mode 100644
index 0000000000..ff9596943d
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial004.py
@@ -0,0 +1,275 @@
+import importlib
+
+import pytest
+from dirty_equals import IsList
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial004_py39"),
+ pytest.param("tutorial004_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_put_all(client: TestClient):
+ response = client.put(
+ "/items/123",
+ json={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ "tags": ["foo", "bar", "foo"],
+ "image": {"url": "http://example.com/image.png", "name": "example image"},
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "item_id": 123,
+ "item": {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ "tags": IsList("foo", "bar", check_order=False),
+ "image": {"url": "http://example.com/image.png", "name": "example image"},
+ },
+ }
+
+
+def test_put_only_required(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={"name": "Foo", "price": 35.4},
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "item_id": 5,
+ "item": {
+ "name": "Foo",
+ "description": None,
+ "price": 35.4,
+ "tax": None,
+ "tags": [],
+ "image": None,
+ },
+ }
+
+
+def test_put_empty_body(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={},
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "name"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "loc": ["body", "price"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_put_missing_required_in_item(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={"description": "A very nice Item"},
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "name"],
+ "input": {"description": "A very nice Item"},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "loc": ["body", "price"],
+ "input": {"description": "A very nice Item"},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_put_missing_required_in_image(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "name": "Foo",
+ "price": 35.4,
+ "image": {"url": "http://example.com/image.png"},
+ },
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "image", "name"],
+ "input": {"url": "http://example.com/image.png"},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "parameters": [
+ {
+ "in": "path",
+ "name": "item_id",
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "integer",
+ },
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Image": {
+ "properties": {
+ "url": {
+ "title": "Url",
+ "type": "string",
+ },
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ },
+ "required": ["url", "name"],
+ "title": "Image",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "price": {
+ "title": "Price",
+ "type": "number",
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ "tags": {
+ "title": "Tags",
+ "default": [],
+ "type": "array",
+ "items": {"type": "string"},
+ "uniqueItems": True,
+ },
+ "image": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Image"},
+ {"type": "null"},
+ ],
+ },
+ },
+ "required": [
+ "name",
+ "price",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial005.py b/tests/test_tutorial/test_body_nested_models/test_tutorial005.py
new file mode 100644
index 0000000000..9a07a904e6
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial005.py
@@ -0,0 +1,301 @@
+import importlib
+
+import pytest
+from dirty_equals import IsList
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial005_py39"),
+ pytest.param("tutorial005_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_put_all(client: TestClient):
+ response = client.put(
+ "/items/123",
+ json={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ "tags": ["foo", "bar", "foo"],
+ "image": {"url": "http://example.com/image.png", "name": "example image"},
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "item_id": 123,
+ "item": {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ "tags": IsList("foo", "bar", check_order=False),
+ "image": {"url": "http://example.com/image.png", "name": "example image"},
+ },
+ }
+
+
+def test_put_only_required(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={"name": "Foo", "price": 35.4},
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "item_id": 5,
+ "item": {
+ "name": "Foo",
+ "description": None,
+ "price": 35.4,
+ "tax": None,
+ "tags": [],
+ "image": None,
+ },
+ }
+
+
+def test_put_empty_body(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={},
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "name"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "loc": ["body", "price"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_put_missing_required_in_item(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={"description": "A very nice Item"},
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "name"],
+ "input": {"description": "A very nice Item"},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "loc": ["body", "price"],
+ "input": {"description": "A very nice Item"},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_put_missing_required_in_image(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "name": "Foo",
+ "price": 35.4,
+ "image": {"url": "http://example.com/image.png"},
+ },
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "image", "name"],
+ "input": {"url": "http://example.com/image.png"},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_put_wrong_url(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "name": "Foo",
+ "price": 35.4,
+ "image": {"url": "not a valid url", "name": "example image"},
+ },
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "image", "url"],
+ "input": "not a valid url",
+ "msg": "Input should be a valid URL, relative URL without a base",
+ "type": "url_parsing",
+ "ctx": {"error": "relative URL without a base"},
+ },
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "parameters": [
+ {
+ "in": "path",
+ "name": "item_id",
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "integer",
+ },
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Image": {
+ "properties": {
+ "url": {
+ "title": "Url",
+ "type": "string",
+ "format": "uri",
+ "maxLength": 2083,
+ "minLength": 1,
+ },
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ },
+ "required": ["url", "name"],
+ "title": "Image",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "price": {
+ "title": "Price",
+ "type": "number",
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ "tags": {
+ "title": "Tags",
+ "default": [],
+ "type": "array",
+ "items": {"type": "string"},
+ "uniqueItems": True,
+ },
+ "image": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Image"},
+ {"type": "null"},
+ ],
+ },
+ },
+ "required": [
+ "name",
+ "price",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial006.py b/tests/test_tutorial/test_body_nested_models/test_tutorial006.py
new file mode 100644
index 0000000000..088177cb95
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial006.py
@@ -0,0 +1,269 @@
+import importlib
+
+import pytest
+from dirty_equals import IsList
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial006_py39"),
+ pytest.param("tutorial006_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_put_all(client: TestClient):
+ response = client.put(
+ "/items/123",
+ json={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ "tags": ["foo", "bar", "foo"],
+ "images": [
+ {"url": "http://example.com/image.png", "name": "example image"}
+ ],
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "item_id": 123,
+ "item": {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ "tags": IsList("foo", "bar", check_order=False),
+ "images": [
+ {"url": "http://example.com/image.png", "name": "example image"}
+ ],
+ },
+ }
+
+
+def test_put_only_required(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={"name": "Foo", "price": 35.4},
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "item_id": 5,
+ "item": {
+ "name": "Foo",
+ "description": None,
+ "price": 35.4,
+ "tax": None,
+ "tags": [],
+ "images": None,
+ },
+ }
+
+
+def test_put_empty_body(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={},
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "name"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "loc": ["body", "price"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_put_images_not_list(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "name": "Foo",
+ "price": 35.4,
+ "images": {"url": "http://example.com/image.png", "name": "example image"},
+ },
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "images"],
+ "input": {
+ "url": "http://example.com/image.png",
+ "name": "example image",
+ },
+ "msg": "Input should be a valid list",
+ "type": "list_type",
+ },
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "parameters": [
+ {
+ "in": "path",
+ "name": "item_id",
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "integer",
+ },
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Image": {
+ "properties": {
+ "url": {
+ "title": "Url",
+ "type": "string",
+ "format": "uri",
+ "maxLength": 2083,
+ "minLength": 1,
+ },
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ },
+ "required": ["url", "name"],
+ "title": "Image",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "price": {
+ "title": "Price",
+ "type": "number",
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ "tags": {
+ "title": "Tags",
+ "default": [],
+ "type": "array",
+ "items": {"type": "string"},
+ "uniqueItems": True,
+ },
+ "images": {
+ "anyOf": [
+ {
+ "items": {
+ "$ref": "#/components/schemas/Image",
+ },
+ "type": "array",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ "title": "Images",
+ },
+ },
+ "required": [
+ "name",
+ "price",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial007.py b/tests/test_tutorial/test_body_nested_models/test_tutorial007.py
new file mode 100644
index 0000000000..a302819505
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial007.py
@@ -0,0 +1,344 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial007_py39"),
+ pytest.param("tutorial007_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_post_all(client: TestClient):
+ data = {
+ "name": "Special Offer",
+ "description": "This is a special offer",
+ "price": 38.6,
+ "items": [
+ {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ "tags": ["foo"],
+ "images": [
+ {
+ "url": "http://example.com/image.png",
+ "name": "example image",
+ }
+ ],
+ }
+ ],
+ }
+
+ response = client.post(
+ "/offers/",
+ json=data,
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == data
+
+
+def test_put_only_required(client: TestClient):
+ response = client.post(
+ "/offers/",
+ json={
+ "name": "Special Offer",
+ "price": 38.6,
+ "items": [
+ {
+ "name": "Foo",
+ "price": 35.4,
+ "images": [
+ {
+ "url": "http://example.com/image.png",
+ "name": "example image",
+ }
+ ],
+ }
+ ],
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Special Offer",
+ "description": None,
+ "price": 38.6,
+ "items": [
+ {
+ "name": "Foo",
+ "description": None,
+ "price": 35.4,
+ "tax": None,
+ "tags": [],
+ "images": [
+ {
+ "url": "http://example.com/image.png",
+ "name": "example image",
+ }
+ ],
+ }
+ ],
+ }
+
+
+def test_put_empty_body(client: TestClient):
+ response = client.post(
+ "/offers/",
+ json={},
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "name"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "loc": ["body", "price"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "loc": ["body", "items"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_put_missing_required_in_items(client: TestClient):
+ response = client.post(
+ "/offers/",
+ json={
+ "name": "Special Offer",
+ "price": 38.6,
+ "items": [{}],
+ },
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "items", 0, "name"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "loc": ["body", "items", 0, "price"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_put_missing_required_in_images(client: TestClient):
+ response = client.post(
+ "/offers/",
+ json={
+ "name": "Special Offer",
+ "price": 38.6,
+ "items": [
+ {"name": "Foo", "price": 35.4, "images": [{}]},
+ ],
+ },
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "items", 0, "images", 0, "url"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ {
+ "loc": ["body", "items", 0, "images", 0, "name"],
+ "input": {},
+ "msg": "Field required",
+ "type": "missing",
+ },
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/offers/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create Offer",
+ "operationId": "create_offer_offers__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Offer",
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Image": {
+ "properties": {
+ "url": {
+ "title": "Url",
+ "type": "string",
+ "format": "uri",
+ "maxLength": 2083,
+ "minLength": 1,
+ },
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ },
+ "required": ["url", "name"],
+ "title": "Image",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "price": {
+ "title": "Price",
+ "type": "number",
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ "tags": {
+ "title": "Tags",
+ "default": [],
+ "type": "array",
+ "items": {"type": "string"},
+ "uniqueItems": True,
+ },
+ "images": {
+ "anyOf": [
+ {
+ "items": {
+ "$ref": "#/components/schemas/Image",
+ },
+ "type": "array",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ "title": "Images",
+ },
+ },
+ "required": [
+ "name",
+ "price",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "Offer": {
+ "properties": {
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "price": {
+ "title": "Price",
+ "type": "number",
+ },
+ "items": {
+ "title": "Items",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "required": ["name", "price", "items"],
+ "title": "Offer",
+ "type": "object",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial008.py b/tests/test_tutorial/test_body_nested_models/test_tutorial008.py
new file mode 100644
index 0000000000..32eb8ee75c
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial008.py
@@ -0,0 +1,157 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial008_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_post_body(client: TestClient):
+ data = [
+ {"url": "http://example.com/", "name": "Example"},
+ {"url": "http://fastapi.tiangolo.com/", "name": "FastAPI"},
+ ]
+ response = client.post("/images/multiple", json=data)
+ assert response.status_code == 200, response.text
+ assert response.json() == data
+
+
+def test_post_invalid_list_item(client: TestClient):
+ data = [{"url": "not a valid url", "name": "Example"}]
+ response = client.post("/images/multiple", json=data)
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", 0, "url"],
+ "input": "not a valid url",
+ "msg": "Input should be a valid URL, relative URL without a base",
+ "type": "url_parsing",
+ "ctx": {"error": "relative URL without a base"},
+ },
+ ]
+ }
+
+
+def test_post_not_a_list(client: TestClient):
+ data = {"url": "http://example.com/", "name": "Example"}
+ response = client.post("/images/multiple", json=data)
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body"],
+ "input": {
+ "name": "Example",
+ "url": "http://example.com/",
+ },
+ "msg": "Input should be a valid list",
+ "type": "list_type",
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/images/multiple/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create Multiple Images",
+ "operationId": "create_multiple_images_images_multiple__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Images",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Image"},
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Image": {
+ "properties": {
+ "url": {
+ "title": "Url",
+ "type": "string",
+ "format": "uri",
+ "maxLength": 2083,
+ "minLength": 1,
+ },
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ },
+ "required": ["url", "name"],
+ "title": "Image",
+ "type": "object",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
index 38ba3c8875..f2e56d40fb 100644
--- a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
@@ -1,17 +1,13 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial009",
- pytest.param("tutorial009_py39", marks=needs_py39),
+ "tutorial009_py39",
],
)
def get_client(request: pytest.FixtureRequest):
@@ -32,29 +28,16 @@ def test_post_invalid_body(client: TestClient):
data = {"foo": 2.2, "3": 3.3}
response = client.post("/index-weights/", json=data)
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["body", "foo", "[key]"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foo",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "__key__"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["body", "foo", "[key]"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo",
+ }
+ ]
+ }
def test_openapi_schema(client: TestClient):
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py
index f874dc9bd6..0401eb7d0d 100644
--- a/tests/test_tutorial/test_body_updates/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py
@@ -3,15 +3,14 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
+ "tutorial001_py39",
pytest.param("tutorial001_py310", marks=needs_py310),
- pytest.param("tutorial001_py39", marks=needs_py39),
],
)
def get_client(request: pytest.FixtureRequest):
@@ -46,7 +45,6 @@ def test_put(client: TestClient):
}
-@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
@@ -186,137 +184,3 @@ def test_openapi_schema(client: TestClient):
}
},
}
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_openapi_schema_pv1(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Item",
- "operationId": "read_item_items__item_id__get",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "string"},
- "name": "item_id",
- "in": "path",
- }
- ],
- },
- "put": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "string"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- "required": True,
- },
- },
- }
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": {"title": "Description", "type": "string"},
- "price": {"title": "Price", "type": "number"},
- "tax": {"title": "Tax", "type": "number", "default": 10.5},
- "tags": {
- "title": "Tags",
- "type": "array",
- "items": {"type": "string"},
- "default": [],
- },
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial002.py b/tests/test_tutorial/test_body_updates/test_tutorial002.py
new file mode 100644
index 0000000000..466e6af8fd
--- /dev/null
+++ b/tests/test_tutorial/test_body_updates/test_tutorial002.py
@@ -0,0 +1,207 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body_updates.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_get(client: TestClient):
+ response = client.get("/items/baz")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Baz",
+ "description": None,
+ "price": 50.2,
+ "tax": 10.5,
+ "tags": [],
+ }
+
+
+def test_patch_all(client: TestClient):
+ response = client.patch(
+ "/items/foo",
+ json={
+ "name": "Fooz",
+ "description": "Item description",
+ "price": 3,
+ "tax": 10.5,
+ "tags": ["tag1", "tag2"],
+ },
+ )
+ assert response.json() == {
+ "name": "Fooz",
+ "description": "Item description",
+ "price": 3,
+ "tax": 10.5,
+ "tags": ["tag1", "tag2"],
+ }
+
+
+def test_patch_name(client: TestClient):
+ response = client.patch(
+ "/items/bar",
+ json={"name": "Barz"},
+ )
+ assert response.json() == {
+ "name": "Barz",
+ "description": "The bartenders",
+ "price": 62,
+ "tax": 20.2,
+ "tags": [],
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ },
+ "patch": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__patch",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ },
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "type": "object",
+ "title": "Item",
+ "properties": {
+ "name": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Name",
+ },
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ "price": {
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ "title": "Price",
+ },
+ "tax": {"title": "Tax", "type": "number", "default": 10.5},
+ "tags": {
+ "title": "Tags",
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py
index b098f259c2..ddc282d85e 100644
--- a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py
+++ b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py
@@ -2,19 +2,16 @@ import importlib
from fastapi.testclient import TestClient
-from ...utils import needs_pydanticv2
-
def get_client() -> TestClient:
- from docs_src.conditional_openapi import tutorial001
+ from docs_src.conditional_openapi import tutorial001_py39
- importlib.reload(tutorial001)
+ importlib.reload(tutorial001_py39)
- client = TestClient(tutorial001.app)
+ client = TestClient(tutorial001_py39.app)
return client
-@needs_pydanticv2
def test_disable_openapi(monkeypatch):
monkeypatch.setenv("OPENAPI_URL", "")
# Load the client after setting the env var
@@ -27,7 +24,6 @@ def test_disable_openapi(monkeypatch):
assert response.status_code == 404, response.text
-@needs_pydanticv2
def test_root():
client = get_client()
response = client.get("/")
@@ -35,7 +31,6 @@ def test_root():
assert response.json() == {"message": "Hello World"}
-@needs_pydanticv2
def test_default_openapi():
client = get_client()
response = client.get("/docs")
diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py
index a04dba2197..1fa9419a76 100644
--- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py
+++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.configure_swagger_ui.tutorial001 import app
+from docs_src.configure_swagger_ui.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py
index ea56b6f21c..c218cc858c 100644
--- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py
+++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.configure_swagger_ui.tutorial002 import app
+from docs_src.configure_swagger_ui.tutorial002_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py
index 926bbb14f0..8b73685499 100644
--- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py
+++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.configure_swagger_ui.tutorial003 import app
+from docs_src.configure_swagger_ui.tutorial003_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py
index 60643185a4..ac8e7bdae1 100644
--- a/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py
+++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py
@@ -1,20 +1,18 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from tests.utils import needs_py39, needs_py310
+from tests.utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
+ "tutorial001_py39",
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ "tutorial001_an_py39",
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
@@ -55,30 +53,16 @@ def test_cookie_param_model_invalid(client: TestClient):
response = client.get("/items/")
assert response.status_code == 422
assert response.json() == snapshot(
- IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["cookie", "session_id"],
- "msg": "Field required",
- "input": {},
- }
- ]
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "type": "value_error.missing",
- "loc": ["cookie", "session_id"],
- "msg": "field required",
- }
- ]
- }
- )
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["cookie", "session_id"],
+ "msg": "Field required",
+ "input": {},
+ }
+ ]
+ }
)
@@ -116,37 +100,19 @@ def test_openapi_schema(client: TestClient):
"name": "fatebook_tracker",
"in": "cookie",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Fatebook Tracker",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Fatebook Tracker",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Fatebook Tracker",
+ },
},
{
"name": "googall_tracker",
"in": "cookie",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Googall Tracker",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Googall Tracker",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Googall Tracker",
+ },
},
],
"responses": {
diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py
index cef6f66309..d7c3d15f1b 100644
--- a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py
+++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py
@@ -1,32 +1,19 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from tests.utils import (
- needs_py39,
- needs_py310,
- needs_pydanticv1,
- needs_pydanticv2,
- pydantic_snapshot,
-)
+from tests.utils import needs_py310
@pytest.fixture(
name="client",
params=[
- pytest.param("tutorial002", marks=needs_pydanticv2),
- pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]),
- pytest.param("tutorial002_an", marks=needs_pydanticv2),
- pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]),
- pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]),
- pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]),
- pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]),
- pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]),
- pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]),
- pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]),
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=[needs_py310]),
+ pytest.param("tutorial002_an_py39"),
+ pytest.param("tutorial002_an_py310", marks=[needs_py310]),
],
)
def get_client(request: pytest.FixtureRequest):
@@ -65,31 +52,16 @@ def test_cookie_param_model_defaults(client: TestClient):
def test_cookie_param_model_invalid(client: TestClient):
response = client.get("/items/")
assert response.status_code == 422
- assert response.json() == pydantic_snapshot(
- v2=snapshot(
+ assert response.json() == {
+ "detail": [
{
- "detail": [
- {
- "type": "missing",
- "loc": ["cookie", "session_id"],
- "msg": "Field required",
- "input": {},
- }
- ]
+ "type": "missing",
+ "loc": ["cookie", "session_id"],
+ "msg": "Field required",
+ "input": {},
}
- ),
- v1=snapshot(
- {
- "detail": [
- {
- "type": "value_error.missing",
- "loc": ["cookie", "session_id"],
- "msg": "field required",
- }
- ]
- }
- ),
- )
+ ]
+ }
def test_cookie_param_model_extra(client: TestClient):
@@ -99,30 +71,16 @@ def test_cookie_param_model_extra(client: TestClient):
response = c.get("/items/")
assert response.status_code == 422
assert response.json() == snapshot(
- IsDict(
- {
- "detail": [
- {
- "type": "extra_forbidden",
- "loc": ["cookie", "extra"],
- "msg": "Extra inputs are not permitted",
- "input": "track-me-here-too",
- }
- ]
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "type": "value_error.extra",
- "loc": ["cookie", "extra"],
- "msg": "extra fields not permitted",
- }
- ]
- }
- )
+ {
+ "detail": [
+ {
+ "type": "extra_forbidden",
+ "loc": ["cookie", "extra"],
+ "msg": "Extra inputs are not permitted",
+ "input": "track-me-here-too",
+ }
+ ]
+ }
)
@@ -149,42 +107,22 @@ def test_openapi_schema(client: TestClient):
"name": "fatebook_tracker",
"in": "cookie",
"required": False,
- "schema": pydantic_snapshot(
- v2=snapshot(
- {
- "anyOf": [
- {"type": "string"},
- {"type": "null"},
- ],
- "title": "Fatebook Tracker",
- }
- ),
- v1=snapshot(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Fatebook Tracker",
- }
- ),
- ),
+ "schema": {
+ "anyOf": [
+ {"type": "string"},
+ {"type": "null"},
+ ],
+ "title": "Fatebook Tracker",
+ },
},
{
"name": "googall_tracker",
"in": "cookie",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Googall Tracker",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Googall Tracker",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Googall Tracker",
+ },
},
],
"responses": {
diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
index 90e8dfd37c..9b47cbc67a 100644
--- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
@@ -2,19 +2,17 @@ import importlib
from types import ModuleType
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="mod",
params=[
- "tutorial001",
+ "tutorial001_py39",
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ "tutorial001_an_py39",
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
@@ -76,16 +74,10 @@ def test_openapi_schema(mod: ModuleType):
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Ads Id",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Ads Id", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Ads Id",
+ },
"name": "ads_id",
"in": "cookie",
}
diff --git a/tests/test_tutorial/test_cors/test_tutorial001.py b/tests/test_tutorial/test_cors/test_tutorial001.py
index f62c9df4f9..6a733693ae 100644
--- a/tests/test_tutorial/test_cors/test_tutorial001.py
+++ b/tests/test_tutorial/test_cors/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.cors.tutorial001 import app
+from docs_src.cors.tutorial001_py39 import app
def test_cors():
diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py
index cb8e8c2248..1816e5d975 100644
--- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py
+++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py
@@ -10,7 +10,7 @@ def client():
static_dir: Path = Path(os.getcwd()) / "static"
print(static_dir)
static_dir.mkdir(exist_ok=True)
- from docs_src.custom_docs_ui.tutorial001 import app
+ from docs_src.custom_docs_ui.tutorial001_py39 import app
with TestClient(app) as client:
yield client
diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py
index 712618807c..e8b7eb7aa3 100644
--- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py
+++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py
@@ -10,7 +10,7 @@ def client():
static_dir: Path = Path(os.getcwd()) / "static"
print(static_dir)
static_dir.mkdir(exist_ok=True)
- from docs_src.custom_docs_ui.tutorial002 import app
+ from docs_src.custom_docs_ui.tutorial002_py39 import app
with TestClient(app) as client:
yield client
diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py
index e6da630e88..b4ea537846 100644
--- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py
+++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py
@@ -1,23 +1,36 @@
import gzip
+import importlib
import json
import pytest
from fastapi import Request
from fastapi.testclient import TestClient
-from docs_src.custom_request_and_route.tutorial001 import app
+from tests.utils import needs_py310
-@app.get("/check-class")
-async def check_gzip_request(request: Request):
- return {"request_class": type(request).__name__}
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ pytest.param("tutorial001_an_py39"),
+ pytest.param("tutorial001_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.custom_request_and_route.{request.param}")
+ @mod.app.get("/check-class")
+ async def check_gzip_request(request: Request):
+ return {"request_class": type(request).__name__}
-client = TestClient(app)
+ client = TestClient(mod.app)
+ return client
@pytest.mark.parametrize("compress", [True, False])
-def test_gzip_request(compress):
+def test_gzip_request(client: TestClient, compress):
n = 1000
headers = {}
body = [1] * n
@@ -30,6 +43,6 @@ def test_gzip_request(compress):
assert response.json() == {"sum": n}
-def test_request_class():
+def test_request_class(client: TestClient):
response = client.get("/check-class")
assert response.json() == {"request_class": "GzipRequest"}
diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py
index 647f1c5ddf..a9c7ae638b 100644
--- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py
+++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py
@@ -1,46 +1,46 @@
-from dirty_equals import IsDict, IsOneOf
+import importlib
+
+import pytest
+from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
-from docs_src.custom_request_and_route.tutorial002 import app
-
-client = TestClient(app)
+from tests.utils import needs_py310
-def test_endpoint_works():
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ pytest.param("tutorial002_an_py39"),
+ pytest.param("tutorial002_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.custom_request_and_route.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_endpoint_works(client: TestClient):
response = client.post("/", json=[1, 2, 3])
assert response.json() == 6
-def test_exception_handler_body_access():
+def test_exception_handler_body_access(client: TestClient):
response = client.post("/", json={"numbers": [1, 2, 3]})
- assert response.json() == IsDict(
- {
- "detail": {
- "errors": [
- {
- "type": "list_type",
- "loc": ["body"],
- "msg": "Input should be a valid list",
- "input": {"numbers": [1, 2, 3]},
- }
- ],
- # httpx 0.28.0 switches to compact JSON https://github.com/encode/httpx/issues/3363
- "body": IsOneOf('{"numbers": [1, 2, 3]}', '{"numbers":[1,2,3]}'),
- }
+ assert response.json() == {
+ "detail": {
+ "errors": [
+ {
+ "type": "list_type",
+ "loc": ["body"],
+ "msg": "Input should be a valid list",
+ "input": {"numbers": [1, 2, 3]},
+ }
+ ],
+ # httpx 0.28.0 switches to compact JSON https://github.com/encode/httpx/issues/3363
+ "body": IsOneOf('{"numbers": [1, 2, 3]}', '{"numbers":[1,2,3]}'),
}
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": {
- # httpx 0.28.0 switches to compact JSON https://github.com/encode/httpx/issues/3363
- "body": IsOneOf('{"numbers": [1, 2, 3]}', '{"numbers":[1,2,3]}'),
- "errors": [
- {
- "loc": ["body"],
- "msg": "value is not a valid list",
- "type": "type_error.list",
- }
- ],
- }
- }
- )
+ }
diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py
index db5dad7cf6..6cad7bd3f0 100644
--- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py
+++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py
@@ -1,17 +1,32 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.custom_request_and_route.tutorial003 import app
-
-client = TestClient(app)
+from tests.utils import needs_py310
-def test_get():
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial003_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.custom_request_and_route.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_get(client: TestClient):
response = client.get("/")
assert response.json() == {"message": "Not timed"}
assert "X-Response-Time" not in response.headers
-def test_get_timed():
+def test_get_timed(client: TestClient):
response = client.get("/timed")
assert response.json() == {"message": "It's the time of my life"}
assert "X-Response-Time" in response.headers
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001.py b/tests/test_tutorial/test_custom_response/test_tutorial001.py
index fc83624673..f1d2accef2 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial001.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py
@@ -1,17 +1,29 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.custom_response.tutorial001 import app
-client = TestClient(app)
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial010_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.custom_response.{request.param}")
+ client = TestClient(mod.app)
+ return client
-def test_get_custom_response():
+def test_get_custom_response(client: TestClient):
response = client.get("/items/")
assert response.status_code == 200, response.text
assert response.json() == [{"item_id": "Foo"}]
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001b.py b/tests/test_tutorial/test_custom_response/test_tutorial001b.py
index 91e5c501e3..3337f47d5f 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial001b.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial001b.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.custom_response.tutorial001b import app
+from docs_src.custom_response.tutorial001b_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py b/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py
new file mode 100644
index 0000000000..22e2e02540
--- /dev/null
+++ b/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py
@@ -0,0 +1,68 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="mod_name",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial004_py39"),
+ ],
+)
+def get_mod_name(request: pytest.FixtureRequest) -> str:
+ return request.param
+
+
+@pytest.fixture(name="client")
+def get_client(mod_name: str) -> TestClient:
+ mod = importlib.import_module(f"docs_src.custom_response.{mod_name}")
+ return TestClient(mod.app)
+
+
+html_contents = """
+
+
+ Some HTML in here
+
+
+ Look ma! HTML!
+
+
+ """
+
+
+def test_get_custom_response(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.text == html_contents
+
+
+def test_openapi_schema(client: TestClient, mod_name: str):
+ if mod_name.startswith("tutorial003"):
+ response_content = {"application/json": {"schema": {}}}
+ else:
+ response_content = {"text/html": {"schema": {"type": "string"}}}
+
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": response_content,
+ }
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ }
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial005.py b/tests/test_tutorial/test_custom_response/test_tutorial005.py
index 889bf3e929..fea105c28e 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial005.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial005.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.custom_response.tutorial005 import app
+from docs_src.custom_response.tutorial005_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006.py b/tests/test_tutorial/test_custom_response/test_tutorial006.py
index 2d0a2cd3f6..e9e6ca1086 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial006.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial006.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.custom_response.tutorial006 import app
+from docs_src.custom_response.tutorial006_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006b.py b/tests/test_tutorial/test_custom_response/test_tutorial006b.py
index 1739fd4570..7ca727d2cd 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial006b.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial006b.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.custom_response.tutorial006b import app
+from docs_src.custom_response.tutorial006b_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006c.py b/tests/test_tutorial/test_custom_response/test_tutorial006c.py
index 2675f2a93c..e3f76c8403 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial006c.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial006c.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.custom_response.tutorial006c import app
+from docs_src.custom_response.tutorial006c_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial007.py b/tests/test_tutorial/test_custom_response/test_tutorial007.py
index 4ede820b94..a62476ec14 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial007.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial007.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.custom_response.tutorial007 import app
+from docs_src.custom_response.tutorial007_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial008.py b/tests/test_tutorial/test_custom_response/test_tutorial008.py
index 10d88a5948..d9fe61f539 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial008.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial008.py
@@ -2,15 +2,15 @@ from pathlib import Path
from fastapi.testclient import TestClient
-from docs_src.custom_response import tutorial008
-from docs_src.custom_response.tutorial008 import app
+from docs_src.custom_response import tutorial008_py39
+from docs_src.custom_response.tutorial008_py39 import app
client = TestClient(app)
def test_get(tmp_path: Path):
file_path: Path = tmp_path / "large-video-file.mp4"
- tutorial008.some_file_path = str(file_path)
+ tutorial008_py39.some_file_path = str(file_path)
test_content = b"Fake video bytes"
file_path.write_bytes(test_content)
response = client.get("/")
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009.py b/tests/test_tutorial/test_custom_response/test_tutorial009.py
index ac20f89e67..cb6a514be6 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial009.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial009.py
@@ -2,15 +2,15 @@ from pathlib import Path
from fastapi.testclient import TestClient
-from docs_src.custom_response import tutorial009
-from docs_src.custom_response.tutorial009 import app
+from docs_src.custom_response import tutorial009_py39
+from docs_src.custom_response.tutorial009_py39 import app
client = TestClient(app)
def test_get(tmp_path: Path):
file_path: Path = tmp_path / "large-video-file.mp4"
- tutorial009.some_file_path = str(file_path)
+ tutorial009_py39.some_file_path = str(file_path)
test_content = b"Fake video bytes"
file_path.write_bytes(test_content)
response = client.get("/")
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009b.py b/tests/test_tutorial/test_custom_response/test_tutorial009b.py
index 4f56e2f3fe..9918bdb1ac 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial009b.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial009b.py
@@ -2,15 +2,15 @@ from pathlib import Path
from fastapi.testclient import TestClient
-from docs_src.custom_response import tutorial009b
-from docs_src.custom_response.tutorial009b import app
+from docs_src.custom_response import tutorial009b_py39
+from docs_src.custom_response.tutorial009b_py39 import app
client = TestClient(app)
def test_get(tmp_path: Path):
file_path: Path = tmp_path / "large-video-file.mp4"
- tutorial009b.some_file_path = str(file_path)
+ tutorial009b_py39.some_file_path = str(file_path)
test_content = b"Fake video bytes"
file_path.write_bytes(test_content)
response = client.get("/")
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009c.py b/tests/test_tutorial/test_custom_response/test_tutorial009c.py
index 23c711abe9..efc3a6b4a0 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial009c.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial009c.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.custom_response.tutorial009c import app
+from docs_src.custom_response.tutorial009c_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py
index 762654d29d..4683062f59 100644
--- a/tests/test_tutorial/test_dataclasses/test_tutorial001.py
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py
@@ -1,12 +1,27 @@
-from dirty_equals import IsDict
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.dataclasses.tutorial001 import app
-
-client = TestClient(app)
+from tests.utils import needs_py310
-def test_post_item():
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.dataclasses_.{request.param}")
+
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
+
+
+def test_post_item(client: TestClient):
response = client.post("/items/", json={"name": "Foo", "price": 3})
assert response.status_code == 200
assert response.json() == {
@@ -17,35 +32,22 @@ def test_post_item():
}
-def test_post_invalid_item():
+def test_post_invalid_item(client: TestClient):
response = client.post("/items/", json={"name": "Foo", "price": "invalid price"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "float_parsing",
- "loc": ["body", "price"],
- "msg": "Input should be a valid number, unable to parse string as a number",
- "input": "invalid price",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "price"],
- "msg": "value is not a valid float",
- "type": "type_error.float",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "float_parsing",
+ "loc": ["body", "price"],
+ "msg": "Input should be a valid number, unable to parse string as a number",
+ "input": "invalid price",
+ }
+ ]
+ }
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
@@ -103,26 +105,14 @@ def test_openapi_schema():
"properties": {
"name": {"title": "Name", "type": "string"},
"price": {"title": "Price", "type": "number"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py
index e6d303cfc1..210d743bb8 100644
--- a/tests/test_tutorial/test_dataclasses/test_tutorial002.py
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py
@@ -1,12 +1,28 @@
-from dirty_equals import IsDict, IsOneOf
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
-from docs_src.dataclasses.tutorial002 import app
-
-client = TestClient(app)
+from tests.utils import needs_py310
-def test_get_item():
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.dataclasses_.{request.param}")
+
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
+
+
+def test_get_item(client: TestClient):
response = client.get("/items/next")
assert response.status_code == 200
assert response.json() == {
@@ -18,80 +34,56 @@ def test_get_item():
}
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/next": {
- "get": {
- "summary": "Read Next Item",
- "operationId": "read_next_item_items_next_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- }
- },
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/next": {
+ "get": {
+ "summary": "Read Next Item",
+ "operationId": "read_next_item_items_next_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ }
+ },
+ }
}
- }
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "required": IsOneOf(
- ["name", "price", "tags", "description", "tax"],
- # TODO: remove when deprecating Pydantic v1
- ["name", "price"],
- ),
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "price": {"title": "Price", "type": "number"},
- "tags": IsDict(
- {
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Tags",
- "type": "array",
- "items": {"type": "string"},
- }
- ),
- "description": IsDict(
- {
+ },
+ "description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "tax": IsDict(
- {
+ },
+ "tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
- },
+ },
+ },
+ }
}
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py
index e1fa45201f..a6a9fc1c7e 100644
--- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py
@@ -1,13 +1,27 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.dataclasses.tutorial003 import app
-
-from ...utils import needs_pydanticv1, needs_pydanticv2
-
-client = TestClient(app)
+from ...utils import needs_py310
-def test_post_authors_item():
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial003_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.dataclasses_.{request.param}")
+
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
+
+
+def test_post_authors_item(client: TestClient):
response = client.post(
"/authors/foo/items/",
json=[{"name": "Bar"}, {"name": "Baz", "description": "Drop the Baz"}],
@@ -22,7 +36,7 @@ def test_post_authors_item():
}
-def test_get_authors():
+def test_get_authors(client: TestClient):
response = client.get("/authors/")
assert response.status_code == 200
assert response.json() == [
@@ -53,8 +67,7 @@ def test_get_authors():
]
-@needs_pydanticv2
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
@@ -187,137 +200,3 @@ def test_openapi_schema():
}
},
}
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_openapi_schema_pv1():
- response = client.get("/openapi.json")
- assert response.status_code == 200
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/authors/{author_id}/items/": {
- "post": {
- "summary": "Create Author Items",
- "operationId": "create_author_items_authors__author_id__items__post",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Author Id", "type": "string"},
- "name": "author_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "title": "Items",
- "type": "array",
- "items": {"$ref": "#/components/schemas/Item"},
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Author"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/authors/": {
- "get": {
- "summary": "Get Authors",
- "operationId": "get_authors_authors__get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "title": "Response Get Authors Authors Get",
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/Author"
- },
- }
- }
- },
- }
- },
- }
- },
- },
- "components": {
- "schemas": {
- "Author": {
- "title": "Author",
- "required": ["name"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "items": {
- "title": "Items",
- "type": "array",
- "items": {"$ref": "#/components/schemas/Item"},
- },
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "Item": {
- "title": "Item",
- "required": ["name"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": {"title": "Description", "type": "string"},
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_debugging/__init__.py b/tests/test_tutorial/test_debugging/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_debugging/test_tutorial001.py b/tests/test_tutorial/test_debugging/test_tutorial001.py
new file mode 100644
index 0000000000..cf62c3b194
--- /dev/null
+++ b/tests/test_tutorial/test_debugging/test_tutorial001.py
@@ -0,0 +1,64 @@
+import importlib
+import runpy
+import sys
+import unittest
+
+import pytest
+from fastapi.testclient import TestClient
+
+MOD_NAME = "docs_src.debugging.tutorial001_py39"
+
+
+@pytest.fixture(name="client")
+def get_client():
+ mod = importlib.import_module(MOD_NAME)
+ client = TestClient(mod.app)
+ return client
+
+
+def test_uvicorn_run_is_not_called_on_import():
+ if sys.modules.get(MOD_NAME):
+ del sys.modules[MOD_NAME] # pragma: no cover
+ with unittest.mock.patch("uvicorn.run") as uvicorn_run_mock:
+ importlib.import_module(MOD_NAME)
+ uvicorn_run_mock.assert_not_called()
+
+
+def test_get_root(client: TestClient):
+ response = client.get("/")
+ assert response.status_code == 200
+ assert response.json() == {"hello world": "ba"}
+
+
+def test_uvicorn_run_called_when_run_as_main(): # Just for coverage
+ if sys.modules.get(MOD_NAME):
+ del sys.modules[MOD_NAME]
+ with unittest.mock.patch("uvicorn.run") as uvicorn_run_mock:
+ runpy.run_module(MOD_NAME, run_name="__main__")
+
+ uvicorn_run_mock.assert_called_once_with(
+ unittest.mock.ANY, host="0.0.0.0", port=8000
+ )
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "get": {
+ "summary": "Root",
+ "operationId": "root__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ },
+ }
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001.py b/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py
similarity index 84%
rename from tests/test_tutorial/test_dependencies/test_tutorial001.py
rename to tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py
index ed9944912e..50d7c4108c 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial001.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py
@@ -1,20 +1,20 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
+ pytest.param("tutorial001_py39"),
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ pytest.param("tutorial001_an_py39"),
pytest.param("tutorial001_an_py310", marks=needs_py310),
+ pytest.param("tutorial001_02_an_py39"),
+ pytest.param("tutorial001_02_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
@@ -70,16 +70,10 @@ def test_openapi_schema(client: TestClient):
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Q", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Q",
+ },
"name": "q",
"in": "query",
},
@@ -129,16 +123,10 @@ def test_openapi_schema(client: TestClient):
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Q", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Q",
+ },
"name": "q",
"in": "query",
},
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py
similarity index 87%
rename from tests/test_tutorial/test_dependencies/test_tutorial004.py
rename to tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py
index 8221c83d44..f09d6f268d 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial004.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py
@@ -1,19 +1,25 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial004",
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ pytest.param("tutorial002_an_py39"),
+ pytest.param("tutorial002_an_py310", marks=needs_py310),
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial003_py310", marks=needs_py310),
+ pytest.param("tutorial003_an_py39"),
+ pytest.param("tutorial003_an_py310", marks=needs_py310),
+ pytest.param("tutorial004_py39"),
pytest.param("tutorial004_py310", marks=needs_py310),
- "tutorial004_an",
- pytest.param("tutorial004_an_py39", marks=needs_py39),
+ pytest.param("tutorial004_an_py39"),
pytest.param("tutorial004_an_py310", marks=needs_py310),
],
)
@@ -108,16 +114,10 @@ def test_openapi_schema(client: TestClient):
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Q", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Q",
+ },
"name": "q",
"in": "query",
},
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial005.py b/tests/test_tutorial/test_dependencies/test_tutorial005.py
new file mode 100644
index 0000000000..a914936ba1
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial005.py
@@ -0,0 +1,139 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial005_py39"),
+ pytest.param("tutorial005_py310", marks=needs_py310),
+ pytest.param("tutorial005_an_py39"),
+ pytest.param("tutorial005_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+@pytest.mark.parametrize(
+ "path,cookie,expected_status,expected_response",
+ [
+ (
+ "/items",
+ "from_cookie",
+ 200,
+ {"q_or_cookie": "from_cookie"},
+ ),
+ (
+ "/items?q=foo",
+ "from_cookie",
+ 200,
+ {"q_or_cookie": "foo"},
+ ),
+ (
+ "/items",
+ None,
+ 200,
+ {"q_or_cookie": None},
+ ),
+ ],
+)
+def test_get(path, cookie, expected_status, expected_response, client: TestClient):
+ if cookie is not None:
+ client.cookies.set("last_query", cookie)
+ else:
+ client.cookies.clear()
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Query",
+ "operationId": "read_query_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Last Query",
+ },
+ "name": "last_query",
+ "in": "cookie",
+ },
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006.py b/tests/test_tutorial/test_dependencies/test_tutorial006.py
index 4530762f76..59202df3bf 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial006.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial006.py
@@ -1,18 +1,14 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial006",
- "tutorial006_an",
- pytest.param("tutorial006_an_py39", marks=needs_py39),
+ pytest.param("tutorial006_py39"),
+ pytest.param("tutorial006_an_py39"),
],
)
def get_client(request: pytest.FixtureRequest):
@@ -25,40 +21,22 @@ def get_client(request: pytest.FixtureRequest):
def test_get_no_headers(client: TestClient):
response = client.get("/items/")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "x-token"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["header", "x-key"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "x-token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["header", "x-key"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "x-token"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["header", "x-key"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_get_invalid_one_header(client: TestClient):
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial007.py b/tests/test_tutorial/test_dependencies/test_tutorial007.py
new file mode 100644
index 0000000000..3e188abcf6
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial007.py
@@ -0,0 +1,24 @@
+import asyncio
+from contextlib import asynccontextmanager
+from unittest.mock import Mock, patch
+
+from docs_src.dependencies.tutorial007_py39 import get_db
+
+
+def test_get_db(): # Just for coverage
+ async def test_async_gen():
+ cm = asynccontextmanager(get_db)
+ async with cm() as db_session:
+ return db_session
+
+ dbsession_moock = Mock()
+
+ with patch(
+ "docs_src.dependencies.tutorial007_py39.DBSession",
+ return_value=dbsession_moock,
+ create=True,
+ ):
+ value = asyncio.run(test_async_gen())
+
+ assert value is dbsession_moock
+ dbsession_moock.close.assert_called_once()
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008.py b/tests/test_tutorial/test_dependencies/test_tutorial008.py
new file mode 100644
index 0000000000..9d7377ebe4
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial008.py
@@ -0,0 +1,58 @@
+import importlib
+from types import ModuleType
+from typing import Annotated, Any
+from unittest.mock import Mock, patch
+
+import pytest
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="module",
+ params=[
+ "tutorial008_py39",
+ # Fails with `NameError: name 'DepA' is not defined`
+ pytest.param("tutorial008_an_py39", marks=pytest.mark.xfail),
+ ],
+)
+def get_module(request: pytest.FixtureRequest):
+ mod_name = f"docs_src.dependencies.{request.param}"
+ mod = importlib.import_module(mod_name)
+ return mod
+
+
+def test_get_db(module: ModuleType):
+ app = FastAPI()
+
+ @app.get("/")
+ def read_root(c: Annotated[Any, Depends(module.dependency_c)]):
+ return {"c": str(c)}
+
+ client = TestClient(app)
+
+ a_mock = Mock()
+ b_mock = Mock()
+ c_mock = Mock()
+
+ with (
+ patch(
+ f"{module.__name__}.generate_dep_a",
+ return_value=a_mock,
+ create=True,
+ ),
+ patch(
+ f"{module.__name__}.generate_dep_b",
+ return_value=b_mock,
+ create=True,
+ ),
+ patch(
+ f"{module.__name__}.generate_dep_c",
+ return_value=c_mock,
+ create=True,
+ ),
+ ):
+ response = client.get("/")
+
+ assert response.status_code == 200
+ assert response.json() == {"c": str(c_mock)}
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008b.py b/tests/test_tutorial/test_dependencies/test_tutorial008b.py
index 4d70922657..91e00b3705 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial008b.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial008b.py
@@ -3,15 +3,12 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial008b",
- "tutorial008b_an",
- pytest.param("tutorial008b_an_py39", marks=needs_py39),
+ pytest.param("tutorial008b_py39"),
+ pytest.param("tutorial008b_an_py39"),
],
)
def get_client(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008c.py b/tests/test_tutorial/test_dependencies/test_tutorial008c.py
index 369b0a221d..aede6f8d25 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial008c.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial008c.py
@@ -5,15 +5,12 @@ import pytest
from fastapi.exceptions import FastAPIError
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="mod",
params=[
- "tutorial008c",
- "tutorial008c_an",
- pytest.param("tutorial008c_an_py39", marks=needs_py39),
+ pytest.param("tutorial008c_py39"),
+ pytest.param("tutorial008c_an_py39"),
],
)
def get_mod(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008d.py b/tests/test_tutorial/test_dependencies/test_tutorial008d.py
index bc99bb3835..5477f8b953 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial008d.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial008d.py
@@ -4,15 +4,12 @@ from types import ModuleType
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="mod",
params=[
- "tutorial008d",
- "tutorial008d_an",
- pytest.param("tutorial008d_an_py39", marks=needs_py39),
+ pytest.param("tutorial008d_py39"),
+ pytest.param("tutorial008d_an_py39"),
],
)
def get_mod(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008e.py b/tests/test_tutorial/test_dependencies/test_tutorial008e.py
index 1ae9ab2cd1..c433157ea1 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial008e.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial008e.py
@@ -3,15 +3,12 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial008e",
- "tutorial008e_an",
- pytest.param("tutorial008e_an_py39", marks=needs_py39),
+ pytest.param("tutorial008e_py39"),
+ pytest.param("tutorial008e_an_py39"),
],
)
def get_client(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial010.py b/tests/test_tutorial/test_dependencies/test_tutorial010.py
new file mode 100644
index 0000000000..6d3815ada2
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial010.py
@@ -0,0 +1,29 @@
+from typing import Annotated, Any
+from unittest.mock import Mock, patch
+
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+
+from docs_src.dependencies.tutorial010_py39 import get_db
+
+
+def test_get_db():
+ app = FastAPI()
+
+ @app.get("/")
+ def read_root(c: Annotated[Any, Depends(get_db)]):
+ return {"c": str(c)}
+
+ client = TestClient(app)
+
+ dbsession_mock = Mock()
+
+ with patch(
+ "docs_src.dependencies.tutorial010_py39.DBSession",
+ return_value=dbsession_mock,
+ create=True,
+ ):
+ response = client.get("/")
+
+ assert response.status_code == 200
+ assert response.json() == {"c": str(dbsession_mock)}
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial011.py b/tests/test_tutorial/test_dependencies/test_tutorial011.py
new file mode 100644
index 0000000000..4868254c0b
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial011.py
@@ -0,0 +1,120 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial011_py39",
+ pytest.param("tutorial011_an_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ (
+ "/query-checker/",
+ 200,
+ {"fixed_content_in_query": False},
+ ),
+ (
+ "/query-checker/?q=qwerty",
+ 200,
+ {"fixed_content_in_query": False},
+ ),
+ (
+ "/query-checker/?q=foobar",
+ 200,
+ {"fixed_content_in_query": True},
+ ),
+ ],
+)
+def test_get(path, expected_status, expected_response, client: TestClient):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/query-checker/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Query Check",
+ "operationId": "read_query_check_query_checker__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "type": "string",
+ "default": "",
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ },
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012.py b/tests/test_tutorial/test_dependencies/test_tutorial012.py
index 0af17e9bc5..d5599ac73a 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial012.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial012.py
@@ -1,18 +1,14 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial012",
- "tutorial012_an",
- pytest.param("tutorial012_an_py39", marks=needs_py39),
+ pytest.param("tutorial012_py39"),
+ pytest.param("tutorial012_an_py39"),
],
)
def get_client(request: pytest.FixtureRequest):
@@ -25,79 +21,43 @@ def get_client(request: pytest.FixtureRequest):
def test_get_no_headers_items(client: TestClient):
response = client.get("/items/")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "x-token"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["header", "x-key"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "x-token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["header", "x-key"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "x-token"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["header", "x-key"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_get_no_headers_users(client: TestClient):
response = client.get("/users/")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["header", "x-token"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["header", "x-key"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["header", "x-token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["header", "x-key"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["header", "x-token"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["header", "x-key"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_get_invalid_one_header_items(client: TestClient):
diff --git a/tests/test_tutorial/test_encoder/__init__.py b/tests/test_tutorial/test_encoder/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_encoder/test_tutorial001.py b/tests/test_tutorial/test_encoder/test_tutorial001.py
new file mode 100644
index 0000000000..5c8ee054d8
--- /dev/null
+++ b/tests/test_tutorial/test_encoder/test_tutorial001.py
@@ -0,0 +1,208 @@
+import importlib
+from types import ModuleType
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="mod",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ ],
+)
+def get_module(request: pytest.FixtureRequest):
+ module = importlib.import_module(f"docs_src.encoder.{request.param}")
+ return module
+
+
+@pytest.fixture(name="client")
+def get_client(mod: ModuleType):
+ client = TestClient(mod.app)
+ return client
+
+
+def test_put(client: TestClient, mod: ModuleType):
+ fake_db = mod.fake_db
+
+ response = client.put(
+ "/items/123",
+ json={
+ "title": "Foo",
+ "timestamp": "2023-01-01T12:00:00",
+ "description": "An optional description",
+ },
+ )
+ assert response.status_code == 200
+ assert "123" in fake_db
+ assert fake_db["123"] == {
+ "title": "Foo",
+ "timestamp": "2023-01-01T12:00:00",
+ "description": "An optional description",
+ }
+
+
+def test_put_invalid_data(client: TestClient, mod: ModuleType):
+ fake_db = mod.fake_db
+
+ response = client.put(
+ "/items/345",
+ json={
+ "title": "Foo",
+ "timestamp": "not a date",
+ },
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "timestamp"],
+ "msg": "Input should be a valid datetime or date, invalid character in year",
+ "type": "datetime_from_date_parsing",
+ "input": "not a date",
+ "ctx": {"error": "invalid character in year"},
+ }
+ ]
+ }
+ assert "345" not in fake_db
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{id}": {
+ "put": {
+ "operationId": "update_item_items__id__put",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": True,
+ "schema": {
+ "title": "Id",
+ "type": "string",
+ },
+ },
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ },
+ },
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Update Item",
+ },
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "description": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ "title": "Description",
+ },
+ "timestamp": {
+ "format": "date-time",
+ "title": "Timestamp",
+ "type": "string",
+ },
+ "title": {
+ "title": "Title",
+ "type": "string",
+ },
+ },
+ "required": [
+ "title",
+ "timestamp",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_events/test_tutorial001.py b/tests/test_tutorial/test_events/test_tutorial001.py
index f65b92d127..5fe99d50df 100644
--- a/tests/test_tutorial/test_events/test_tutorial001.py
+++ b/tests/test_tutorial/test_events/test_tutorial001.py
@@ -6,7 +6,7 @@ from fastapi.testclient import TestClient
@pytest.fixture(name="app", scope="module")
def get_app():
with pytest.warns(DeprecationWarning):
- from docs_src.events.tutorial001 import app
+ from docs_src.events.tutorial001_py39 import app
yield app
diff --git a/tests/test_tutorial/test_events/test_tutorial002.py b/tests/test_tutorial/test_events/test_tutorial002.py
index 137294d737..0612899b55 100644
--- a/tests/test_tutorial/test_events/test_tutorial002.py
+++ b/tests/test_tutorial/test_events/test_tutorial002.py
@@ -6,7 +6,7 @@ from fastapi.testclient import TestClient
@pytest.fixture(name="app", scope="module")
def get_app():
with pytest.warns(DeprecationWarning):
- from docs_src.events.tutorial002 import app
+ from docs_src.events.tutorial002_py39 import app
yield app
diff --git a/tests/test_tutorial/test_events/test_tutorial003.py b/tests/test_tutorial/test_events/test_tutorial003.py
index 0ad1a1f8b2..38710edfea 100644
--- a/tests/test_tutorial/test_events/test_tutorial003.py
+++ b/tests/test_tutorial/test_events/test_tutorial003.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.events.tutorial003 import (
+from docs_src.events.tutorial003_py39 import (
app,
fake_answer_to_everything_ml_model,
ml_models,
diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py
index a85a313501..83ecb300ee 100644
--- a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py
+++ b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.extending_openapi.tutorial001 import app
+from docs_src.extending_openapi.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
index b816c9cab5..5479e29252 100644
--- a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
+++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
@@ -1,19 +1,18 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
+ pytest.param("tutorial001_py39"),
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ pytest.param("tutorial001_an_py39"),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
@@ -48,146 +47,117 @@ def test_extra_types(client: TestClient):
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "put": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
},
- "422": {
- "description": "Validation Error",
+ "summary": "Read Items",
+ "operationId": "read_items_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "string",
+ "format": "uuid",
+ },
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "required": True,
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
+ "$ref": "#/components/schemas/Body_read_items_items__item_id__put"
}
}
},
},
- },
- "summary": "Read Items",
- "operationId": "read_items_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {
- "title": "Item Id",
- "type": "string",
- "format": "uuid",
- },
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "required": True,
- "content": {
- "application/json": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_read_items_items__item_id__put"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "$ref": "#/components/schemas/Body_read_items_items__item_id__put"
- }
- )
- }
- },
- },
+ }
}
- }
- },
- "components": {
- "schemas": {
- "Body_read_items_items__item_id__put": {
- "title": "Body_read_items_items__item_id__put",
- "type": "object",
- "properties": {
- "start_datetime": {
- "title": "Start Datetime",
- "type": "string",
- "format": "date-time",
- },
- "end_datetime": {
- "title": "End Datetime",
- "type": "string",
- "format": "date-time",
- },
- "repeat_at": IsDict(
- {
+ },
+ "components": {
+ "schemas": {
+ "Body_read_items_items__item_id__put": {
+ "title": "Body_read_items_items__item_id__put",
+ "type": "object",
+ "properties": {
+ "start_datetime": {
+ "title": "Start Datetime",
+ "type": "string",
+ "format": "date-time",
+ },
+ "end_datetime": {
+ "title": "End Datetime",
+ "type": "string",
+ "format": "date-time",
+ },
+ "repeat_at": {
"title": "Repeat At",
"anyOf": [
{"type": "string", "format": "time"},
{"type": "null"},
],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Repeat At",
- "type": "string",
- "format": "time",
- }
- ),
- "process_after": IsDict(
- {
+ },
+ "process_after": {
"title": "Process After",
"type": "string",
"format": "duration",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Process After",
- "type": "number",
- "format": "time-delta",
- }
- ),
- },
- "required": ["start_datetime", "end_datetime", "process_after"],
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
+ "required": ["start_datetime", "end_datetime", "process_after"],
},
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
},
- },
- }
- },
- }
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
+ },
+ },
+ }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py
new file mode 100644
index 0000000000..3f2f508a11
--- /dev/null
+++ b/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py
@@ -0,0 +1,156 @@
+import importlib
+
+import pytest
+from dirty_equals import IsList
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.extra_models.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_post(client: TestClient):
+ response = client.post(
+ "/user/",
+ json={
+ "username": "johndoe",
+ "password": "secret",
+ "email": "johndoe@example.com",
+ "full_name": "John Doe",
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "username": "johndoe",
+ "email": "johndoe@example.com",
+ "full_name": "John Doe",
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/user/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserOut",
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create User",
+ "operationId": "create_user_user__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/UserIn"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "UserIn": {
+ "title": "UserIn",
+ "required": IsList(
+ "username", "password", "email", check_order=False
+ ),
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "password": {"title": "Password", "type": "string"},
+ "email": {
+ "title": "Email",
+ "type": "string",
+ "format": "email",
+ },
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ },
+ },
+ "UserOut": {
+ "title": "UserOut",
+ "required": ["username", "email"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {
+ "title": "Email",
+ "type": "string",
+ "format": "email",
+ },
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003.py b/tests/test_tutorial/test_extra_models/test_tutorial003.py
index 73aa299039..872af53830 100644
--- a/tests/test_tutorial/test_extra_models/test_tutorial003.py
+++ b/tests/test_tutorial/test_extra_models/test_tutorial003.py
@@ -1,8 +1,8 @@
import importlib
import pytest
-from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from ...utils import needs_py310
@@ -10,7 +10,7 @@ from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial003",
+ pytest.param("tutorial003_py39"),
pytest.param("tutorial003_py310", marks=needs_py310),
],
)
@@ -43,107 +43,115 @@ def test_get_plane(client: TestClient):
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "title": "Response Read Item Items Item Id Get",
- "anyOf": [
- {"$ref": "#/components/schemas/PlaneItem"},
- {"$ref": "#/components/schemas/CarItem"},
- ],
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Read Item Items Item Id Get",
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/PlaneItem"
+ },
+ {
+ "$ref": "#/components/schemas/CarItem"
+ },
+ ],
+ }
}
- }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
},
},
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Item",
- "operationId": "read_item_items__item_id__get",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "string"},
- "name": "item_id",
- "in": "path",
- }
- ],
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
}
- }
- },
- "components": {
- "schemas": {
- "PlaneItem": {
- "title": "PlaneItem",
- "required": IsOneOf(
- ["description", "type", "size"],
- # TODO: remove when deprecating Pydantic v1
- ["description", "size"],
- ),
- "type": "object",
- "properties": {
- "description": {"title": "Description", "type": "string"},
- "type": {"title": "Type", "type": "string", "default": "plane"},
- "size": {"title": "Size", "type": "integer"},
+ },
+ "components": {
+ "schemas": {
+ "PlaneItem": {
+ "title": "PlaneItem",
+ "required": ["description", "size"],
+ "type": "object",
+ "properties": {
+ "description": {"title": "Description", "type": "string"},
+ "type": {
+ "title": "Type",
+ "type": "string",
+ "default": "plane",
+ },
+ "size": {"title": "Size", "type": "integer"},
+ },
},
- },
- "CarItem": {
- "title": "CarItem",
- "required": IsOneOf(
- ["description", "type"],
- # TODO: remove when deprecating Pydantic v1
- ["description"],
- ),
- "type": "object",
- "properties": {
- "description": {"title": "Description", "type": "string"},
- "type": {"title": "Type", "type": "string", "default": "car"},
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
+ "CarItem": {
+ "title": "CarItem",
+ "required": ["description"],
+ "type": "object",
+ "properties": {
+ "description": {"title": "Description", "type": "string"},
+ "type": {
+ "title": "Type",
+ "type": "string",
+ "default": "car",
},
},
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
},
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
},
- },
- }
- },
- }
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
+ },
+ },
+ }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004.py b/tests/test_tutorial/test_extra_models/test_tutorial004.py
index 7628db30c7..073375ccc9 100644
--- a/tests/test_tutorial/test_extra_models/test_tutorial004.py
+++ b/tests/test_tutorial/test_extra_models/test_tutorial004.py
@@ -3,14 +3,11 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial004",
- pytest.param("tutorial004_py39", marks=needs_py39),
+ pytest.param("tutorial004_py39"),
],
)
def get_client(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005.py b/tests/test_tutorial/test_extra_models/test_tutorial005.py
index 553e442385..8e52d8d4ba 100644
--- a/tests/test_tutorial/test_extra_models/test_tutorial005.py
+++ b/tests/test_tutorial/test_extra_models/test_tutorial005.py
@@ -3,14 +3,11 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial005",
- pytest.param("tutorial005_py39", marks=needs_py39),
+ pytest.param("tutorial005_py39"),
],
)
def get_client(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_first_steps/test_tutorial001.py b/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py
similarity index 70%
rename from tests/test_tutorial/test_first_steps/test_tutorial001.py
rename to tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py
index 6cc9fc2282..aa65218cde 100644
--- a/tests/test_tutorial/test_first_steps/test_tutorial001.py
+++ b/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py
@@ -1,9 +1,20 @@
+import importlib
+
import pytest
from fastapi.testclient import TestClient
-from docs_src.first_steps.tutorial001 import app
-client = TestClient(app)
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial001_py39",
+ "tutorial003_py39",
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.first_steps.{request.param}")
+ client = TestClient(mod.app)
+ return client
@pytest.mark.parametrize(
@@ -13,13 +24,13 @@ client = TestClient(app)
("/nonexistent", 404, {"detail": "Not Found"}),
],
)
-def test_get_path(path, expected_status, expected_response):
+def test_get_path(client: TestClient, path, expected_status, expected_response):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial001.py b/tests/test_tutorial/test_generate_clients/test_tutorial001.py
new file mode 100644
index 0000000000..bbb66b4516
--- /dev/null
+++ b/tests/test_tutorial/test_generate_clients/test_tutorial001.py
@@ -0,0 +1,142 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.generate_clients.{request.param}")
+ client = TestClient(mod.app)
+ return client
+
+
+def test_post_items(client: TestClient):
+ response = client.post("/items/", json={"name": "Foo", "price": 5})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "item received"}
+
+
+def test_get_items(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "summary": "Get Items",
+ "operationId": "get_items_items__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Get Items Items Get",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResponseMessage"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "ResponseMessage": {
+ "title": "ResponseMessage",
+ "required": ["message"],
+ "type": "object",
+ "properties": {"message": {"title": "Message", "type": "string"}},
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial002.py b/tests/test_tutorial/test_generate_clients/test_tutorial002.py
new file mode 100644
index 0000000000..ab8bc4c11c
--- /dev/null
+++ b/tests/test_tutorial/test_generate_clients/test_tutorial002.py
@@ -0,0 +1,187 @@
+from fastapi.testclient import TestClient
+
+from docs_src.generate_clients.tutorial002_py39 import app
+
+client = TestClient(app)
+
+
+def test_post_items():
+ response = client.post("/items/", json={"name": "Foo", "price": 5})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Item received"}
+
+
+def test_post_users():
+ response = client.post(
+ "/users/", json={"username": "Foo", "email": "foo@example.com"}
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "User received"}
+
+
+def test_get_items():
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "tags": ["items"],
+ "summary": "Get Items",
+ "operationId": "get_items_items__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Get Items Items Get",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "tags": ["items"],
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResponseMessage"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
+ "/users/": {
+ "post": {
+ "tags": ["users"],
+ "summary": "Create User",
+ "operationId": "create_user_users__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResponseMessage"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "ResponseMessage": {
+ "title": "ResponseMessage",
+ "required": ["message"],
+ "type": "object",
+ "properties": {"message": {"title": "Message", "type": "string"}},
+ },
+ "User": {
+ "title": "User",
+ "required": ["username", "email"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {"title": "Email", "type": "string"},
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial003.py b/tests/test_tutorial/test_generate_clients/test_tutorial003.py
index 1cd9678a1c..bac52e4fd6 100644
--- a/tests/test_tutorial/test_generate_clients/test_tutorial003.py
+++ b/tests/test_tutorial/test_generate_clients/test_tutorial003.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.generate_clients.tutorial003 import app
+from docs_src.generate_clients.tutorial003_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial004.py b/tests/test_tutorial/test_generate_clients/test_tutorial004.py
new file mode 100644
index 0000000000..e66f6d2a12
--- /dev/null
+++ b/tests/test_tutorial/test_generate_clients/test_tutorial004.py
@@ -0,0 +1,230 @@
+import importlib
+import json
+import pathlib
+from unittest.mock import patch
+
+from docs_src.generate_clients import tutorial003_py39
+
+
+def test_remove_tags(tmp_path: pathlib.Path):
+ tmp_file = tmp_path / "openapi.json"
+ openapi_json = tutorial003_py39.app.openapi()
+ tmp_file.write_text(json.dumps(openapi_json))
+
+ with patch("pathlib.Path", return_value=tmp_file):
+ importlib.import_module("docs_src.generate_clients.tutorial004_py39")
+
+ modified_openapi = json.loads(tmp_file.read_text())
+ assert modified_openapi == {
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "price": {
+ "title": "Price",
+ "type": "number",
+ },
+ },
+ "required": [
+ "name",
+ "price",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "ResponseMessage": {
+ "properties": {
+ "message": {
+ "title": "Message",
+ "type": "string",
+ },
+ },
+ "required": [
+ "message",
+ ],
+ "title": "ResponseMessage",
+ "type": "object",
+ },
+ "User": {
+ "properties": {
+ "email": {
+ "title": "Email",
+ "type": "string",
+ },
+ "username": {
+ "title": "Username",
+ "type": "string",
+ },
+ },
+ "required": [
+ "username",
+ "email",
+ ],
+ "title": "User",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ "info": {
+ "title": "FastAPI",
+ "version": "0.1.0",
+ },
+ "openapi": "3.1.0",
+ "paths": {
+ "/items/": {
+ "get": {
+ "operationId": "get_items",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/Item",
+ },
+ "title": "Response Items-Get Items",
+ "type": "array",
+ },
+ },
+ },
+ "description": "Successful Response",
+ },
+ },
+ "summary": "Get Items",
+ "tags": [
+ "items",
+ ],
+ },
+ "post": {
+ "operationId": "create_item",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ },
+ },
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResponseMessage",
+ },
+ },
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Create Item",
+ "tags": [
+ "items",
+ ],
+ },
+ },
+ "/users/": {
+ "post": {
+ "operationId": "create_user",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/User",
+ },
+ },
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResponseMessage",
+ },
+ },
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Create User",
+ "tags": [
+ "users",
+ ],
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_graphql/__init__.py b/tests/test_tutorial/test_graphql/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_graphql/test_tutorial001.py b/tests/test_tutorial/test_graphql/test_tutorial001.py
new file mode 100644
index 0000000000..9ba7147b55
--- /dev/null
+++ b/tests/test_tutorial/test_graphql/test_tutorial001.py
@@ -0,0 +1,70 @@
+import warnings
+
+import pytest
+from starlette.testclient import TestClient
+
+warnings.filterwarnings(
+ "ignore",
+ message=r"The 'lia' package has been renamed to 'cross_web'\..*",
+ category=DeprecationWarning,
+)
+
+from docs_src.graphql_.tutorial001_py39 import app # noqa: E402
+
+
+@pytest.fixture(name="client")
+def get_client() -> TestClient:
+ return TestClient(app)
+
+
+def test_query(client: TestClient):
+ response = client.post("/graphql", json={"query": "{ user { name, age } }"})
+ assert response.status_code == 200
+ assert response.json() == {"data": {"user": {"name": "Patrick", "age": 100}}}
+
+
+def test_openapi(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == {
+ "info": {
+ "title": "FastAPI",
+ "version": "0.1.0",
+ },
+ "openapi": "3.1.0",
+ "paths": {
+ "/graphql": {
+ "get": {
+ "operationId": "handle_http_get_graphql_get",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "The GraphiQL integrated development environment.",
+ },
+ "404": {
+ "description": "Not found if GraphiQL or query via GET are not enabled.",
+ },
+ },
+ "summary": "Handle Http Get",
+ },
+ "post": {
+ "operationId": "handle_http_post_graphql_post",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "Successful Response",
+ },
+ },
+ "summary": "Handle Http Post",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial001.py b/tests/test_tutorial/test_handling_errors/test_tutorial001.py
index 8809c135bd..c01850fae6 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial001.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.handling_errors.tutorial001 import app
+from docs_src.handling_errors.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial002.py b/tests/test_tutorial/test_handling_errors/test_tutorial002.py
index efd86ebdec..09366a86fa 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial002.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial002.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.handling_errors.tutorial002 import app
+from docs_src.handling_errors.tutorial002_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial003.py b/tests/test_tutorial/test_handling_errors/test_tutorial003.py
index 4763f68f3f..51ac3e7b28 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial003.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial003.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.handling_errors.tutorial003 import app
+from docs_src.handling_errors.tutorial003_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial004.py b/tests/test_tutorial/test_handling_errors/test_tutorial004.py
index 217159a596..376bc8266f 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial004.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial004.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.handling_errors.tutorial004 import app
+from docs_src.handling_errors.tutorial004_py39 import app
client = TestClient(app)
@@ -8,18 +8,8 @@ client = TestClient(app)
def test_get_validation_error():
response = client.get("/items/foo")
assert response.status_code == 400, response.text
- # TODO: remove when deprecating Pydantic v1
- assert (
- # TODO: remove when deprecating Pydantic v1
- "path -> item_id" in response.text
- or "'loc': ('path', 'item_id')" in response.text
- )
- assert (
- # TODO: remove when deprecating Pydantic v1
- "value is not a valid integer" in response.text
- or "Input should be a valid integer, unable to parse string as an integer"
- in response.text
- )
+ assert "Validation errors:" in response.text
+ assert "Field: ('path', 'item_id')" in response.text
def test_get_http_error():
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial005.py b/tests/test_tutorial/test_handling_errors/test_tutorial005.py
index 581b2e4c75..7bd947f194 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial005.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial005.py
@@ -1,7 +1,6 @@
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from docs_src.handling_errors.tutorial005 import app
+from docs_src.handling_errors.tutorial005_py39 import app
client = TestClient(app)
@@ -9,31 +8,17 @@ client = TestClient(app)
def test_post_validation_error():
response = client.post("/items/", json={"title": "towel", "size": "XL"})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["body", "size"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "XL",
- }
- ],
- "body": {"title": "towel", "size": "XL"},
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "size"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ],
- "body": {"title": "towel", "size": "XL"},
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["body", "size"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "XL",
+ }
+ ],
+ "body": {"title": "towel", "size": "XL"},
+ }
def test_post():
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial006.py b/tests/test_tutorial/test_handling_errors/test_tutorial006.py
index 7d2f553aac..e95e53d5ed 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial006.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial006.py
@@ -1,7 +1,6 @@
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from docs_src.handling_errors.tutorial006 import app
+from docs_src.handling_errors.tutorial006_py39 import app
client = TestClient(app)
@@ -9,29 +8,16 @@ client = TestClient(app)
def test_get_validation_error():
response = client.get("/items/foo")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "int_parsing",
- "loc": ["path", "item_id"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "foo",
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["path", "item_id"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "foo",
+ }
+ ]
+ }
def test_get_http_error():
diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial001.py b/tests/test_tutorial/test_header_param_models/test_tutorial001.py
index bc876897b2..1fa8aee461 100644
--- a/tests/test_tutorial/test_header_param_models/test_tutorial001.py
+++ b/tests/test_tutorial/test_header_param_models/test_tutorial001.py
@@ -1,21 +1,18 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from tests.utils import needs_py39, needs_py310
+from tests.utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
- pytest.param("tutorial001_py39", marks=needs_py39),
+ pytest.param("tutorial001_py39"),
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ pytest.param("tutorial001_an_py39"),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
@@ -65,29 +62,19 @@ def test_header_param_model_invalid(client: TestClient):
assert response.json() == snapshot(
{
"detail": [
- IsDict(
- {
- "type": "missing",
- "loc": ["header", "save_data"],
- "msg": "Field required",
- "input": {
- "x_tag": [],
- "host": "testserver",
- "accept": "*/*",
- "accept-encoding": "gzip, deflate",
- "connection": "keep-alive",
- "user-agent": "testclient",
- },
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "value_error.missing",
- "loc": ["header", "save_data"],
- "msg": "field required",
- }
- )
+ {
+ "type": "missing",
+ "loc": ["header", "save_data"],
+ "msg": "Field required",
+ "input": {
+ "x_tag": [],
+ "host": "testserver",
+ "accept": "*/*",
+ "accept-encoding": "gzip, deflate",
+ "connection": "keep-alive",
+ "user-agent": "testclient",
+ },
+ }
]
}
)
@@ -138,37 +125,19 @@ def test_openapi_schema(client: TestClient):
"name": "if-modified-since",
"in": "header",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "If Modified Since",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "If Modified Since",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "If Modified Since",
+ },
},
{
"name": "traceparent",
"in": "header",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Traceparent",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Traceparent",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Traceparent",
+ },
},
{
"name": "x-tag",
diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial002.py b/tests/test_tutorial/test_header_param_models/test_tutorial002.py
index 0615521c43..079a8f5402 100644
--- a/tests/test_tutorial/test_header_param_models/test_tutorial002.py
+++ b/tests/test_tutorial/test_header_param_models/test_tutorial002.py
@@ -1,26 +1,19 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
+from tests.utils import needs_py310
@pytest.fixture(
name="client",
params=[
- pytest.param("tutorial002", marks=needs_pydanticv2),
- pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]),
- pytest.param("tutorial002_an", marks=needs_pydanticv2),
- pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]),
- pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]),
- pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]),
- pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]),
- pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]),
- pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]),
- pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]),
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=[needs_py310]),
+ pytest.param("tutorial002_an_py39"),
+ pytest.param("tutorial002_an_py310", marks=[needs_py310]),
],
)
def get_client(request: pytest.FixtureRequest):
@@ -70,22 +63,12 @@ def test_header_param_model_invalid(client: TestClient):
assert response.json() == snapshot(
{
"detail": [
- IsDict(
- {
- "type": "missing",
- "loc": ["header", "save_data"],
- "msg": "Field required",
- "input": {"x_tag": [], "host": "testserver"},
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "value_error.missing",
- "loc": ["header", "save_data"],
- "msg": "field required",
- }
- )
+ {
+ "type": "missing",
+ "loc": ["header", "save_data"],
+ "msg": "Field required",
+ "input": {"x_tag": [], "host": "testserver"},
+ }
]
}
)
@@ -99,22 +82,12 @@ def test_header_param_model_extra(client: TestClient):
assert response.json() == snapshot(
{
"detail": [
- IsDict(
- {
- "type": "extra_forbidden",
- "loc": ["header", "tool"],
- "msg": "Extra inputs are not permitted",
- "input": "plumbus",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "value_error.extra",
- "loc": ["header", "tool"],
- "msg": "extra fields not permitted",
- }
- )
+ {
+ "type": "extra_forbidden",
+ "loc": ["header", "tool"],
+ "msg": "Extra inputs are not permitted",
+ "input": "plumbus",
+ }
]
}
)
@@ -149,37 +122,19 @@ def test_openapi_schema(client: TestClient):
"name": "if-modified-since",
"in": "header",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "If Modified Since",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "If Modified Since",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "If Modified Since",
+ },
},
{
"name": "traceparent",
"in": "header",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Traceparent",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Traceparent",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Traceparent",
+ },
},
{
"name": "x-tag",
diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial003.py b/tests/test_tutorial/test_header_param_models/test_tutorial003.py
index 60940e1da2..4c89d80ee2 100644
--- a/tests/test_tutorial/test_header_param_models/test_tutorial003.py
+++ b/tests/test_tutorial/test_header_param_models/test_tutorial003.py
@@ -1,21 +1,18 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from tests.utils import needs_py39, needs_py310
+from tests.utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial003",
- pytest.param("tutorial003_py39", marks=needs_py39),
+ pytest.param("tutorial003_py39"),
pytest.param("tutorial003_py310", marks=needs_py310),
- "tutorial003_an",
- pytest.param("tutorial003_an_py39", marks=needs_py39),
+ pytest.param("tutorial003_an_py39"),
pytest.param("tutorial003_an_py310", marks=needs_py310),
],
)
@@ -62,33 +59,23 @@ def test_header_param_model_no_underscore(client: TestClient):
assert response.json() == snapshot(
{
"detail": [
- IsDict(
- {
- "type": "missing",
- "loc": ["header", "save_data"],
- "msg": "Field required",
- "input": {
- "host": "testserver",
- "traceparent": "123",
- "x_tag": [],
- "accept": "*/*",
- "accept-encoding": "gzip, deflate",
- "connection": "keep-alive",
- "user-agent": "testclient",
- "save-data": "true",
- "if-modified-since": "yesterday",
- "x-tag": "two",
- },
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "value_error.missing",
- "loc": ["header", "save_data"],
- "msg": "field required",
- }
- )
+ {
+ "type": "missing",
+ "loc": ["header", "save_data"],
+ "msg": "Field required",
+ "input": {
+ "host": "testserver",
+ "traceparent": "123",
+ "x_tag": [],
+ "accept": "*/*",
+ "accept-encoding": "gzip, deflate",
+ "connection": "keep-alive",
+ "user-agent": "testclient",
+ "save-data": "true",
+ "if-modified-since": "yesterday",
+ "x-tag": ["one", "two"],
+ },
+ }
]
}
)
@@ -112,29 +99,19 @@ def test_header_param_model_invalid(client: TestClient):
assert response.json() == snapshot(
{
"detail": [
- IsDict(
- {
- "type": "missing",
- "loc": ["header", "save_data"],
- "msg": "Field required",
- "input": {
- "x_tag": [],
- "host": "testserver",
- "accept": "*/*",
- "accept-encoding": "gzip, deflate",
- "connection": "keep-alive",
- "user-agent": "testclient",
- },
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "value_error.missing",
- "loc": ["header", "save_data"],
- "msg": "field required",
- }
- )
+ {
+ "type": "missing",
+ "loc": ["header", "save_data"],
+ "msg": "Field required",
+ "input": {
+ "x_tag": [],
+ "host": "testserver",
+ "accept": "*/*",
+ "accept-encoding": "gzip, deflate",
+ "connection": "keep-alive",
+ "user-agent": "testclient",
+ },
+ }
]
}
)
@@ -185,37 +162,19 @@ def test_openapi_schema(client: TestClient):
"name": "if_modified_since",
"in": "header",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "If Modified Since",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "If Modified Since",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "If Modified Since",
+ },
},
{
"name": "traceparent",
"in": "header",
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Traceparent",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Traceparent",
- }
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Traceparent",
+ },
},
{
"name": "x_tag",
diff --git a/tests/test_tutorial/test_header_params/test_tutorial001.py b/tests/test_tutorial/test_header_params/test_tutorial001.py
index d6f7fe6189..88591b8225 100644
--- a/tests/test_tutorial/test_header_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_header_params/test_tutorial001.py
@@ -1,7 +1,6 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -10,9 +9,9 @@ from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
+ pytest.param("tutorial001_py39"),
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
+ pytest.param("tutorial001_an_py39"),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
@@ -67,16 +66,10 @@ def test_openapi_schema(client: TestClient):
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "User-Agent",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "User-Agent", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "User-Agent",
+ },
"name": "user-agent",
"in": "header",
}
diff --git a/tests/test_tutorial/test_header_params/test_tutorial002.py b/tests/test_tutorial/test_header_params/test_tutorial002.py
index 7158f8651b..229f96c1f8 100644
--- a/tests/test_tutorial/test_header_params/test_tutorial002.py
+++ b/tests/test_tutorial/test_header_params/test_tutorial002.py
@@ -1,19 +1,17 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial002",
+ pytest.param("tutorial002_py39"),
pytest.param("tutorial002_py310", marks=needs_py310),
- "tutorial002_an",
- pytest.param("tutorial002_an_py39", marks=needs_py39),
+ pytest.param("tutorial002_an_py39"),
pytest.param("tutorial002_an_py310", marks=needs_py310),
],
)
@@ -79,16 +77,10 @@ def test_openapi_schema(client: TestClient):
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Strange Header",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Strange Header", "type": "string"}
- ),
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Strange Header",
+ },
"name": "strange_header",
"in": "header",
}
diff --git a/tests/test_tutorial/test_header_params/test_tutorial003.py b/tests/test_tutorial/test_header_params/test_tutorial003.py
index 473b961236..cf067ccf9e 100644
--- a/tests/test_tutorial/test_header_params/test_tutorial003.py
+++ b/tests/test_tutorial/test_header_params/test_tutorial003.py
@@ -1,19 +1,17 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial003",
+ pytest.param("tutorial003_py39"),
pytest.param("tutorial003_py310", marks=needs_py310),
- "tutorial003_an",
- pytest.param("tutorial003_an_py39", marks=needs_py39),
+ pytest.param("tutorial003_an_py39"),
pytest.param("tutorial003_an_py310", marks=needs_py310),
],
)
@@ -57,23 +55,13 @@ def test_openapi_schema(client: TestClient):
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "title": "X-Token",
- "anyOf": [
- {"type": "array", "items": {"type": "string"}},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "X-Token",
- "type": "array",
- "items": {"type": "string"},
- }
- ),
+ "schema": {
+ "title": "X-Token",
+ "anyOf": [
+ {"type": "array", "items": {"type": "string"}},
+ {"type": "null"},
+ ],
+ },
"name": "x-token",
"in": "header",
}
diff --git a/tests/test_tutorial/test_metadata/test_tutorial001.py b/tests/test_tutorial/test_metadata/test_tutorial001.py
index 04e8ff82b0..ead48577d0 100644
--- a/tests/test_tutorial/test_metadata/test_tutorial001.py
+++ b/tests/test_tutorial/test_metadata/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.metadata.tutorial001 import app
+from docs_src.metadata.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_metadata/test_tutorial001_1.py b/tests/test_tutorial/test_metadata/test_tutorial001_1.py
index 3efb1c4329..40878ccfd4 100644
--- a/tests/test_tutorial/test_metadata/test_tutorial001_1.py
+++ b/tests/test_tutorial/test_metadata/test_tutorial001_1.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.metadata.tutorial001_1 import app
+from docs_src.metadata.tutorial001_1_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial004.py b/tests/test_tutorial/test_metadata/test_tutorial002.py
similarity index 61%
rename from tests/test_tutorial/test_custom_response/test_tutorial004.py
rename to tests/test_tutorial/test_metadata/test_tutorial002.py
index de60574f5e..e2814c88f9 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial004.py
+++ b/tests/test_tutorial/test_metadata/test_tutorial002.py
@@ -1,45 +1,41 @@
from fastapi.testclient import TestClient
-from docs_src.custom_response.tutorial004 import app
+from docs_src.metadata.tutorial002_py39 import app
client = TestClient(app)
-html_contents = """
-
-
- Some HTML in here
-
-
- Look ma! HTML!
-
-
- """
-
-
-def test_get_custom_response():
+def test_items():
response = client.get("/items/")
assert response.status_code == 200, response.text
- assert response.text == html_contents
+ assert response.json() == [{"name": "Foo"}]
+
+
+def test_get_openapi_json_default_url():
+ response = client.get("/openapi.json")
+ assert response.status_code == 404, response.text
def test_openapi_schema():
- response = client.get("/openapi.json")
+ response = client.get("/api/v1/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
+ "info": {
+ "title": "FastAPI",
+ "version": "0.1.0",
+ },
"paths": {
"/items/": {
"get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
- "content": {"text/html": {"schema": {"type": "string"}}},
+ "content": {"application/json": {"schema": {}}},
}
},
- "summary": "Read Items",
- "operationId": "read_items_items__get",
}
}
},
diff --git a/tests/test_tutorial/test_metadata/test_tutorial003.py b/tests/test_tutorial/test_metadata/test_tutorial003.py
new file mode 100644
index 0000000000..085c271cdb
--- /dev/null
+++ b/tests/test_tutorial/test_metadata/test_tutorial003.py
@@ -0,0 +1,53 @@
+from fastapi.testclient import TestClient
+
+from docs_src.metadata.tutorial003_py39 import app
+
+client = TestClient(app)
+
+
+def test_items():
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [{"name": "Foo"}]
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {
+ "title": "FastAPI",
+ "version": "0.1.0",
+ },
+ "paths": {
+ "/items/": {
+ "get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ }
+ },
+ }
+
+
+def test_swagger_ui_default_url():
+ response = client.get("/docs")
+ assert response.status_code == 404, response.text
+
+
+def test_swagger_ui_custom_url():
+ response = client.get("/documentation")
+ assert response.status_code == 200, response.text
+ assert "FastAPI - Swagger UI " in response.text
+
+
+def test_redoc_ui_default_url():
+ response = client.get("/redoc")
+ assert response.status_code == 404, response.text
diff --git a/tests/test_tutorial/test_metadata/test_tutorial004.py b/tests/test_tutorial/test_metadata/test_tutorial004.py
index 5072203712..4ef93fd5e3 100644
--- a/tests/test_tutorial/test_metadata/test_tutorial004.py
+++ b/tests/test_tutorial/test_metadata/test_tutorial004.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.metadata.tutorial004 import app
+from docs_src.metadata.tutorial004_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_middleware/__init__.py b/tests/test_tutorial/test_middleware/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_middleware/test_tutorial001.py b/tests/test_tutorial/test_middleware/test_tutorial001.py
new file mode 100644
index 0000000000..cbcfd4146f
--- /dev/null
+++ b/tests/test_tutorial/test_middleware/test_tutorial001.py
@@ -0,0 +1,24 @@
+from fastapi.testclient import TestClient
+
+from docs_src.middleware.tutorial001_py39 import app
+
+client = TestClient(app)
+
+
+def test_response_headers():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert "X-Process-Time" in response.headers
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {
+ "title": "FastAPI",
+ "version": "0.1.0",
+ },
+ "paths": {},
+ }
diff --git a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
index 73af420ae1..6fde96cb5b 100644
--- a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
+++ b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
@@ -1,12 +1,32 @@
-from dirty_equals import IsDict
+import importlib
+from types import ModuleType
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.openapi_callbacks.tutorial001 import app, invoice_notification
-
-client = TestClient(app)
+from tests.utils import needs_py310
-def test_get():
+@pytest.fixture(
+ name="mod",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ ],
+)
+def get_mod(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.openapi_callbacks.{request.param}")
+ return mod
+
+
+@pytest.fixture(name="client")
+def get_client(mod: ModuleType):
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
+
+
+def test_get(client: TestClient):
response = client.post(
"/invoices/", json={"id": "fooinvoice", "customer": "John", "total": 5.3}
)
@@ -14,12 +34,12 @@ def test_get():
assert response.json() == {"msg": "Invoice received"}
-def test_dummy_callback():
+def test_dummy_callback(mod: ModuleType):
# Just for coverage
- invoice_notification({})
+ mod.invoice_notification({})
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@@ -34,30 +54,18 @@ def test_openapi_schema():
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [
- {
- "type": "string",
- "format": "uri",
- "minLength": 1,
- "maxLength": 2083,
- },
- {"type": "null"},
- ],
- "title": "Callback Url",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Callback Url",
- "maxLength": 2083,
- "minLength": 1,
- "type": "string",
- "format": "uri",
- }
- ),
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uri",
+ "minLength": 1,
+ "maxLength": 2083,
+ },
+ {"type": "null"},
+ ],
+ "title": "Callback Url",
+ },
"name": "callback_url",
"in": "query",
}
@@ -150,16 +158,10 @@ def test_openapi_schema():
"type": "object",
"properties": {
"id": {"title": "Id", "type": "string"},
- "title": IsDict(
- {
- "title": "Title",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Title", "type": "string"}
- ),
+ "title": {
+ "title": "Title",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
"customer": {"title": "Customer", "type": "string"},
"total": {"title": "Total", "type": "number"},
},
diff --git a/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py b/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py
index dc67ec401d..27619489fa 100644
--- a/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py
+++ b/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.openapi_webhooks.tutorial001 import app
+from docs_src.openapi_webhooks.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py
index 95542398e4..ee0b707108 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.path_operation_advanced_configuration.tutorial001 import app
+from docs_src.path_operation_advanced_configuration.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py
index d1388c3670..f6580d72e3 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.path_operation_advanced_configuration.tutorial002 import app
+from docs_src.path_operation_advanced_configuration.tutorial002_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py
index 313bb2a04a..104554fce3 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.path_operation_advanced_configuration.tutorial003 import app
+from docs_src.path_operation_advanced_configuration.tutorial003_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
index 4f69e4646c..a95540731d 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
@@ -1,13 +1,29 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.path_operation_advanced_configuration.tutorial004 import app
-
-from ...utils import needs_pydanticv1, needs_pydanticv2
-
-client = TestClient(app)
+from ...utils import needs_py310
-def test_query_params_str_validations():
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial004_py39"),
+ pytest.param("tutorial004_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.path_operation_advanced_configuration.{request.param}"
+ )
+
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
+
+
+def test_query_params_str_validations(client: TestClient):
response = client.post("/items/", json={"name": "Foo", "price": 42})
assert response.status_code == 200, response.text
assert response.json() == {
@@ -19,8 +35,7 @@ def test_query_params_str_validations():
}
-@needs_pydanticv2
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@@ -119,100 +134,3 @@ def test_openapi_schema():
}
},
}
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_openapi_schema_pv1():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Create an item",
- "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- "required": True,
- },
- }
- }
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": {"title": "Description", "type": "string"},
- "price": {"title": "Price", "type": "number"},
- "tax": {"title": "Tax", "type": "number"},
- "tags": {
- "title": "Tags",
- "uniqueItems": True,
- "type": "array",
- "items": {"type": "string"},
- "default": [],
- },
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py
index 07e2d7d202..e2a71236ff 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.path_operation_advanced_configuration.tutorial005 import app
+from docs_src.path_operation_advanced_configuration.tutorial005_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py
index f92c59015e..9484f7f573 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.path_operation_advanced_configuration.tutorial006 import app
+from docs_src.path_operation_advanced_configuration.tutorial006_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py
index 8240b60a62..d5f284e3bd 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py
@@ -1,18 +1,24 @@
+import importlib
+
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_pydanticv2
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial007_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.path_operation_advanced_configuration.{request.param}"
+ )
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.path_operation_advanced_configuration.tutorial007 import app
-
- client = TestClient(app)
+ client = TestClient(mod.app)
return client
-@needs_pydanticv2
def test_post(client: TestClient):
yaml_data = """
name: Deadpoolio
@@ -29,7 +35,6 @@ def test_post(client: TestClient):
}
-@needs_pydanticv2
def test_post_broken_yaml(client: TestClient):
yaml_data = """
name: Deadpoolio
@@ -43,7 +48,6 @@ def test_post_broken_yaml(client: TestClient):
assert response.json() == {"detail": "Invalid YAML"}
-@needs_pydanticv2
def test_post_invalid(client: TestClient):
yaml_data = """
name: Deadpoolio
@@ -68,7 +72,6 @@ def test_post_invalid(client: TestClient):
}
-@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py
deleted file mode 100644
index ef012f8a6c..0000000000
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py
+++ /dev/null
@@ -1,106 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_pydanticv1
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.path_operation_advanced_configuration.tutorial007_pv1 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_pydanticv1
-def test_post(client: TestClient):
- yaml_data = """
- name: Deadpoolio
- tags:
- - x-force
- - x-men
- - x-avengers
- """
- response = client.post("/items/", content=yaml_data)
- assert response.status_code == 200, response.text
- assert response.json() == {
- "name": "Deadpoolio",
- "tags": ["x-force", "x-men", "x-avengers"],
- }
-
-
-@needs_pydanticv1
-def test_post_broken_yaml(client: TestClient):
- yaml_data = """
- name: Deadpoolio
- tags:
- x - x-force
- x - x-men
- x - x-avengers
- """
- response = client.post("/items/", content=yaml_data)
- assert response.status_code == 422, response.text
- assert response.json() == {"detail": "Invalid YAML"}
-
-
-@needs_pydanticv1
-def test_post_invalid(client: TestClient):
- yaml_data = """
- name: Deadpoolio
- tags:
- - x-force
- - x-men
- - x-avengers
- - sneaky: object
- """
- response = client.post("/items/", content=yaml_data)
- assert response.status_code == 422, response.text
- assert response.json() == {
- "detail": [
- {"loc": ["tags", 3], "msg": "str type expected", "type": "type_error.str"}
- ]
- }
-
-
-@needs_pydanticv1
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "post": {
- "summary": "Create Item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "content": {
- "application/x-yaml": {
- "schema": {
- "title": "Item",
- "required": ["name", "tags"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "tags": {
- "title": "Tags",
- "type": "array",
- "items": {"type": "string"},
- },
- },
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- }
- }
- },
- }
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py
new file mode 100644
index 0000000000..085d1f5e19
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py
@@ -0,0 +1,186 @@
+import importlib
+
+import pytest
+from dirty_equals import IsList
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest) -> TestClient:
+ mod = importlib.import_module(
+ f"docs_src.path_operation_configuration.{request.param}"
+ )
+ return TestClient(mod.app)
+
+
+def test_post_items(client: TestClient):
+ response = client.post(
+ "/items/",
+ json={
+ "name": "Foo",
+ "description": "Item description",
+ "price": 42.0,
+ "tax": 3.2,
+ "tags": ["bar", "baz"],
+ },
+ )
+ assert response.status_code == 201, response.text
+ assert response.json() == {
+ "name": "Foo",
+ "description": "Item description",
+ "price": 42.0,
+ "tax": 3.2,
+ "tags": IsList("bar", "baz", check_order=False),
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "description": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ "title": "Description",
+ },
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "price": {
+ "title": "Price",
+ "type": "number",
+ },
+ "tags": {
+ "default": [],
+ "items": {
+ "type": "string",
+ },
+ "title": "Tags",
+ "type": "array",
+ "uniqueItems": True,
+ },
+ "tax": {
+ "anyOf": [
+ {
+ "type": "number",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ "title": "Tax",
+ },
+ },
+ "required": [
+ "name",
+ "price",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py
new file mode 100644
index 0000000000..c7414d756a
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py
@@ -0,0 +1,223 @@
+import importlib
+
+import pytest
+from dirty_equals import IsList
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest) -> TestClient:
+ mod = importlib.import_module(
+ f"docs_src.path_operation_configuration.{request.param}"
+ )
+ return TestClient(mod.app)
+
+
+def test_post_items(client: TestClient):
+ response = client.post(
+ "/items/",
+ json={
+ "name": "Foo",
+ "description": "Item description",
+ "price": 42.0,
+ "tax": 3.2,
+ "tags": ["bar", "baz"],
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Foo",
+ "description": "Item description",
+ "price": 42.0,
+ "tax": 3.2,
+ "tags": IsList("bar", "baz", check_order=False),
+ }
+
+
+def test_get_items(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [{"name": "Foo", "price": 42}]
+
+
+def test_get_users(client: TestClient):
+ response = client.get("/users/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [{"username": "johndoe"}]
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "tags": ["items"],
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ },
+ "post": {
+ "tags": ["items"],
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
+ "/users/": {
+ "get": {
+ "tags": ["users"],
+ "summary": "Read Users",
+ "operationId": "read_users_users__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "description": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ "title": "Description",
+ },
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "price": {
+ "title": "Price",
+ "type": "number",
+ },
+ "tags": {
+ "default": [],
+ "items": {
+ "type": "string",
+ },
+ "title": "Tags",
+ "type": "array",
+ "uniqueItems": True,
+ },
+ "tax": {
+ "anyOf": [
+ {
+ "type": "number",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ "title": "Tax",
+ },
+ },
+ "required": [
+ "name",
+ "price",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py
index 58dec5769d..5a0193adfa 100644
--- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.path_operation_configuration.tutorial002b import app
+from docs_src.path_operation_configuration.tutorial002b_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py
new file mode 100644
index 0000000000..791db24625
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py
@@ -0,0 +1,208 @@
+import importlib
+from textwrap import dedent
+
+import pytest
+from dirty_equals import IsList
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+DESCRIPTIONS = {
+ "tutorial003": "Create an item with all the information, name, description, price, tax and a set of unique tags",
+ "tutorial004": dedent("""
+ Create an item with all the information:
+
+ - **name**: each item must have a name
+ - **description**: a long description
+ - **price**: required
+ - **tax**: if the item doesn't have tax, you can omit this
+ - **tags**: a set of unique tag strings for this item
+ """).strip(),
+}
+
+
+@pytest.fixture(
+ name="mod_name",
+ params=[
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial003_py310", marks=needs_py310),
+ pytest.param("tutorial004_py39"),
+ pytest.param("tutorial004_py310", marks=needs_py310),
+ ],
+)
+def get_mod_name(request: pytest.FixtureRequest) -> str:
+ return request.param
+
+
+@pytest.fixture(name="client")
+def get_client(mod_name: str) -> TestClient:
+ mod = importlib.import_module(f"docs_src.path_operation_configuration.{mod_name}")
+ return TestClient(mod.app)
+
+
+def test_post_items(client: TestClient):
+ response = client.post(
+ "/items/",
+ json={
+ "name": "Foo",
+ "description": "Item description",
+ "price": 42.0,
+ "tax": 3.2,
+ "tags": ["bar", "baz"],
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Foo",
+ "description": "Item description",
+ "price": 42.0,
+ "tax": 3.2,
+ "tags": IsList("bar", "baz", check_order=False),
+ }
+
+
+def test_openapi_schema(client: TestClient, mod_name: str):
+ mod_name = mod_name[:11]
+
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "summary": "Create an item",
+ "description": DESCRIPTIONS[mod_name],
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "description": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ "title": "Description",
+ },
+ "name": {
+ "title": "Name",
+ "type": "string",
+ },
+ "price": {
+ "title": "Price",
+ "type": "number",
+ },
+ "tags": {
+ "default": [],
+ "items": {
+ "type": "string",
+ },
+ "title": "Tags",
+ "type": "array",
+ "uniqueItems": True,
+ },
+ "tax": {
+ "anyOf": [
+ {
+ "type": "number",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ "title": "Tax",
+ },
+ },
+ "required": [
+ "name",
+ "price",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
index 0742f5d0e8..c5a3aec1d9 100644
--- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
@@ -3,14 +3,13 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial005",
- pytest.param("tutorial005_py39", marks=needs_py39),
+ pytest.param("tutorial005_py39"),
pytest.param("tutorial005_py310", marks=needs_py310),
],
)
@@ -35,7 +34,6 @@ def test_query_params_str_validations(client: TestClient):
}
-@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
@@ -135,100 +133,3 @@ def test_openapi_schema(client: TestClient):
}
},
}
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_openapi_schema_pv1(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "post": {
- "responses": {
- "200": {
- "description": "The created item",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Create an item",
- "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- "required": True,
- },
- }
- }
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": {"title": "Description", "type": "string"},
- "price": {"title": "Price", "type": "number"},
- "tax": {"title": "Tax", "type": "number"},
- "tags": {
- "title": "Tags",
- "uniqueItems": True,
- "type": "array",
- "items": {"type": "string"},
- "default": [],
- },
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py
index 91180d1097..5d9c55675f 100644
--- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py
@@ -1,7 +1,7 @@
import pytest
from fastapi.testclient import TestClient
-from docs_src.path_operation_configuration.tutorial006 import app
+from docs_src.path_operation_configuration.tutorial006_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_path_params/test_tutorial001.py b/tests/test_tutorial/test_path_params/test_tutorial001.py
new file mode 100644
index 0000000000..a898e386fb
--- /dev/null
+++ b/tests/test_tutorial/test_path_params/test_tutorial001.py
@@ -0,0 +1,116 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from docs_src.path_params.tutorial001_py39 import app
+
+client = TestClient(app)
+
+
+@pytest.mark.parametrize(
+ ("item_id", "expected_response"),
+ [
+ (1, {"item_id": "1"}),
+ ("alice", {"item_id": "alice"}),
+ ],
+)
+def test_get_items(item_id, expected_response):
+ response = client.get(f"/items/{item_id}")
+ assert response.status_code == 200, response.text
+ assert response.json() == expected_response
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "item_id",
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ },
+ },
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Read Item",
+ },
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_path_params/test_tutorial002.py b/tests/test_tutorial/test_path_params/test_tutorial002.py
new file mode 100644
index 0000000000..0bfc9f807e
--- /dev/null
+++ b/tests/test_tutorial/test_path_params/test_tutorial002.py
@@ -0,0 +1,124 @@
+from fastapi.testclient import TestClient
+
+from docs_src.path_params.tutorial002_py39 import app
+
+client = TestClient(app)
+
+
+def test_get_items():
+ response = client.get("/items/1")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"item_id": 1}
+
+
+def test_get_items_invalid_id():
+ response = client.get("/items/item1")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "input": "item1",
+ "loc": ["path", "item_id"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "type": "int_parsing",
+ }
+ ]
+ }
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "item_id",
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "integer",
+ },
+ },
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Read Item",
+ },
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_path_params/test_tutorial003.py b/tests/test_tutorial/test_path_params/test_tutorial003.py
new file mode 100644
index 0000000000..cd2c39ab06
--- /dev/null
+++ b/tests/test_tutorial/test_path_params/test_tutorial003.py
@@ -0,0 +1,133 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from docs_src.path_params.tutorial003_py39 import app
+
+client = TestClient(app)
+
+
+@pytest.mark.parametrize(
+ ("user_id", "expected_response"),
+ [
+ ("me", {"user_id": "the current user"}),
+ ("alice", {"user_id": "alice"}),
+ ],
+)
+def test_get_users(user_id: str, expected_response: dict):
+ response = client.get(f"/users/{user_id}")
+ assert response.status_code == 200, response.text
+ assert response.json() == expected_response
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/users/me": {
+ "get": {
+ "operationId": "read_user_me_users_me_get",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "Successful Response",
+ },
+ },
+ "summary": "Read User Me",
+ },
+ },
+ "/users/{user_id}": {
+ "get": {
+ "operationId": "read_user_users__user_id__get",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "user_id",
+ "required": True,
+ "schema": {
+ "title": "User Id",
+ "type": "string",
+ },
+ },
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Read User",
+ },
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_path_params/test_tutorial003b.py b/tests/test_tutorial/test_path_params/test_tutorial003b.py
new file mode 100644
index 0000000000..8e4a26a1ca
--- /dev/null
+++ b/tests/test_tutorial/test_path_params/test_tutorial003b.py
@@ -0,0 +1,44 @@
+import asyncio
+
+from fastapi.testclient import TestClient
+
+from docs_src.path_params.tutorial003b_py39 import app, read_users2
+
+client = TestClient(app)
+
+
+def test_get_users():
+ response = client.get("/users")
+ assert response.status_code == 200, response.text
+ assert response.json() == ["Rick", "Morty"]
+
+
+def test_read_users2(): # Just for coverage
+ assert asyncio.run(read_users2()) == ["Bean", "Elfo"]
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/users": {
+ "get": {
+ "operationId": "read_users2_users_get",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "Successful Response",
+ },
+ },
+ "summary": "Read Users2",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_path_params/test_tutorial004.py b/tests/test_tutorial/test_path_params/test_tutorial004.py
index acbeaca769..f7f233ccff 100644
--- a/tests/test_tutorial/test_path_params/test_tutorial004.py
+++ b/tests/test_tutorial/test_path_params/test_tutorial004.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.path_params.tutorial004 import app
+from docs_src.path_params.tutorial004_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_path_params/test_tutorial005.py b/tests/test_tutorial/test_path_params/test_tutorial005.py
index 2e4b0146be..86ccce7b6d 100644
--- a/tests/test_tutorial/test_path_params/test_tutorial005.py
+++ b/tests/test_tutorial/test_path_params/test_tutorial005.py
@@ -1,7 +1,6 @@
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from docs_src.path_params.tutorial005 import app
+from docs_src.path_params.tutorial005_py39 import app
client = TestClient(app)
@@ -27,31 +26,17 @@ def test_get_enums_resnet():
def test_get_enums_invalid():
response = client.get("/models/foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "enum",
- "loc": ["path", "model_name"],
- "msg": "Input should be 'alexnet', 'resnet' or 'lenet'",
- "input": "foo",
- "ctx": {"expected": "'alexnet', 'resnet' or 'lenet'"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"enum_values": ["alexnet", "resnet", "lenet"]},
- "loc": ["path", "model_name"],
- "msg": "value is not a valid enumeration member; permitted: 'alexnet', 'resnet', 'lenet'",
- "type": "type_error.enum",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "enum",
+ "loc": ["path", "model_name"],
+ "msg": "Input should be 'alexnet', 'resnet' or 'lenet'",
+ "input": "foo",
+ "ctx": {"expected": "'alexnet', 'resnet' or 'lenet'"},
+ }
+ ]
+ }
def test_openapi_schema():
@@ -106,22 +91,11 @@ def test_openapi_schema():
}
},
},
- "ModelName": IsDict(
- {
- "title": "ModelName",
- "enum": ["alexnet", "resnet", "lenet"],
- "type": "string",
- }
- )
- | IsDict(
- {
- # TODO: remove when deprecating Pydantic v1
- "title": "ModelName",
- "enum": ["alexnet", "resnet", "lenet"],
- "type": "string",
- "description": "An enumeration.",
- }
- ),
+ "ModelName": {
+ "title": "ModelName",
+ "enum": ["alexnet", "resnet", "lenet"],
+ "type": "string",
+ },
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
diff --git a/tests/test_tutorial/test_path_params_numeric_validations/__init__.py b/tests/test_tutorial/test_path_params_numeric_validations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py
new file mode 100644
index 0000000000..f1e3041030
--- /dev/null
+++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py
@@ -0,0 +1,164 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ pytest.param("tutorial001_an_py39"),
+ pytest.param("tutorial001_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest) -> TestClient:
+ mod = importlib.import_module(
+ f"docs_src.path_params_numeric_validations.{request.param}"
+ )
+ return TestClient(mod.app)
+
+
+@pytest.mark.parametrize(
+ "path,expected_response",
+ [
+ ("/items/42", {"item_id": 42}),
+ ("/items/123?item-query=somequery", {"item_id": 123, "q": "somequery"}),
+ ],
+)
+def test_read_items(client: TestClient, path, expected_response):
+ response = client.get(path)
+ assert response.status_code == 200, response.text
+ assert response.json() == expected_response
+
+
+def test_read_items_invalid_item_id(client: TestClient):
+ response = client.get("/items/invalid_id")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "input": "invalid_id",
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "type": "int_parsing",
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "title": "The ID of the item to get",
+ "type": "integer",
+ },
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ "title": "Item-Query",
+ },
+ "name": "item-query",
+ "in": "query",
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {},
+ }
+ },
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py
new file mode 100644
index 0000000000..467c915dcd
--- /dev/null
+++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py
@@ -0,0 +1,170 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_an_py39"),
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial003_an_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest) -> TestClient:
+ mod = importlib.import_module(
+ f"docs_src.path_params_numeric_validations.{request.param}"
+ )
+ return TestClient(mod.app)
+
+
+@pytest.mark.parametrize(
+ "path,expected_response",
+ [
+ ("/items/42?q=", {"item_id": 42}),
+ ("/items/123?q=somequery", {"item_id": 123, "q": "somequery"}),
+ ],
+)
+def test_read_items(client: TestClient, path, expected_response):
+ response = client.get(path)
+ assert response.status_code == 200, response.text
+ assert response.json() == expected_response
+
+
+def test_read_items_invalid_item_id(client: TestClient):
+ response = client.get("/items/invalid_id?q=somequery")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "input": "invalid_id",
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "type": "int_parsing",
+ }
+ ]
+ }
+
+
+def test_read_items_missing_q(client: TestClient):
+ response = client.get("/items/42")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["query", "q"],
+ "input": None,
+ "msg": "Field required",
+ "type": "missing",
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "title": "The ID of the item to get",
+ "type": "integer",
+ },
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": True,
+ "schema": {
+ "type": "string",
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {},
+ }
+ },
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py
new file mode 100644
index 0000000000..d3593c984c
--- /dev/null
+++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py
@@ -0,0 +1,185 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial004_py39"),
+ pytest.param("tutorial004_an_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest) -> TestClient:
+ mod = importlib.import_module(
+ f"docs_src.path_params_numeric_validations.{request.param}"
+ )
+ return TestClient(mod.app)
+
+
+@pytest.mark.parametrize(
+ "path,expected_response",
+ [
+ ("/items/42?q=", {"item_id": 42}),
+ ("/items/1?q=somequery", {"item_id": 1, "q": "somequery"}),
+ ],
+)
+def test_read_items(client: TestClient, path, expected_response):
+ response = client.get(path)
+ assert response.status_code == 200, response.text
+ assert response.json() == expected_response
+
+
+def test_read_items_non_int_item_id(client: TestClient):
+ response = client.get("/items/invalid_id?q=somequery")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "input": "invalid_id",
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "type": "int_parsing",
+ }
+ ]
+ }
+
+
+def test_read_items_item_id_less_than_one(client: TestClient):
+ response = client.get("/items/0?q=somequery")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "input": "0",
+ "msg": "Input should be greater than or equal to 1",
+ "type": "greater_than_equal",
+ "ctx": {"ge": 1},
+ }
+ ]
+ }
+
+
+def test_read_items_missing_q(client: TestClient):
+ response = client.get("/items/42")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["query", "q"],
+ "input": None,
+ "msg": "Field required",
+ "type": "missing",
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "title": "The ID of the item to get",
+ "type": "integer",
+ "minimum": 1,
+ },
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": True,
+ "schema": {
+ "type": "string",
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {},
+ }
+ },
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py
new file mode 100644
index 0000000000..296192593b
--- /dev/null
+++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py
@@ -0,0 +1,202 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial005_py39"),
+ pytest.param("tutorial005_an_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest) -> TestClient:
+ mod = importlib.import_module(
+ f"docs_src.path_params_numeric_validations.{request.param}"
+ )
+ return TestClient(mod.app)
+
+
+@pytest.mark.parametrize(
+ "path,expected_response",
+ [
+ ("/items/1?q=", {"item_id": 1}),
+ ("/items/1000?q=somequery", {"item_id": 1000, "q": "somequery"}),
+ ],
+)
+def test_read_items(client: TestClient, path, expected_response):
+ response = client.get(path)
+ assert response.status_code == 200, response.text
+ assert response.json() == expected_response
+
+
+def test_read_items_non_int_item_id(client: TestClient):
+ response = client.get("/items/invalid_id?q=somequery")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "input": "invalid_id",
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "type": "int_parsing",
+ }
+ ]
+ }
+
+
+def test_read_items_item_id_less_than_one(client: TestClient):
+ response = client.get("/items/0?q=somequery")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "input": "0",
+ "msg": "Input should be greater than 0",
+ "type": "greater_than",
+ "ctx": {"gt": 0},
+ }
+ ]
+ }
+
+
+def test_read_items_item_id_greater_than_one_thousand(client: TestClient):
+ response = client.get("/items/1001?q=somequery")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "input": "1001",
+ "msg": "Input should be less than or equal to 1000",
+ "type": "less_than_equal",
+ "ctx": {"le": 1000},
+ }
+ ]
+ }
+
+
+def test_read_items_missing_q(client: TestClient):
+ response = client.get("/items/42")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["query", "q"],
+ "input": None,
+ "msg": "Field required",
+ "type": "missing",
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "title": "The ID of the item to get",
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "maximum": 1000,
+ },
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": True,
+ "schema": {
+ "type": "string",
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {},
+ }
+ },
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py
new file mode 100644
index 0000000000..9dc7d7aac2
--- /dev/null
+++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py
@@ -0,0 +1,221 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial006_py39"),
+ pytest.param("tutorial006_an_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest) -> TestClient:
+ mod = importlib.import_module(
+ f"docs_src.path_params_numeric_validations.{request.param}"
+ )
+ return TestClient(mod.app)
+
+
+@pytest.mark.parametrize(
+ "path,expected_response",
+ [
+ (
+ "/items/0?q=&size=0.1",
+ {"item_id": 0, "size": 0.1},
+ ),
+ (
+ "/items/1000?q=somequery&size=10.4",
+ {"item_id": 1000, "q": "somequery", "size": 10.4},
+ ),
+ ],
+)
+def test_read_items(client: TestClient, path, expected_response):
+ response = client.get(path)
+ assert response.status_code == 200, response.text
+ assert response.json() == expected_response
+
+
+def test_read_items_item_id_less_than_zero(client: TestClient):
+ response = client.get("/items/-1?q=somequery&size=5")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "input": "-1",
+ "msg": "Input should be greater than or equal to 0",
+ "type": "greater_than_equal",
+ "ctx": {"ge": 0},
+ }
+ ]
+ }
+
+
+def test_read_items_item_id_greater_than_one_thousand(client: TestClient):
+ response = client.get("/items/1001?q=somequery&size=5")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "input": "1001",
+ "msg": "Input should be less than or equal to 1000",
+ "type": "less_than_equal",
+ "ctx": {"le": 1000},
+ }
+ ]
+ }
+
+
+def test_read_items_size_too_small(client: TestClient):
+ response = client.get("/items/1?q=somequery&size=0.0")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["query", "size"],
+ "input": "0.0",
+ "msg": "Input should be greater than 0",
+ "type": "greater_than",
+ "ctx": {"gt": 0.0},
+ }
+ ]
+ }
+
+
+def test_read_items_size_too_large(client: TestClient):
+ response = client.get("/items/1?q=somequery&size=10.5")
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["query", "size"],
+ "input": "10.5",
+ "msg": "Input should be less than 10.5",
+ "type": "less_than",
+ "ctx": {"lt": 10.5},
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "title": "The ID of the item to get",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 1000,
+ },
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": True,
+ "schema": {
+ "type": "string",
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "in": "query",
+ "name": "size",
+ "required": True,
+ "schema": {
+ "exclusiveMaximum": 10.5,
+ "exclusiveMinimum": 0,
+ "title": "Size",
+ "type": "number",
+ },
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {},
+ }
+ },
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py
deleted file mode 100644
index 3075a05f51..0000000000
--- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py
+++ /dev/null
@@ -1,37 +0,0 @@
-import sys
-from typing import Any
-
-import pytest
-from fastapi._compat import PYDANTIC_V2
-
-from tests.utils import skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-
-if not PYDANTIC_V2:
- pytest.skip("This test is only for Pydantic v2", allow_module_level=True)
-
-import importlib
-
-import pytest
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(
- name="mod",
- params=[
- "tutorial001_an",
- pytest.param("tutorial001_an_py310", marks=needs_py310),
- ],
-)
-def get_mod(request: pytest.FixtureRequest):
- mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}")
- return mod
-
-
-def test_model(mod: Any):
- item = mod.Item(name="Foo", size=3.4)
- assert item.dict() == {"name": "Foo", "description": None, "size": 3.4}
diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py
deleted file mode 100644
index a402c663d1..0000000000
--- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py
+++ /dev/null
@@ -1,140 +0,0 @@
-import sys
-
-import pytest
-from fastapi._compat import PYDANTIC_V2
-from inline_snapshot import snapshot
-
-from tests.utils import skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-
-if not PYDANTIC_V2:
- pytest.skip("This test is only for Pydantic v2", allow_module_level=True)
-
-import importlib
-
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(
- name="client",
- params=[
- "tutorial002_an",
- pytest.param("tutorial002_an_py310", marks=needs_py310),
- ],
-)
-def get_client(request: pytest.FixtureRequest):
- mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}")
-
- c = TestClient(mod.app)
- return c
-
-
-def test_call(client: TestClient):
- response = client.post("/items/", json={"name": "Foo", "size": 3.4})
- assert response.status_code == 200, response.text
- assert response.json() == {
- "name": "Foo",
- "description": None,
- "size": 3.4,
- }
-
-
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "post": {
- "summary": "Create Item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- }
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {"type": "string", "title": "Description"},
- "size": {"type": "number", "title": "Size"},
- },
- "type": "object",
- "required": ["name", "size"],
- "title": "Item",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py
deleted file mode 100644
index 03155c9249..0000000000
--- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py
+++ /dev/null
@@ -1,154 +0,0 @@
-import sys
-
-import pytest
-from fastapi._compat import PYDANTIC_V2
-from inline_snapshot import snapshot
-
-from tests.utils import skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-if not PYDANTIC_V2:
- pytest.skip("This test is only for Pydantic v2", allow_module_level=True)
-
-
-import importlib
-
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(
- name="client",
- params=[
- "tutorial003_an",
- pytest.param("tutorial003_an_py310", marks=needs_py310),
- ],
-)
-def get_client(request: pytest.FixtureRequest):
- mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}")
-
- c = TestClient(mod.app)
- return c
-
-
-def test_call(client: TestClient):
- response = client.post("/items/", json={"name": "Foo", "size": 3.4})
- assert response.status_code == 200, response.text
- assert response.json() == {
- "name": "Foo",
- "description": None,
- "size": 3.4,
- }
-
-
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "post": {
- "summary": "Create Item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/ItemV2"
- }
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- }
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {"type": "string", "title": "Description"},
- "size": {"type": "number", "title": "Size"},
- },
- "type": "object",
- "required": ["name", "size"],
- "title": "Item",
- },
- "ItemV2": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Description",
- },
- "size": {"type": "number", "title": "Size"},
- },
- "type": "object",
- "required": ["name", "size"],
- "title": "ItemV2",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py
deleted file mode 100644
index d2e204ddaf..0000000000
--- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py
+++ /dev/null
@@ -1,153 +0,0 @@
-import sys
-
-import pytest
-from fastapi._compat import PYDANTIC_V2
-from inline_snapshot import snapshot
-
-from tests.utils import skip_module_if_py_gte_314
-
-if sys.version_info >= (3, 14):
- skip_module_if_py_gte_314()
-
-if not PYDANTIC_V2:
- pytest.skip("This test is only for Pydantic v2", allow_module_level=True)
-
-
-import importlib
-
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39, needs_py310
-
-
-@pytest.fixture(
- name="client",
- params=[
- "tutorial004_an",
- pytest.param("tutorial004_an_py39", marks=needs_py39),
- pytest.param("tutorial004_an_py310", marks=needs_py310),
- ],
-)
-def get_client(request: pytest.FixtureRequest):
- mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}")
-
- c = TestClient(mod.app)
- return c
-
-
-def test_call(client: TestClient):
- response = client.post("/items/", json={"item": {"name": "Foo", "size": 3.4}})
- assert response.status_code == 200, response.text
- assert response.json() == {
- "name": "Foo",
- "description": None,
- "size": 3.4,
- }
-
-
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == snapshot(
- {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "post": {
- "summary": "Create Item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_item_items__post"
- }
- ],
- "title": "Body",
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- }
- },
- "components": {
- "schemas": {
- "Body_create_item_items__post": {
- "properties": {
- "item": {
- "allOf": [{"$ref": "#/components/schemas/Item"}],
- "title": "Item",
- }
- },
- "type": "object",
- "required": ["item"],
- "title": "Body_create_item_items__post",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {"type": "string", "title": "Description"},
- "size": {"type": "number", "title": "Size"},
- },
- "type": "object",
- "required": ["name", "size"],
- "title": "Item",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- "type": "array",
- "title": "Location",
- },
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
- },
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
- )
diff --git a/tests/test_tutorial/test_python_types/__init__.py b/tests/test_tutorial/test_python_types/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_python_types/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_python_types/test_tutorial001_tutorial002.py
new file mode 100644
index 0000000000..ccb0968576
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial001_tutorial002.py
@@ -0,0 +1,18 @@
+import runpy
+from unittest.mock import patch
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ "module_name",
+ [
+ "tutorial001_py39",
+ "tutorial002_py39",
+ ],
+)
+def test_run_module(module_name: str):
+ with patch("builtins.print") as mock_print:
+ runpy.run_module(f"docs_src.python_types.{module_name}", run_name="__main__")
+
+ mock_print.assert_called_with("John Doe")
diff --git a/tests/test_tutorial/test_python_types/test_tutorial003.py b/tests/test_tutorial/test_python_types/test_tutorial003.py
new file mode 100644
index 0000000000..34d2649171
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial003.py
@@ -0,0 +1,12 @@
+import pytest
+
+from docs_src.python_types.tutorial003_py39 import get_name_with_age
+
+
+def test_get_name_with_age_pass_int():
+ with pytest.raises(TypeError):
+ get_name_with_age("John", 30)
+
+
+def test_get_name_with_age_pass_str():
+ assert get_name_with_age("John", "30") == "John is this old: 30"
diff --git a/tests/test_tutorial/test_python_types/test_tutorial004.py b/tests/test_tutorial/test_python_types/test_tutorial004.py
new file mode 100644
index 0000000000..24af32883e
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial004.py
@@ -0,0 +1,5 @@
+from docs_src.python_types.tutorial004_py39 import get_name_with_age
+
+
+def test_get_name_with_age_pass_int():
+ assert get_name_with_age("John", 30) == "John is this old: 30"
diff --git a/tests/test_tutorial/test_python_types/test_tutorial005.py b/tests/test_tutorial/test_python_types/test_tutorial005.py
new file mode 100644
index 0000000000..6d67ec4716
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial005.py
@@ -0,0 +1,12 @@
+from docs_src.python_types.tutorial005_py39 import get_items
+
+
+def test_get_items():
+ res = get_items(
+ "item_a",
+ "item_b",
+ "item_c",
+ "item_d",
+ "item_e",
+ )
+ assert res == ("item_a", "item_b", "item_c", "item_d", "item_e")
diff --git a/tests/test_tutorial/test_python_types/test_tutorial006.py b/tests/test_tutorial/test_python_types/test_tutorial006.py
new file mode 100644
index 0000000000..50976926e7
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial006.py
@@ -0,0 +1,16 @@
+from unittest.mock import patch
+
+from docs_src.python_types.tutorial006_py39 import process_items
+
+
+def test_process_items():
+ with patch("builtins.print") as mock_print:
+ process_items(["item_a", "item_b", "item_c"])
+
+ assert mock_print.call_count == 3
+ call_args = [arg.args for arg in mock_print.call_args_list]
+ assert call_args == [
+ ("item_a",),
+ ("item_b",),
+ ("item_c",),
+ ]
diff --git a/tests/test_tutorial/test_python_types/test_tutorial007.py b/tests/test_tutorial/test_python_types/test_tutorial007.py
new file mode 100644
index 0000000000..c045294652
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial007.py
@@ -0,0 +1,8 @@
+from docs_src.python_types.tutorial007_py39 import process_items
+
+
+def test_process_items():
+ items_t = (1, 2, "foo")
+ items_s = {b"a", b"b", b"c"}
+
+ assert process_items(items_t, items_s) == (items_t, items_s)
diff --git a/tests/test_tutorial/test_python_types/test_tutorial008.py b/tests/test_tutorial/test_python_types/test_tutorial008.py
new file mode 100644
index 0000000000..33cf6cbfbc
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial008.py
@@ -0,0 +1,17 @@
+from unittest.mock import patch
+
+from docs_src.python_types.tutorial008_py39 import process_items
+
+
+def test_process_items():
+ with patch("builtins.print") as mock_print:
+ process_items({"a": 1.0, "b": 2.5})
+
+ assert mock_print.call_count == 4
+ call_args = [arg.args for arg in mock_print.call_args_list]
+ assert call_args == [
+ ("a",),
+ (1.0,),
+ ("b",),
+ (2.5,),
+ ]
diff --git a/tests/test_tutorial/test_python_types/test_tutorial008b.py b/tests/test_tutorial/test_python_types/test_tutorial008b.py
new file mode 100644
index 0000000000..1ef0d4ea16
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial008b.py
@@ -0,0 +1,27 @@
+import importlib
+from types import ModuleType
+from unittest.mock import patch
+
+import pytest
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="module",
+ params=[
+ pytest.param("tutorial008b_py39"),
+ pytest.param("tutorial008b_py310", marks=needs_py310),
+ ],
+)
+def get_module(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.python_types.{request.param}")
+ return mod
+
+
+def test_process_items(module: ModuleType):
+ with patch("builtins.print") as mock_print:
+ module.process_item("a")
+
+ assert mock_print.call_count == 1
+ mock_print.assert_called_with("a")
diff --git a/tests/test_tutorial/test_python_types/test_tutorial009_tutorial009b.py b/tests/test_tutorial/test_python_types/test_tutorial009_tutorial009b.py
new file mode 100644
index 0000000000..34046c5c48
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial009_tutorial009b.py
@@ -0,0 +1,33 @@
+import importlib
+from types import ModuleType
+from unittest.mock import patch
+
+import pytest
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="module",
+ params=[
+ pytest.param("tutorial009_py39"),
+ pytest.param("tutorial009_py310", marks=needs_py310),
+ pytest.param("tutorial009b_py39"),
+ ],
+)
+def get_module(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.python_types.{request.param}")
+ return mod
+
+
+def test_say_hi(module: ModuleType):
+ with patch("builtins.print") as mock_print:
+ module.say_hi("FastAPI")
+ module.say_hi()
+
+ assert mock_print.call_count == 2
+ call_args = [arg.args for arg in mock_print.call_args_list]
+ assert call_args == [
+ ("Hey FastAPI!",),
+ ("Hello World",),
+ ]
diff --git a/tests/test_tutorial/test_python_types/test_tutorial009c.py b/tests/test_tutorial/test_python_types/test_tutorial009c.py
new file mode 100644
index 0000000000..7bd4049113
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial009c.py
@@ -0,0 +1,33 @@
+import importlib
+import re
+from types import ModuleType
+from unittest.mock import patch
+
+import pytest
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="module",
+ params=[
+ pytest.param("tutorial009c_py39"),
+ pytest.param("tutorial009c_py310", marks=needs_py310),
+ ],
+)
+def get_module(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.python_types.{request.param}")
+ return mod
+
+
+def test_say_hi(module: ModuleType):
+ with patch("builtins.print") as mock_print:
+ module.say_hi("FastAPI")
+
+ mock_print.assert_called_once_with("Hey FastAPI!")
+
+ with pytest.raises(
+ TypeError,
+ match=re.escape("say_hi() missing 1 required positional argument: 'name'"),
+ ):
+ module.say_hi()
diff --git a/tests/test_tutorial/test_python_types/test_tutorial010.py b/tests/test_tutorial/test_python_types/test_tutorial010.py
new file mode 100644
index 0000000000..9e4d2e36bf
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial010.py
@@ -0,0 +1,5 @@
+from docs_src.python_types.tutorial010_py39 import Person, get_person_name
+
+
+def test_get_person_name():
+ assert get_person_name(Person("John Doe")) == "John Doe"
diff --git a/tests/test_tutorial/test_python_types/test_tutorial011.py b/tests/test_tutorial/test_python_types/test_tutorial011.py
new file mode 100644
index 0000000000..a05751b974
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial011.py
@@ -0,0 +1,25 @@
+import runpy
+from unittest.mock import patch
+
+import pytest
+
+from ...utils import needs_py310
+
+
+@pytest.mark.parametrize(
+ "module_name",
+ [
+ pytest.param("tutorial011_py39"),
+ pytest.param("tutorial011_py310", marks=needs_py310),
+ ],
+)
+def test_run_module(module_name: str):
+ with patch("builtins.print") as mock_print:
+ runpy.run_module(f"docs_src.python_types.{module_name}", run_name="__main__")
+
+ assert mock_print.call_count == 2
+ call_args = [str(arg.args[0]) for arg in mock_print.call_args_list]
+ assert call_args == [
+ "id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]",
+ "123",
+ ]
diff --git a/tests/test_tutorial/test_python_types/test_tutorial012.py b/tests/test_tutorial/test_python_types/test_tutorial012.py
new file mode 100644
index 0000000000..e578048204
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial012.py
@@ -0,0 +1,7 @@
+from docs_src.python_types.tutorial012_py39 import User
+
+
+def test_user():
+ user = User(name="John Doe", age=30)
+ assert user.name == "John Doe"
+ assert user.age == 30
diff --git a/tests/test_tutorial/test_python_types/test_tutorial013.py b/tests/test_tutorial/test_python_types/test_tutorial013.py
new file mode 100644
index 0000000000..5602ef76f8
--- /dev/null
+++ b/tests/test_tutorial/test_python_types/test_tutorial013.py
@@ -0,0 +1,5 @@
+from docs_src.python_types.tutorial013_py39 import say_hello
+
+
+def test_say_hello():
+ assert say_hello("FastAPI") == "Hello FastAPI"
diff --git a/tests/test_tutorial/test_query_param_models/test_tutorial001.py b/tests/test_tutorial/test_query_param_models/test_tutorial001.py
index 5b7bc7b424..d3ce57121d 100644
--- a/tests/test_tutorial/test_query_param_models/test_tutorial001.py
+++ b/tests/test_tutorial/test_query_param_models/test_tutorial001.py
@@ -1,21 +1,18 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from tests.utils import needs_py39, needs_py310
+from tests.utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
- pytest.param("tutorial001_py39", marks=needs_py39),
+ pytest.param("tutorial001_py39"),
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ pytest.param("tutorial001_an_py39"),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
@@ -67,61 +64,31 @@ def test_query_param_model_invalid(client: TestClient):
)
assert response.status_code == 422
assert response.json() == snapshot(
- IsDict(
- {
- "detail": [
- {
- "type": "less_than_equal",
- "loc": ["query", "limit"],
- "msg": "Input should be less than or equal to 100",
- "input": "150",
- "ctx": {"le": 100},
- },
- {
- "type": "greater_than_equal",
- "loc": ["query", "offset"],
- "msg": "Input should be greater than or equal to 0",
- "input": "-1",
- "ctx": {"ge": 0},
- },
- {
- "type": "literal_error",
- "loc": ["query", "order_by"],
- "msg": "Input should be 'created_at' or 'updated_at'",
- "input": "invalid",
- "ctx": {"expected": "'created_at' or 'updated_at'"},
- },
- ]
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "type": "value_error.number.not_le",
- "loc": ["query", "limit"],
- "msg": "ensure this value is less than or equal to 100",
- "ctx": {"limit_value": 100},
- },
- {
- "type": "value_error.number.not_ge",
- "loc": ["query", "offset"],
- "msg": "ensure this value is greater than or equal to 0",
- "ctx": {"limit_value": 0},
- },
- {
- "type": "value_error.const",
- "loc": ["query", "order_by"],
- "msg": "unexpected value; permitted: 'created_at', 'updated_at'",
- "ctx": {
- "given": "invalid",
- "permitted": ["created_at", "updated_at"],
- },
- },
- ]
- }
- )
+ {
+ "detail": [
+ {
+ "type": "less_than_equal",
+ "loc": ["query", "limit"],
+ "msg": "Input should be less than or equal to 100",
+ "input": "150",
+ "ctx": {"le": 100},
+ },
+ {
+ "type": "greater_than_equal",
+ "loc": ["query", "offset"],
+ "msg": "Input should be greater than or equal to 0",
+ "input": "-1",
+ "ctx": {"ge": 0},
+ },
+ {
+ "type": "literal_error",
+ "loc": ["query", "order_by"],
+ "msg": "Input should be 'created_at' or 'updated_at'",
+ "input": "invalid",
+ "ctx": {"expected": "'created_at' or 'updated_at'"},
+ },
+ ]
+ }
)
diff --git a/tests/test_tutorial/test_query_param_models/test_tutorial002.py b/tests/test_tutorial/test_query_param_models/test_tutorial002.py
index 4432c9d8a3..96abce6ab9 100644
--- a/tests/test_tutorial/test_query_param_models/test_tutorial002.py
+++ b/tests/test_tutorial/test_query_param_models/test_tutorial002.py
@@ -1,28 +1,19 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
+from tests.utils import needs_py310
@pytest.fixture(
name="client",
params=[
- pytest.param("tutorial002", marks=needs_pydanticv2),
- pytest.param("tutorial002_py39", marks=[needs_py39, needs_pydanticv2]),
- pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]),
- pytest.param("tutorial002_an", marks=needs_pydanticv2),
- pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]),
- pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]),
- pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]),
- pytest.param("tutorial002_pv1_py39", marks=[needs_py39, needs_pydanticv1]),
- pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]),
- pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]),
- pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]),
- pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]),
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=[needs_py310]),
+ pytest.param("tutorial002_an_py39"),
+ pytest.param("tutorial002_an_py310", marks=[needs_py310]),
],
)
def get_client(request: pytest.FixtureRequest):
@@ -73,61 +64,31 @@ def test_query_param_model_invalid(client: TestClient):
)
assert response.status_code == 422
assert response.json() == snapshot(
- IsDict(
- {
- "detail": [
- {
- "type": "less_than_equal",
- "loc": ["query", "limit"],
- "msg": "Input should be less than or equal to 100",
- "input": "150",
- "ctx": {"le": 100},
- },
- {
- "type": "greater_than_equal",
- "loc": ["query", "offset"],
- "msg": "Input should be greater than or equal to 0",
- "input": "-1",
- "ctx": {"ge": 0},
- },
- {
- "type": "literal_error",
- "loc": ["query", "order_by"],
- "msg": "Input should be 'created_at' or 'updated_at'",
- "input": "invalid",
- "ctx": {"expected": "'created_at' or 'updated_at'"},
- },
- ]
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "type": "value_error.number.not_le",
- "loc": ["query", "limit"],
- "msg": "ensure this value is less than or equal to 100",
- "ctx": {"limit_value": 100},
- },
- {
- "type": "value_error.number.not_ge",
- "loc": ["query", "offset"],
- "msg": "ensure this value is greater than or equal to 0",
- "ctx": {"limit_value": 0},
- },
- {
- "type": "value_error.const",
- "loc": ["query", "order_by"],
- "msg": "unexpected value; permitted: 'created_at', 'updated_at'",
- "ctx": {
- "given": "invalid",
- "permitted": ["created_at", "updated_at"],
- },
- },
- ]
- }
- )
+ {
+ "detail": [
+ {
+ "type": "less_than_equal",
+ "loc": ["query", "limit"],
+ "msg": "Input should be less than or equal to 100",
+ "input": "150",
+ "ctx": {"le": 100},
+ },
+ {
+ "type": "greater_than_equal",
+ "loc": ["query", "offset"],
+ "msg": "Input should be greater than or equal to 0",
+ "input": "-1",
+ "ctx": {"ge": 0},
+ },
+ {
+ "type": "literal_error",
+ "loc": ["query", "order_by"],
+ "msg": "Input should be 'created_at' or 'updated_at'",
+ "input": "invalid",
+ "ctx": {"expected": "'created_at' or 'updated_at'"},
+ },
+ ]
+ }
)
@@ -146,22 +107,12 @@ def test_query_param_model_extra(client: TestClient):
assert response.json() == snapshot(
{
"detail": [
- IsDict(
- {
- "type": "extra_forbidden",
- "loc": ["query", "tool"],
- "msg": "Extra inputs are not permitted",
- "input": "plumbus",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "value_error.extra",
- "loc": ["query", "tool"],
- "msg": "extra fields not permitted",
- }
- )
+ {
+ "type": "extra_forbidden",
+ "loc": ["query", "tool"],
+ "msg": "Extra inputs are not permitted",
+ "input": "plumbus",
+ }
]
}
)
diff --git a/tests/test_tutorial/test_query_params/test_tutorial001.py b/tests/test_tutorial/test_query_params/test_tutorial001.py
new file mode 100644
index 0000000000..4c92b57b8d
--- /dev/null
+++ b/tests/test_tutorial/test_query_params/test_tutorial001.py
@@ -0,0 +1,126 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.query_params.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+@pytest.mark.parametrize(
+ ("path", "expected_json"),
+ [
+ (
+ "/items/",
+ [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}],
+ ),
+ (
+ "/items/?skip=1",
+ [{"item_name": "Bar"}, {"item_name": "Baz"}],
+ ),
+ (
+ "/items/?skip=1&limit=1",
+ [{"item_name": "Bar"}],
+ ),
+ ],
+)
+def test_read_user_item(client: TestClient, path, expected_json):
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == expected_json
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "summary": "Read Item",
+ "operationId": "read_item_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Skip",
+ "type": "integer",
+ "default": 0,
+ },
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Limit",
+ "type": "integer",
+ "default": 10,
+ },
+ "name": "limit",
+ "in": "query",
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params/test_tutorial002.py b/tests/test_tutorial/test_query_params/test_tutorial002.py
new file mode 100644
index 0000000000..ae3ee7613d
--- /dev/null
+++ b/tests/test_tutorial/test_query_params/test_tutorial002.py
@@ -0,0 +1,127 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.query_params.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+@pytest.mark.parametrize(
+ ("path", "expected_json"),
+ [
+ (
+ "/items/foo",
+ {"item_id": "foo"},
+ ),
+ (
+ "/items/bar?q=somequery",
+ {"item_id": "bar", "q": "somequery"},
+ ),
+ ],
+)
+def test_read_user_item(client: TestClient, path, expected_json):
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == expected_json
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Q",
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ },
+ "name": "q",
+ "in": "query",
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params/test_tutorial003.py b/tests/test_tutorial/test_query_params/test_tutorial003.py
new file mode 100644
index 0000000000..c0b7e3b133
--- /dev/null
+++ b/tests/test_tutorial/test_query_params/test_tutorial003.py
@@ -0,0 +1,148 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial003_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.query_params.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+@pytest.mark.parametrize(
+ ("path", "expected_json"),
+ [
+ (
+ "/items/foo",
+ {
+ "item_id": "foo",
+ "description": "This is an amazing item that has a long description",
+ },
+ ),
+ (
+ "/items/bar?q=somequery",
+ {
+ "item_id": "bar",
+ "q": "somequery",
+ "description": "This is an amazing item that has a long description",
+ },
+ ),
+ (
+ "/items/baz?short=true",
+ {"item_id": "baz"},
+ ),
+ ],
+)
+def test_read_user_item(client: TestClient, path, expected_json):
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == expected_json
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Q",
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ },
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Short",
+ "type": "boolean",
+ "default": False,
+ },
+ "name": "short",
+ "in": "query",
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params/test_tutorial004.py b/tests/test_tutorial/test_query_params/test_tutorial004.py
new file mode 100644
index 0000000000..9be18b74df
--- /dev/null
+++ b/tests/test_tutorial/test_query_params/test_tutorial004.py
@@ -0,0 +1,156 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial004_py39"),
+ pytest.param("tutorial004_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.query_params.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+@pytest.mark.parametrize(
+ ("path", "expected_json"),
+ [
+ (
+ "/users/123/items/foo",
+ {
+ "item_id": "foo",
+ "owner_id": 123,
+ "description": "This is an amazing item that has a long description",
+ },
+ ),
+ (
+ "/users/1/items/bar?q=somequery",
+ {
+ "item_id": "bar",
+ "owner_id": 1,
+ "q": "somequery",
+ "description": "This is an amazing item that has a long description",
+ },
+ ),
+ (
+ "/users/42/items/baz?short=true",
+ {"item_id": "baz", "owner_id": 42},
+ ),
+ ],
+)
+def test_read_user_item(client: TestClient, path, expected_json):
+ response = client.get(path)
+ assert response.status_code == 200
+ assert response.json() == expected_json
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/users/{user_id}/items/{item_id}": {
+ "get": {
+ "summary": "Read User Item",
+ "operationId": "read_user_item_users__user_id__items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "User Id", "type": "integer"},
+ "name": "user_id",
+ "in": "path",
+ },
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Q",
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "null",
+ },
+ ],
+ },
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Short",
+ "type": "boolean",
+ "default": False,
+ },
+ "name": "short",
+ "in": "query",
+ },
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params/test_tutorial005.py b/tests/test_tutorial/test_query_params/test_tutorial005.py
index 05ae85b451..1030781472 100644
--- a/tests/test_tutorial/test_query_params/test_tutorial005.py
+++ b/tests/test_tutorial/test_query_params/test_tutorial005.py
@@ -1,7 +1,6 @@
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from docs_src.query_params.tutorial005 import app
+from docs_src.query_params.tutorial005_py39 import app
client = TestClient(app)
@@ -15,29 +14,16 @@ def test_foo_needy_very():
def test_foo_no_needy():
response = client.get("/items/foo")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "needy"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "needy"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "needy"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_openapi_schema():
diff --git a/tests/test_tutorial/test_query_params/test_tutorial006.py b/tests/test_tutorial/test_query_params/test_tutorial006.py
index a0b5ef4943..157322c7e3 100644
--- a/tests/test_tutorial/test_query_params/test_tutorial006.py
+++ b/tests/test_tutorial/test_query_params/test_tutorial006.py
@@ -1,7 +1,6 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -10,7 +9,7 @@ from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial006",
+ pytest.param("tutorial006_py39"),
pytest.param("tutorial006_py310", marks=needs_py310),
],
)
@@ -35,51 +34,28 @@ def test_foo_needy_very(client: TestClient):
def test_foo_no_needy(client: TestClient):
response = client.get("/items/foo?skip=a&limit=b")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["query", "needy"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "int_parsing",
- "loc": ["query", "skip"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "a",
- },
- {
- "type": "int_parsing",
- "loc": ["query", "limit"],
- "msg": "Input should be a valid integer, unable to parse string as an integer",
- "input": "b",
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["query", "needy"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["query", "skip"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- },
- {
- "loc": ["query", "limit"],
- "msg": "value is not a valid integer",
- "type": "type_error.integer",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "needy"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "int_parsing",
+ "loc": ["query", "skip"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "a",
+ },
+ {
+ "type": "int_parsing",
+ "loc": ["query", "limit"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "b",
+ },
+ ]
+ }
def test_openapi_schema(client: TestClient):
@@ -134,16 +110,10 @@ def test_openapi_schema(client: TestClient):
},
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "integer"}, {"type": "null"}],
- "title": "Limit",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Limit", "type": "integer"}
- ),
+ "schema": {
+ "anyOf": [{"type": "integer"}, {"type": "null"}],
+ "title": "Limit",
+ },
"name": "limit",
"in": "query",
},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py
new file mode 100644
index 0000000000..f1af7e08c1
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py
@@ -0,0 +1,121 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_query_params_str_validations_no_query(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+
+
+def test_query_params_str_validations_q_empty_str(client: TestClient):
+ response = client.get("/items/", params={"q": ""})
+ assert response.status_code == 200
+ assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+
+
+def test_query_params_str_validations_q_query(client: TestClient):
+ response = client.get("/items/", params={"q": "query"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ "q": "query",
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {"type": "string"},
+ {"type": "null"},
+ ],
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py
new file mode 100644
index 0000000000..62018b80b5
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py
@@ -0,0 +1,142 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ pytest.param("tutorial002_an_py39"),
+ pytest.param("tutorial002_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_query_params_str_validations_no_query(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+
+
+def test_query_params_str_validations_q_empty_str(client: TestClient):
+ response = client.get("/items/", params={"q": ""})
+ assert response.status_code == 200
+ assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+
+
+def test_query_params_str_validations_q_query(client: TestClient):
+ response = client.get("/items/", params={"q": "query"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ "q": "query",
+ }
+
+
+def test_query_params_str_validations_q_too_long(client: TestClient):
+ response = client.get("/items/", params={"q": "q" * 51})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_long",
+ "loc": ["query", "q"],
+ "msg": "String should have at most 50 characters",
+ "input": "q" * 51,
+ "ctx": {"max_length": 50},
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "maxLength": 50,
+ },
+ {"type": "null"},
+ ],
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py
new file mode 100644
index 0000000000..a4ad7a63ba
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py
@@ -0,0 +1,153 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial003_py310", marks=needs_py310),
+ pytest.param("tutorial003_an_py39"),
+ pytest.param("tutorial003_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_query_params_str_validations_no_query(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+
+
+def test_query_params_str_validations_q_query(client: TestClient):
+ response = client.get("/items/", params={"q": "query"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ "q": "query",
+ }
+
+
+def test_query_params_str_validations_q_too_short(client: TestClient):
+ response = client.get("/items/", params={"q": "qu"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_short",
+ "loc": ["query", "q"],
+ "msg": "String should have at least 3 characters",
+ "input": "qu",
+ "ctx": {"min_length": 3},
+ }
+ ]
+ }
+
+
+def test_query_params_str_validations_q_too_long(client: TestClient):
+ response = client.get("/items/", params={"q": "q" * 51})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_long",
+ "loc": ["query", "q"],
+ "msg": "String should have at most 50 characters",
+ "input": "q" * 51,
+ "ctx": {"max_length": 50},
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 50,
+ },
+ {"type": "null"},
+ ],
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py
new file mode 100644
index 0000000000..585989a827
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py
@@ -0,0 +1,147 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial004_py39"),
+ pytest.param("tutorial004_py310", marks=needs_py310),
+ pytest.param("tutorial004_an_py39"),
+ pytest.param("tutorial004_an_py310", marks=needs_py310),
+ pytest.param(
+ "tutorial004_regex_an_py310",
+ marks=(
+ needs_py310,
+ pytest.mark.filterwarnings(
+ "ignore:`regex` has been deprecated, please use `pattern` instead:fastapi.exceptions.FastAPIDeprecationWarning"
+ ),
+ ),
+ ),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_query_params_str_validations_no_query(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+
+
+def test_query_params_str_validations_q_fixedquery(client: TestClient):
+ response = client.get("/items/", params={"q": "fixedquery"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ "q": "fixedquery",
+ }
+
+
+def test_query_params_str_validations_q_nonregexquery(client: TestClient):
+ response = client.get("/items/", params={"q": "nonregexquery"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["query", "q"],
+ "msg": "String should match pattern '^fixedquery$'",
+ "input": "nonregexquery",
+ "ctx": {"pattern": "^fixedquery$"},
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 50,
+ "pattern": "^fixedquery$",
+ },
+ {"type": "null"},
+ ],
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py
new file mode 100644
index 0000000000..52462fe33b
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py
@@ -0,0 +1,131 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial005_py39"),
+ pytest.param("tutorial005_an_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_query_params_str_validations_no_query(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ "q": "fixedquery",
+ }
+
+
+def test_query_params_str_validations_q_query(client: TestClient):
+ response = client.get("/items/", params={"q": "query"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ "q": "query",
+ }
+
+
+def test_query_params_str_validations_q_short(client: TestClient):
+ response = client.get("/items/", params={"q": "fa"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_short",
+ "loc": ["query", "q"],
+ "msg": "String should have at least 3 characters",
+ "input": "fa",
+ "ctx": {"min_length": 3},
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "type": "string",
+ "default": "fixedquery",
+ "minLength": 3,
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py
new file mode 100644
index 0000000000..640cedce19
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py
@@ -0,0 +1,136 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial006_py39"),
+ pytest.param("tutorial006_an_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_query_params_str_validations_no_query(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["query", "q"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
+
+
+def test_query_params_str_validations_q_fixedquery(client: TestClient):
+ response = client.get("/items/", params={"q": "fixedquery"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ "q": "fixedquery",
+ }
+
+
+def test_query_params_str_validations_q_fixedquery_too_short(client: TestClient):
+ response = client.get("/items/", params={"q": "fa"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_short",
+ "loc": ["query", "q"],
+ "msg": "String should have at least 3 characters",
+ "input": "fa",
+ "ctx": {"min_length": 3},
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "type": "string",
+ "minLength": 3,
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py
new file mode 100644
index 0000000000..f287b5dcd8
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py
@@ -0,0 +1,148 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial006c_py39"),
+ pytest.param("tutorial006c_py310", marks=needs_py310),
+ pytest.param("tutorial006c_an_py39"),
+ pytest.param("tutorial006c_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
+ client = TestClient(mod.app)
+ return client
+
+
+@pytest.mark.xfail(
+ reason="Code example is not valid. See https://github.com/fastapi/fastapi/issues/12419"
+)
+def test_query_params_str_validations_no_query(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == { # pragma: no cover
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ }
+
+
+@pytest.mark.xfail(
+ reason="Code example is not valid. See https://github.com/fastapi/fastapi/issues/12419"
+)
+def test_query_params_str_validations_empty_str(client: TestClient):
+ response = client.get("/items/?q=")
+ assert response.status_code == 200
+ assert response.json() == { # pragma: no cover
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ }
+
+
+def test_query_params_str_validations_q_query(client: TestClient):
+ response = client.get("/items/", params={"q": "query"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ "q": "query",
+ }
+
+
+def test_query_params_str_validations_q_short(client: TestClient):
+ response = client.get("/items/", params={"q": "fa"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_short",
+ "loc": ["query", "q"],
+ "msg": "String should have at least 3 characters",
+ "input": "fa",
+ "ctx": {"min_length": 3},
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "anyOf": [
+ {"type": "string", "minLength": 3},
+ {"type": "null"},
+ ],
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py
new file mode 100644
index 0000000000..b17bc27719
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py
@@ -0,0 +1,136 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial007_py39"),
+ pytest.param("tutorial007_py310", marks=needs_py310),
+ pytest.param("tutorial007_an_py39"),
+ pytest.param("tutorial007_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_query_params_str_validations_no_query(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+
+
+def test_query_params_str_validations_q_fixedquery(client: TestClient):
+ response = client.get("/items/", params={"q": "fixedquery"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ "q": "fixedquery",
+ }
+
+
+def test_query_params_str_validations_q_fixedquery_too_short(client: TestClient):
+ response = client.get("/items/", params={"q": "fa"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_short",
+ "loc": ["query", "q"],
+ "msg": "String should have at least 3 characters",
+ "input": "fa",
+ "ctx": {"min_length": 3},
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "minLength": 3,
+ },
+ {"type": "null"},
+ ],
+ "title": "Query string",
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py
new file mode 100644
index 0000000000..c631115744
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py
@@ -0,0 +1,138 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial008_py39"),
+ pytest.param("tutorial008_py310", marks=needs_py310),
+ pytest.param("tutorial008_an_py39"),
+ pytest.param("tutorial008_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_query_params_str_validations_no_query(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+
+
+def test_query_params_str_validations_q_fixedquery(client: TestClient):
+ response = client.get("/items/", params={"q": "fixedquery"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ "q": "fixedquery",
+ }
+
+
+def test_query_params_str_validations_q_fixedquery_too_short(client: TestClient):
+ response = client.get("/items/", params={"q": "fa"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_too_short",
+ "loc": ["query", "q"],
+ "msg": "String should have at least 3 characters",
+ "input": "fa",
+ "ctx": {"min_length": 3},
+ }
+ ]
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "description": "Query string for the items to search in the database that have a good match",
+ "required": False,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "minLength": 3,
+ },
+ {"type": "null"},
+ ],
+ "title": "Query string",
+ "description": "Query string for the items to search in the database that have a good match",
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py
new file mode 100644
index 0000000000..7e9d69d41c
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py
@@ -0,0 +1,123 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial009_py39"),
+ pytest.param("tutorial009_py310", marks=needs_py310),
+ pytest.param("tutorial009_an_py39"),
+ pytest.param("tutorial009_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_query_params_str_validations_no_query(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+
+
+def test_query_params_str_validations_item_query_fixedquery(client: TestClient):
+ response = client.get("/items/", params={"item-query": "fixedquery"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
+ "q": "fixedquery",
+ }
+
+
+def test_query_params_str_validations_q_fixedquery(client: TestClient):
+ response = client.get("/items/", params={"q": "fixedquery"})
+ assert response.status_code == 200
+ assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "schema": {
+ "anyOf": [
+ {"type": "string"},
+ {"type": "null"},
+ ],
+ "title": "Item-Query",
+ },
+ "required": False,
+ "name": "item-query",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py
index e08e169633..00889c5bf7 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py
@@ -1,20 +1,18 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial010",
+ pytest.param("tutorial010_py39"),
pytest.param("tutorial010_py310", marks=needs_py310),
- "tutorial010_an",
- pytest.param("tutorial010_an_py39", marks=needs_py39),
+ pytest.param("tutorial010_an_py39"),
pytest.param("tutorial010_an_py310", marks=needs_py310),
],
)
@@ -51,31 +49,17 @@ def test_query_params_str_validations_q_fixedquery(client: TestClient):
def test_query_params_str_validations_item_query_nonregexquery(client: TestClient):
response = client.get("/items/", params={"item-query": "nonregexquery"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["query", "item-query"],
- "msg": "String should match pattern '^fixedquery$'",
- "input": "nonregexquery",
- "ctx": {"pattern": "^fixedquery$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"pattern": "^fixedquery$"},
- "loc": ["query", "item-query"],
- "msg": 'string does not match regex "^fixedquery$"',
- "type": "value_error.str.regex",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "string_pattern_mismatch",
+ "loc": ["query", "item-query"],
+ "msg": "String should match pattern '^fixedquery$'",
+ "input": "nonregexquery",
+ "ctx": {"pattern": "^fixedquery$"},
+ }
+ ]
+ }
def test_openapi_schema(client: TestClient):
@@ -110,38 +94,25 @@ def test_openapi_schema(client: TestClient):
"description": "Query string for the items to search in the database that have a good match",
"required": False,
"deprecated": True,
- "schema": IsDict(
- {
- "anyOf": [
- {
- "type": "string",
- "minLength": 3,
- "maxLength": 50,
- "pattern": "^fixedquery$",
- },
- {"type": "null"},
- ],
- "title": "Query string",
- "description": "Query string for the items to search in the database that have a good match",
- # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34.
- **(
- {"deprecated": True}
- if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10)
- else {}
- ),
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Query string",
- "maxLength": 50,
- "minLength": 3,
- "pattern": "^fixedquery$",
- "type": "string",
- "description": "Query string for the items to search in the database that have a good match",
- }
- ),
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 50,
+ "pattern": "^fixedquery$",
+ },
+ {"type": "null"},
+ ],
+ "title": "Query string",
+ "description": "Query string for the items to search in the database that have a good match",
+ # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34.
+ **(
+ {"deprecated": True}
+ if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10)
+ else {}
+ ),
+ },
"name": "item-query",
"in": "query",
}
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
index f4da25752b..11de33ae14 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
@@ -1,20 +1,17 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial011",
- pytest.param("tutorial011_py39", marks=needs_py310),
+ pytest.param("tutorial011_py39"),
pytest.param("tutorial011_py310", marks=needs_py310),
- "tutorial011_an",
- pytest.param("tutorial011_an_py39", marks=needs_py39),
+ pytest.param("tutorial011_an_py39"),
pytest.param("tutorial011_an_py310", marks=needs_py310),
],
)
@@ -71,23 +68,13 @@ def test_openapi_schema(client: TestClient):
"parameters": [
{
"required": False,
- "schema": IsDict(
- {
- "anyOf": [
- {"type": "array", "items": {"type": "string"}},
- {"type": "null"},
- ],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Q",
- "type": "array",
- "items": {"type": "string"},
- }
- ),
+ "schema": {
+ "anyOf": [
+ {"type": "array", "items": {"type": "string"}},
+ {"type": "null"},
+ ],
+ "title": "Q",
+ },
"name": "q",
"in": "query",
}
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
index 549a90519e..1826928611 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
@@ -3,16 +3,12 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial012",
- pytest.param("tutorial012_py39", marks=needs_py39),
- "tutorial012_an",
- pytest.param("tutorial012_an_py39", marks=needs_py39),
+ pytest.param("tutorial012_py39"),
+ pytest.param("tutorial012_an_py39"),
],
)
def get_client(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
index f2f5f7a858..46c367c86b 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
@@ -3,15 +3,12 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial013",
- "tutorial013_an",
- pytest.param("tutorial013_an_py39", marks=needs_py39),
+ "tutorial013_py39",
+ "tutorial013_an_py39",
],
)
def get_client(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
index edd40bb1ab..0feaccfa44 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
@@ -3,16 +3,15 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial014",
+ pytest.param("tutorial014_py39"),
pytest.param("tutorial014_py310", marks=needs_py310),
- "tutorial014_an",
- pytest.param("tutorial014_an_py39", marks=needs_py39),
+ pytest.param("tutorial014_an_py39"),
pytest.param("tutorial014_an_py310", marks=needs_py310),
],
)
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py
index ae1c40286d..82bb606a9b 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py
@@ -5,15 +5,14 @@ from dirty_equals import IsStr
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
-from ...utils import needs_py39, needs_py310, needs_pydanticv2
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- pytest.param("tutorial015_an", marks=needs_pydanticv2),
- pytest.param("tutorial015_an_py310", marks=(needs_py310, needs_pydanticv2)),
- pytest.param("tutorial015_an_py39", marks=(needs_py39, needs_pydanticv2)),
+ pytest.param("tutorial015_an_py39"),
+ pytest.param("tutorial015_an_py310", marks=[needs_py310]),
],
)
def get_client(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py
index b069199612..e0e1bbe639 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial001.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial001.py
@@ -1,18 +1,14 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial001",
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ "tutorial001_py39",
+ "tutorial001_an_py39",
],
)
def get_client(request: pytest.FixtureRequest):
@@ -25,57 +21,31 @@ def get_client(request: pytest.FixtureRequest):
def test_post_form_no_body(client: TestClient):
response = client.post("/files/")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "file"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "file"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "file"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_post_body_json(client: TestClient):
response = client.post("/files/", json={"file": "Foo"})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "file"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "file"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "file"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_post_file(tmp_path, client: TestClient):
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py
index 9075a17561..18948c5444 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py
@@ -2,19 +2,18 @@ import importlib
from pathlib import Path
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001_02",
+ pytest.param("tutorial001_02_py39"),
pytest.param("tutorial001_02_py310", marks=needs_py310),
- "tutorial001_02_an",
- pytest.param("tutorial001_02_an_py39", marks=needs_py39),
+ pytest.param("tutorial001_02_an_py39"),
pytest.param("tutorial001_02_an_py310", marks=needs_py310),
],
)
@@ -60,166 +59,132 @@ def test_post_upload_file(tmp_path: Path, client: TestClient):
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/files/": {
- "post": {
- "summary": "Create File",
- "operationId": "create_file_files__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/files/": {
+ "post": {
+ "summary": "Create File",
+ "operationId": "create_file_files__post",
+ "requestBody": {
+ "content": {
+ "multipart/form-data": {
+ "schema": {
"$ref": "#/components/schemas/Body_create_file_files__post"
}
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
}
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
},
},
- },
- }
- },
- "/uploadfile/": {
- "post": {
- "summary": "Create Upload File",
- "operationId": "create_upload_file_uploadfile__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
+ }
+ },
+ "/uploadfile/": {
+ "post": {
+ "summary": "Create Upload File",
+ "operationId": "create_upload_file_uploadfile__post",
+ "requestBody": {
+ "content": {
+ "multipart/form-data": {
+ "schema": {
"$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
}
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
}
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
},
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_create_file_files__post": {
+ "title": "Body_create_file_files__post",
+ "type": "object",
+ "properties": {
+ "file": {
+ "title": "File",
+ "anyOf": [
+ {"type": "string", "format": "binary"},
+ {"type": "null"},
+ ],
+ }
+ },
+ },
+ "Body_create_upload_file_uploadfile__post": {
+ "title": "Body_create_upload_file_uploadfile__post",
+ "type": "object",
+ "properties": {
+ "file": {
+ "title": "File",
+ "anyOf": [
+ {"type": "string", "format": "binary"},
+ {"type": "null"},
+ ],
+ }
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
},
},
}
},
- },
- "components": {
- "schemas": {
- "Body_create_file_files__post": {
- "title": "Body_create_file_files__post",
- "type": "object",
- "properties": {
- "file": IsDict(
- {
- "title": "File",
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "File", "type": "string", "format": "binary"}
- )
- },
- },
- "Body_create_upload_file_uploadfile__post": {
- "title": "Body_create_upload_file_uploadfile__post",
- "type": "object",
- "properties": {
- "file": IsDict(
- {
- "title": "File",
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "File", "type": "string", "format": "binary"}
- )
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
+ }
+ )
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py
index 9fbe2166c1..53a7a0cf85 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py
@@ -3,15 +3,12 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial001_03",
- "tutorial001_03_an",
- pytest.param("tutorial001_03_an_py39", marks=needs_py39),
+ "tutorial001_03_py39",
+ "tutorial001_03_an_py39",
],
)
def get_client(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py
index 446a876578..03772419ad 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial002.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial002.py
@@ -1,20 +1,15 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="app",
params=[
- "tutorial002",
- "tutorial002_an",
- pytest.param("tutorial002_py39", marks=needs_py39),
- pytest.param("tutorial002_an_py39", marks=needs_py39),
+ "tutorial002_py39",
+ "tutorial002_an_py39",
],
)
def get_app(request: pytest.FixtureRequest):
@@ -32,57 +27,31 @@ def get_client(app: FastAPI):
def test_post_form_no_body(client: TestClient):
response = client.post("/files/")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "files"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "files"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "files"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_post_body_json(client: TestClient):
response = client.post("/files/", json={"file": "Foo"})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "files"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "files"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "files"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_post_files(tmp_path, app: FastAPI):
diff --git a/tests/test_tutorial/test_request_files/test_tutorial003.py b/tests/test_tutorial/test_request_files/test_tutorial003.py
index 8534ba3e9a..fa4bfd5695 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial003.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial003.py
@@ -4,16 +4,12 @@ import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="app",
params=[
- "tutorial003",
- "tutorial003_an",
- pytest.param("tutorial003_py39", marks=needs_py39),
- pytest.param("tutorial003_an_py39", marks=needs_py39),
+ "tutorial003_py39",
+ "tutorial003_an_py39",
],
)
def get_app(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial001.py b/tests/test_tutorial/test_request_form_models/test_tutorial001.py
index 1ca3c96d33..0c43dd7b21 100644
--- a/tests/test_tutorial/test_request_form_models/test_tutorial001.py
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial001.py
@@ -1,18 +1,14 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial001",
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ "tutorial001_py39",
+ "tutorial001_an_py39",
],
)
def get_client(request: pytest.FixtureRequest):
@@ -31,135 +27,73 @@ def test_post_body_form(client: TestClient):
def test_post_body_form_no_password(client: TestClient):
response = client.post("/login/", data={"username": "Foo"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": {"username": "Foo"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {"username": "Foo"},
+ }
+ ]
+ }
def test_post_body_form_no_username(client: TestClient):
response = client.post("/login/", data={"password": "secret"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": {"password": "secret"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {"password": "secret"},
+ }
+ ]
+ }
def test_post_body_form_no_data(client: TestClient):
response = client.post("/login/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": {},
- },
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": {},
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
def test_post_body_json(client: TestClient):
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": {},
- },
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": {},
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
def test_openapi_schema(client: TestClient):
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002.py b/tests/test_tutorial/test_request_form_models/test_tutorial002.py
index b3f6be63ab..238f8fa2ef 100644
--- a/tests/test_tutorial/test_request_form_models/test_tutorial002.py
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial002.py
@@ -3,15 +3,12 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_pydanticv2
-
@pytest.fixture(
name="client",
params=[
- "tutorial002",
- "tutorial002_an",
- pytest.param("tutorial002_an_py39", marks=needs_py39),
+ "tutorial002_py39",
+ "tutorial002_an_py39",
],
)
def get_client(request: pytest.FixtureRequest):
@@ -21,14 +18,12 @@ def get_client(request: pytest.FixtureRequest):
return client
-@needs_pydanticv2
def test_post_body_form(client: TestClient):
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
assert response.status_code == 200
assert response.json() == {"username": "Foo", "password": "secret"}
-@needs_pydanticv2
def test_post_body_extra_form(client: TestClient):
response = client.post(
"/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
@@ -46,7 +41,6 @@ def test_post_body_extra_form(client: TestClient):
}
-@needs_pydanticv2
def test_post_body_form_no_password(client: TestClient):
response = client.post("/login/", data={"username": "Foo"})
assert response.status_code == 422
@@ -62,7 +56,6 @@ def test_post_body_form_no_password(client: TestClient):
}
-@needs_pydanticv2
def test_post_body_form_no_username(client: TestClient):
response = client.post("/login/", data={"password": "secret"})
assert response.status_code == 422
@@ -78,7 +71,6 @@ def test_post_body_form_no_username(client: TestClient):
}
-@needs_pydanticv2
def test_post_body_form_no_data(client: TestClient):
response = client.post("/login/")
assert response.status_code == 422
@@ -100,7 +92,6 @@ def test_post_body_form_no_data(client: TestClient):
}
-@needs_pydanticv2
def test_post_body_json(client: TestClient):
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
assert response.status_code == 422, response.text
@@ -122,7 +113,6 @@ def test_post_body_json(client: TestClient):
}
-@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py
deleted file mode 100644
index b503f23a53..0000000000
--- a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py
+++ /dev/null
@@ -1,205 +0,0 @@
-import importlib
-
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39, needs_pydanticv1
-
-
-@pytest.fixture(
- name="client",
- params=[
- "tutorial002_pv1",
- "tutorial002_pv1_an",
- pytest.param("tutorial002_pv1_an_py39", marks=needs_py39),
- ],
-)
-def get_client(request: pytest.FixtureRequest):
- mod = importlib.import_module(f"docs_src.request_form_models.{request.param}")
-
- client = TestClient(mod.app)
- return client
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_post_body_form(client: TestClient):
- response = client.post("/login/", data={"username": "Foo", "password": "secret"})
- assert response.status_code == 200
- assert response.json() == {"username": "Foo", "password": "secret"}
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_post_body_extra_form(client: TestClient):
- response = client.post(
- "/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
- )
- assert response.status_code == 422
- assert response.json() == {
- "detail": [
- {
- "type": "value_error.extra",
- "loc": ["body", "extra"],
- "msg": "extra fields not permitted",
- }
- ]
- }
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_post_body_form_no_password(client: TestClient):
- response = client.post("/login/", data={"username": "Foo"})
- assert response.status_code == 422
- assert response.json() == {
- "detail": [
- {
- "type": "value_error.missing",
- "loc": ["body", "password"],
- "msg": "field required",
- }
- ]
- }
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_post_body_form_no_username(client: TestClient):
- response = client.post("/login/", data={"password": "secret"})
- assert response.status_code == 422
- assert response.json() == {
- "detail": [
- {
- "type": "value_error.missing",
- "loc": ["body", "username"],
- "msg": "field required",
- }
- ]
- }
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_post_body_form_no_data(client: TestClient):
- response = client.post("/login/")
- assert response.status_code == 422
- assert response.json() == {
- "detail": [
- {
- "type": "value_error.missing",
- "loc": ["body", "username"],
- "msg": "field required",
- },
- {
- "type": "value_error.missing",
- "loc": ["body", "password"],
- "msg": "field required",
- },
- ]
- }
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_post_body_json(client: TestClient):
- response = client.post("/login/", json={"username": "Foo", "password": "secret"})
- assert response.status_code == 422, response.text
- assert response.json() == {
- "detail": [
- {
- "type": "value_error.missing",
- "loc": ["body", "username"],
- "msg": "field required",
- },
- {
- "type": "value_error.missing",
- "loc": ["body", "password"],
- "msg": "field required",
- },
- ]
- }
-
-
-# TODO: remove when deprecating Pydantic v1
-@needs_pydanticv1
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/login/": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Login",
- "operationId": "login_login__post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": {"$ref": "#/components/schemas/FormData"}
- }
- },
- "required": True,
- },
- }
- }
- },
- "components": {
- "schemas": {
- "FormData": {
- "properties": {
- "username": {"type": "string", "title": "Username"},
- "password": {"type": "string", "title": "Password"},
- },
- "additionalProperties": False,
- "type": "object",
- "required": ["username", "password"],
- "title": "FormData",
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_request_forms/test_tutorial001.py b/tests/test_tutorial/test_request_forms/test_tutorial001.py
index 321f8022b7..4276414fc2 100644
--- a/tests/test_tutorial/test_request_forms/test_tutorial001.py
+++ b/tests/test_tutorial/test_request_forms/test_tutorial001.py
@@ -1,18 +1,14 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial001",
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ "tutorial001_py39",
+ "tutorial001_an_py39",
],
)
def get_client(request: pytest.FixtureRequest):
@@ -31,135 +27,73 @@ def test_post_body_form(client: TestClient):
def test_post_body_form_no_password(client: TestClient):
response = client.post("/login/", data={"username": "Foo"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_post_body_form_no_username(client: TestClient):
response = client.post("/login/", data={"password": "secret"})
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": None,
+ }
+ ]
+ }
def test_post_body_form_no_data(client: TestClient):
response = client.post("/login/")
assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_post_body_json(client: TestClient):
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "username"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "password"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "username"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "password"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_openapi_schema(client: TestClient):
diff --git a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py
index d12219245e..7fa4c3de57 100644
--- a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py
+++ b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py
@@ -1,19 +1,15 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi import FastAPI
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="app",
params=[
- "tutorial001",
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ "tutorial001_py39",
+ "tutorial001_an_py39",
],
)
def get_app(request: pytest.FixtureRequest):
@@ -31,140 +27,76 @@ def get_client(app: FastAPI):
def test_post_form_no_body(client: TestClient):
response = client.post("/files/")
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "file"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "fileb"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "token"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "file"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "fileb"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "file"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "fileb"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "token"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_post_form_no_file(client: TestClient):
response = client.post("/files/", data={"token": "foo"})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "file"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "fileb"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "file"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "fileb"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "file"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "fileb"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_post_body_json(client: TestClient):
response = client.post("/files/", json={"file": "Foo", "token": "Bar"})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "file"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "fileb"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "token"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "file"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "fileb"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "file"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "fileb"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "token"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_post_file_no_token(tmp_path, app: FastAPI):
@@ -175,40 +107,22 @@ def test_post_file_no_token(tmp_path, app: FastAPI):
with path.open("rb") as file:
response = client.post("/files/", files={"file": file})
assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "fileb"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "token"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "fileb"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "token"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "fileb"],
+ "msg": "Field required",
+ "input": None,
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "token"],
+ "msg": "Field required",
+ "input": None,
+ },
+ ]
+ }
def test_post_files_and_token(tmp_path, app: FastAPI):
diff --git a/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py b/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py
index 8ce3dcf1ab..05d5ca619f 100644
--- a/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py
+++ b/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.response_change_status_code.tutorial001 import app
+from docs_src.response_change_status_code.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_response_cookies/test_tutorial001.py b/tests/test_tutorial/test_response_cookies/test_tutorial001.py
index eecd1a24c9..6b931c8bda 100644
--- a/tests/test_tutorial/test_response_cookies/test_tutorial001.py
+++ b/tests/test_tutorial/test_response_cookies/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.response_cookies.tutorial001 import app
+from docs_src.response_cookies.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_response_cookies/test_tutorial002.py b/tests/test_tutorial/test_response_cookies/test_tutorial002.py
index 3e390025fa..3ee5f500c5 100644
--- a/tests/test_tutorial/test_response_cookies/test_tutorial002.py
+++ b/tests/test_tutorial/test_response_cookies/test_tutorial002.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.response_cookies.tutorial002 import app
+from docs_src.response_cookies.tutorial002_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_response_directly/__init__.py b/tests/test_tutorial/test_response_directly/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_response_directly/test_tutorial001.py b/tests/test_tutorial/test_response_directly/test_tutorial001.py
new file mode 100644
index 0000000000..2d0c387195
--- /dev/null
+++ b/tests/test_tutorial/test_response_directly/test_tutorial001.py
@@ -0,0 +1,152 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.response_directly.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_path_operation(client: TestClient):
+ response = client.put(
+ "/items/1",
+ json={
+ "title": "Foo",
+ "timestamp": "2023-01-01T12:00:00",
+ "description": "A test item",
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "description": "A test item",
+ "timestamp": "2023-01-01T12:00:00",
+ "title": "Foo",
+ }
+
+
+def test_openapi_schema_pv2(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "info": {
+ "title": "FastAPI",
+ "version": "0.1.0",
+ },
+ "openapi": "3.1.0",
+ "paths": {
+ "/items/{id}": {
+ "put": {
+ "operationId": "update_item_items__id__put",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": True,
+ "schema": {"title": "Id", "type": "string"},
+ },
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ },
+ },
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {"schema": {}},
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Update Item",
+ },
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "Item": {
+ "properties": {
+ "description": {
+ "anyOf": [
+ {"type": "string"},
+ {"type": "null"},
+ ],
+ "title": "Description",
+ },
+ "timestamp": {
+ "format": "date-time",
+ "title": "Timestamp",
+ "type": "string",
+ },
+ "title": {"title": "Title", "type": "string"},
+ },
+ "required": [
+ "title",
+ "timestamp",
+ ],
+ "title": "Item",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {"type": "string"},
+ {"type": "integer"},
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_response_directly/test_tutorial002.py b/tests/test_tutorial/test_response_directly/test_tutorial002.py
new file mode 100644
index 0000000000..ef84575723
--- /dev/null
+++ b/tests/test_tutorial/test_response_directly/test_tutorial002.py
@@ -0,0 +1,65 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.response_directly.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_path_operation(client: TestClient):
+ expected_content = """
+
+
+ Apply shampoo here.
+
+
+ You'll have to use soap here.
+
+
+ """
+
+ response = client.get("/legacy/")
+ assert response.status_code == 200, response.text
+ assert response.headers["content-type"] == "application/xml"
+ assert response.text == expected_content
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "info": {
+ "title": "FastAPI",
+ "version": "0.1.0",
+ },
+ "openapi": "3.1.0",
+ "paths": {
+ "/legacy/": {
+ "get": {
+ "operationId": "get_legacy_data_legacy__get",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "Successful Response",
+ },
+ },
+ "summary": "Get Legacy Data",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_response_headers/test_tutorial001.py b/tests/test_tutorial/test_response_headers/test_tutorial001.py
index 1549d6b5b1..6232aad23e 100644
--- a/tests/test_tutorial/test_response_headers/test_tutorial001.py
+++ b/tests/test_tutorial/test_response_headers/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.response_headers.tutorial001 import app
+from docs_src.response_headers.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_response_headers/test_tutorial002.py b/tests/test_tutorial/test_response_headers/test_tutorial002.py
index 2826833f84..2ac2226cb1 100644
--- a/tests/test_tutorial/test_response_headers/test_tutorial002.py
+++ b/tests/test_tutorial/test_response_headers/test_tutorial002.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.response_headers.tutorial002 import app
+from docs_src.response_headers.tutorial002_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py b/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py
new file mode 100644
index 0000000000..10692f9904
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py
@@ -0,0 +1,193 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ pytest.param("tutorial001_01_py39"),
+ pytest.param("tutorial001_01_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.response_model.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_read_items(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {
+ "name": "Portal Gun",
+ "description": None,
+ "price": 42.0,
+ "tags": [],
+ "tax": None,
+ },
+ {
+ "name": "Plumbus",
+ "description": None,
+ "price": 32.0,
+ "tags": [],
+ "tax": None,
+ },
+ ]
+
+
+def test_create_item(client: TestClient):
+ item_data = {
+ "name": "Test Item",
+ "description": "A test item",
+ "price": 10.5,
+ "tax": 1.5,
+ "tags": ["test", "item"],
+ }
+ response = client.post("/items/", json=item_data)
+ assert response.status_code == 200, response.text
+ assert response.json() == item_data
+
+
+def test_create_item_only_required(client: TestClient):
+ response = client.post(
+ "/items/",
+ json={
+ "name": "Test Item",
+ "price": 10.5,
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Test Item",
+ "price": 10.5,
+ "description": None,
+ "tax": None,
+ "tags": [],
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ "title": "Response Read Items Items Get",
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ },
+ "post": {
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ },
+ },
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"},
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ },
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
+ "tags": {
+ "title": "Tags",
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py b/tests/test_tutorial/test_response_model/test_tutorial002.py
similarity index 58%
rename from tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py
rename to tests/test_tutorial/test_response_model/test_tutorial002.py
index 48732dbf06..216d4c420c 100644
--- a/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial002.py
@@ -1,43 +1,40 @@
+import importlib
+
import pytest
-from fastapi.exceptions import ResponseValidationError
from fastapi.testclient import TestClient
-from ..utils import needs_pydanticv1
+from ...utils import needs_py310
-@pytest.fixture(name="client")
-def get_client():
- from .app_pv1 import app
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.response_model.{request.param}")
- client = TestClient(app)
+ client = TestClient(mod.app)
return client
-@needs_pydanticv1
-def test_filter_sub_model(client: TestClient):
- response = client.get("/model/modelA")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "name": "modelA",
- "description": "model-a-desc",
- "model_b": {"username": "test-user"},
+def test_post_user(client: TestClient):
+ user_data = {
+ "username": "foo",
+ "password": "fighter",
+ "email": "foo@example.com",
+ "full_name": "Grave Dohl",
}
+ response = client.post(
+ "/user/",
+ json=user_data,
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == user_data
-@needs_pydanticv1
-def test_validator_is_cloned(client: TestClient):
- with pytest.raises(ResponseValidationError) as err:
- client.get("/model/modelX")
- assert err.value.errors() == [
- {
- "loc": ("response", "name"),
- "msg": "name must end in A",
- "type": "value_error",
- }
- ]
-
-
-@needs_pydanticv1
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
@@ -45,24 +42,14 @@ def test_openapi_schema(client: TestClient):
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
- "/model/{name}": {
- "get": {
- "summary": "Get Model A",
- "operationId": "get_model_a_model__name__get",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Name", "type": "string"},
- "name": "name",
- "in": "path",
- }
- ],
+ "/user/": {
+ "post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
- "schema": {"$ref": "#/components/schemas/ModelA"}
+ "schema": {"$ref": "#/components/schemas/UserIn"}
}
},
},
@@ -77,38 +64,39 @@ def test_openapi_schema(client: TestClient):
},
},
},
+ "summary": "Create User",
+ "operationId": "create_user_user__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/UserIn"}
+ }
+ },
+ "required": True,
+ },
}
}
},
"components": {
"schemas": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
+ "UserIn": {
+ "title": "UserIn",
+ "required": ["username", "password", "email"],
"type": "object",
"properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
+ "username": {"title": "Username", "type": "string"},
+ "password": {"title": "Password", "type": "string"},
+ "email": {
+ "title": "Email",
+ "type": "string",
+ "format": "email",
+ },
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
},
},
- "ModelA": {
- "title": "ModelA",
- "required": ["name", "model_b"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": {"title": "Description", "type": "string"},
- "model_b": {"$ref": "#/components/schemas/ModelB"},
- },
- },
- "ModelB": {
- "title": "ModelB",
- "required": ["username"],
- "type": "object",
- "properties": {"username": {"title": "Username", "type": "string"}},
- },
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
@@ -125,6 +113,17 @@ def test_openapi_schema(client: TestClient):
"type": {"title": "Error Type", "type": "string"},
},
},
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
}
},
}
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003.py b/tests/test_tutorial/test_response_model/test_tutorial003.py
index 70cfd6e4cc..35ed5572dd 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial003.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial003.py
@@ -1,8 +1,8 @@
import importlib
import pytest
-from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from ...utils import needs_py310
@@ -10,7 +10,7 @@ from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial003",
+ pytest.param("tutorial003_py39"),
pytest.param("tutorial003_py310", marks=needs_py310),
],
)
@@ -42,125 +42,115 @@ def test_post_user(client: TestClient):
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/user/": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/UserOut"}
- }
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/user/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserOut"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
},
},
- "422": {
- "description": "Validation Error",
+ "summary": "Create User",
+ "operationId": "create_user_user__post",
+ "requestBody": {
"content": {
"application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
+ "schema": {"$ref": "#/components/schemas/UserIn"}
}
},
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "UserOut": {
+ "title": "UserOut",
+ "required": ["username", "email"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {
+ "title": "Email",
+ "type": "string",
+ "format": "email",
+ },
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
},
},
- "summary": "Create User",
- "operationId": "create_user_user__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/UserIn"}
+ "UserIn": {
+ "title": "UserIn",
+ "required": ["username", "password", "email"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "password": {"title": "Password", "type": "string"},
+ "email": {
+ "title": "Email",
+ "type": "string",
+ "format": "email",
+ },
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
}
},
- "required": True,
},
}
- }
- },
- "components": {
- "schemas": {
- "UserOut": {
- "title": "UserOut",
- "required": IsOneOf(
- ["username", "email", "full_name"],
- # TODO: remove when deprecating Pydantic v1
- ["username", "email"],
- ),
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "email": {
- "title": "Email",
- "type": "string",
- "format": "email",
- },
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- },
- },
- "UserIn": {
- "title": "UserIn",
- "required": ["username", "password", "email"],
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "password": {"title": "Password", "type": "string"},
- "email": {
- "title": "Email",
- "type": "string",
- "format": "email",
- },
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_01.py b/tests/test_tutorial/test_response_model/test_tutorial003_01.py
index 3975856b6c..fa1eb62770 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial003_01.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_01.py
@@ -1,8 +1,8 @@
import importlib
import pytest
-from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from ...utils import needs_py310
@@ -10,7 +10,7 @@ from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial003_01",
+ pytest.param("tutorial003_01_py39"),
pytest.param("tutorial003_01_py310", marks=needs_py310),
],
)
@@ -42,125 +42,115 @@ def test_post_user(client: TestClient):
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/user/": {
- "post": {
- "summary": "Create User",
- "operationId": "create_user_user__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/UserIn"}
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/user/": {
+ "post": {
+ "summary": "Create User",
+ "operationId": "create_user_user__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/UserIn"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/BaseUser"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "BaseUser": {
+ "title": "BaseUser",
+ "required": ["username", "email"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {
+ "title": "Email",
+ "type": "string",
+ "format": "email",
+ },
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
}
},
- "required": True,
},
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/BaseUser"}
- }
+ "UserIn": {
+ "title": "UserIn",
+ "required": ["username", "email", "password"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {
+ "title": "Email",
+ "type": "string",
+ "format": "email",
},
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "password": {"title": "Password", "type": "string"},
},
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
},
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
},
},
}
- }
- },
- "components": {
- "schemas": {
- "BaseUser": {
- "title": "BaseUser",
- "required": IsOneOf(
- ["username", "email", "full_name"],
- # TODO: remove when deprecating Pydantic v1
- ["username", "email"],
- ),
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "email": {
- "title": "Email",
- "type": "string",
- "format": "email",
- },
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "UserIn": {
- "title": "UserIn",
- "required": ["username", "email", "password"],
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "email": {
- "title": "Email",
- "type": "string",
- "format": "email",
- },
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- "password": {"title": "Password", "type": "string"},
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_02.py b/tests/test_tutorial/test_response_model/test_tutorial003_02.py
index eabd203456..b7507b7110 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial003_02.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_02.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.response_model.tutorial003_02 import app
+from docs_src.response_model.tutorial003_02_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_03.py b/tests/test_tutorial/test_response_model/test_tutorial003_03.py
index 970ff58450..ea3c733b24 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial003_03.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_03.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.response_model.tutorial003_03 import app
+from docs_src.response_model.tutorial003_03_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_04.py b/tests/test_tutorial/test_response_model/test_tutorial003_04.py
index f32e93ddcb..145af126fd 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial003_04.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_04.py
@@ -9,7 +9,7 @@ from ...utils import needs_py310
@pytest.mark.parametrize(
"module_name",
[
- "tutorial003_04",
+ pytest.param("tutorial003_04_py39"),
pytest.param("tutorial003_04_py310", marks=needs_py310),
],
)
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_05.py b/tests/test_tutorial/test_response_model/test_tutorial003_05.py
index 9500852e15..19a7c601bb 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial003_05.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_05.py
@@ -9,7 +9,7 @@ from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial003_05",
+ pytest.param("tutorial003_05_py39"),
pytest.param("tutorial003_05_py310", marks=needs_py310),
],
)
diff --git a/tests/test_tutorial/test_response_model/test_tutorial004.py b/tests/test_tutorial/test_response_model/test_tutorial004.py
index 449a52b813..9c0d95ebd0 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial004.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial004.py
@@ -1,17 +1,16 @@
import importlib
import pytest
-from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial004",
- pytest.param("tutorial004_py39", marks=needs_py39),
+ pytest.param("tutorial004_py39"),
pytest.param("tutorial004_py310", marks=needs_py310),
],
)
@@ -51,104 +50,98 @@ def test_get(url, data, client: TestClient):
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
}
- }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
},
},
- },
- "summary": "Read Item",
- "operationId": "read_item_items__item_id__get",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "string"},
- "name": "item_id",
- "in": "path",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "required": IsOneOf(
- ["name", "description", "price", "tax", "tags"],
- # TODO: remove when deprecating Pydantic v1
- ["name", "price"],
- ),
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "price": {"title": "Price", "type": "number"},
- "description": IsDict(
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
{
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "tax": {"title": "Tax", "type": "number", "default": 10.5},
- "tags": {
- "title": "Tags",
- "type": "array",
- "items": {"type": "string"},
- "default": [],
- },
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "tax": {"title": "Tax", "type": "number", "default": 10.5},
+ "tags": {
+ "title": "Tags",
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
},
},
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
},
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
},
- },
- }
- },
- }
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
+ },
+ },
+ }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_response_model/test_tutorial005.py b/tests/test_tutorial/test_response_model/test_tutorial005.py
index a633a3fddd..63e8535db0 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial005.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial005.py
@@ -1,8 +1,8 @@
import importlib
import pytest
-from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from ...utils import needs_py310
@@ -10,7 +10,7 @@ from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial005",
+ pytest.param("tutorial005_py39"),
pytest.param("tutorial005_py310", marks=needs_py310),
],
)
@@ -40,132 +40,126 @@ def test_read_item_public_data(client: TestClient):
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}/name": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}/name": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
}
- }
+ },
},
- },
- },
- "summary": "Read Item Name",
- "operationId": "read_item_name_items__item_id__name_get",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "string"},
- "name": "item_id",
- "in": "path",
- }
- ],
- }
- },
- "/items/{item_id}/public": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
}
- }
+ },
},
},
- },
- "summary": "Read Item Public Data",
- "operationId": "read_item_public_data_items__item_id__public_get",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "string"},
- "name": "item_id",
- "in": "path",
- }
- ],
- }
- },
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "required": IsOneOf(
- ["name", "description", "price", "tax"],
- # TODO: remove when deprecating Pydantic v1
- ["name", "price"],
- ),
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "price": {"title": "Price", "type": "number"},
- "description": IsDict(
+ "summary": "Read Item Name",
+ "operationId": "read_item_name_items__item_id__name_get",
+ "parameters": [
{
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
+ },
+ "/items/{item_id}/public": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Item Public Data",
+ "operationId": "read_item_public_data_items__item_id__public_get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "tax": {"title": "Tax", "type": "number", "default": 10.5},
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
},
+ "tax": {"title": "Tax", "type": "number", "default": 10.5},
},
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
},
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
},
- },
- }
- },
- }
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
+ },
+ },
+ }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_response_model/test_tutorial006.py b/tests/test_tutorial/test_response_model/test_tutorial006.py
index 863522d1b4..08ab659527 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial006.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial006.py
@@ -1,8 +1,8 @@
import importlib
import pytest
-from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
from ...utils import needs_py310
@@ -10,7 +10,7 @@ from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial006",
+ pytest.param("tutorial006_py39"),
pytest.param("tutorial006_py310", marks=needs_py310),
],
)
@@ -40,132 +40,126 @@ def test_read_item_public_data(client: TestClient):
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}/name": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}/name": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
}
- }
+ },
},
- },
- },
- "summary": "Read Item Name",
- "operationId": "read_item_name_items__item_id__name_get",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "string"},
- "name": "item_id",
- "in": "path",
- }
- ],
- }
- },
- "/items/{item_id}/public": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
}
- }
+ },
},
},
- },
- "summary": "Read Item Public Data",
- "operationId": "read_item_public_data_items__item_id__public_get",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "string"},
- "name": "item_id",
- "in": "path",
- }
- ],
- }
- },
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "required": IsOneOf(
- ["name", "description", "price", "tax"],
- # TODO: remove when deprecating Pydantic v1
- ["name", "price"],
- ),
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "price": {"title": "Price", "type": "number"},
- "description": IsDict(
+ "summary": "Read Item Name",
+ "operationId": "read_item_name_items__item_id__name_get",
+ "parameters": [
{
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
+ },
+ "/items/{item_id}/public": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Item Public Data",
+ "operationId": "read_item_public_data_items__item_id__public_get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "tax": {"title": "Tax", "type": "number", "default": 10.5},
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
},
+ "tax": {"title": "Tax", "type": "number", "default": 10.5},
},
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
},
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
},
- },
- }
- },
- }
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
+ },
+ },
+ }
+ },
+ }
+ )
diff --git a/tests/test_tutorial/test_response_status_code/__init__.py b/tests/test_tutorial/test_response_status_code/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py
new file mode 100644
index 0000000000..ddf55a045d
--- /dev/null
+++ b/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py
@@ -0,0 +1,96 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial002_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.response_status_code.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_create_item(client: TestClient):
+ response = client.post("/items/", params={"name": "Test Item"})
+ assert response.status_code == 201, response.text
+ assert response.json() == {"name": "Test Item"}
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "parameters": [
+ {
+ "name": "name",
+ "in": "query",
+ "required": True,
+ "schema": {"title": "Name", "type": "string"},
+ }
+ ],
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py
index c21cbb4bc2..82f69fd463 100644
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py
@@ -3,13 +3,13 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py310, needs_pydanticv2
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
+ pytest.param("tutorial001_py39"),
pytest.param("tutorial001_py310", marks=needs_py310),
],
)
@@ -20,7 +20,6 @@ def get_client(request: pytest.FixtureRequest):
return client
-@needs_pydanticv2
def test_post_body_example(client: TestClient):
response = client.put(
"/items/5",
@@ -34,7 +33,6 @@ def test_post_body_example(client: TestClient):
assert response.status_code == 200
-@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py
similarity index 79%
rename from tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py
rename to tests/test_tutorial/test_schema_extra_example/test_tutorial002.py
index b79f42e642..4f52408605 100644
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py
@@ -3,14 +3,14 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py310, needs_pydanticv1
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001_pv1",
- pytest.param("tutorial001_pv1_py310", marks=needs_py310),
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
@@ -20,7 +20,6 @@ def get_client(request: pytest.FixtureRequest):
return client
-@needs_pydanticv1
def test_post_body_example(client: TestClient):
response = client.put(
"/items/5",
@@ -34,7 +33,6 @@ def test_post_body_example(client: TestClient):
assert response.status_code == 200
-@needs_pydanticv1
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
@@ -49,19 +47,19 @@ def test_openapi_schema(client: TestClient):
"operationId": "update_item_items__item_id__put",
"parameters": [
{
- "required": True,
- "schema": {"type": "integer", "title": "Item Id"},
"name": "item_id",
"in": "path",
+ "required": True,
+ "schema": {"type": "integer", "title": "Item Id"},
}
],
"requestBody": {
+ "required": True,
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
- "required": True,
},
"responses": {
"200": {
@@ -97,22 +95,30 @@ def test_openapi_schema(client: TestClient):
},
"Item": {
"properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {"type": "string", "title": "Description"},
- "price": {"type": "number", "title": "Price"},
- "tax": {"type": "number", "title": "Tax"},
+ "name": {
+ "type": "string",
+ "title": "Name",
+ "examples": ["Foo"],
+ },
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ "examples": ["A very nice Item"],
+ },
+ "price": {
+ "type": "number",
+ "title": "Price",
+ "examples": [35.4],
+ },
+ "tax": {
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ "title": "Tax",
+ "examples": [3.2],
+ },
},
"type": "object",
"required": ["name", "price"],
"title": "Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- }
- ],
},
"ValidationError": {
"properties": {
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py
new file mode 100644
index 0000000000..3529a9bf02
--- /dev/null
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py
@@ -0,0 +1,143 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial003_py39"),
+ pytest.param("tutorial003_py310", marks=needs_py310),
+ pytest.param("tutorial003_an_py39"),
+ pytest.param("tutorial003_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_post_body_example(client: TestClient):
+ response = client.put(
+ "/items/5",
+ json={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ )
+ assert response.status_code == 200
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ # insert_assert(response.json())
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "name": "item_id",
+ "in": "path",
+ "required": True,
+ "schema": {"type": "integer", "title": "Item Id"},
+ }
+ ],
+ "requestBody": {
+ "required": True,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ "examples": [
+ {
+ "description": "A very nice Item",
+ "name": "Foo",
+ "price": 35.4,
+ "tax": 3.2,
+ }
+ ],
+ },
+ }
+ },
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "Item": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ "price": {"type": "number", "title": "Price"},
+ "tax": {
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ "title": "Tax",
+ },
+ },
+ "type": "object",
+ "required": ["name", "price"],
+ "title": "Item",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py
index 61aefd12a8..9326e06290 100644
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py
@@ -1,19 +1,17 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial004",
+ pytest.param("tutorial004_py39"),
pytest.param("tutorial004_py310", marks=needs_py310),
- "tutorial004_an",
- pytest.param("tutorial004_an_py39", marks=needs_py39),
+ pytest.param("tutorial004_an_py39"),
pytest.param("tutorial004_an_py310", marks=needs_py310),
],
)
@@ -59,46 +57,22 @@ def test_openapi_schema(client: TestClient):
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict(
- {
- "$ref": "#/components/schemas/Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- {"name": "Bar", "price": "35.4"},
- {
- "name": "Baz",
- "price": "thirty five point four",
- },
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- {"name": "Bar", "price": "35.4"},
- {
- "name": "Baz",
- "price": "thirty five point four",
- },
- ],
- }
- )
+ "schema": {
+ "$ref": "#/components/schemas/Item",
+ "examples": [
+ {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ {"name": "Bar", "price": "35.4"},
+ {
+ "name": "Baz",
+ "price": "thirty five point four",
+ },
+ ],
+ }
}
},
"required": True,
@@ -141,27 +115,15 @@ def test_openapi_schema(client: TestClient):
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
"price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py
index 12859227b1..2d0dee48ca 100644
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py
@@ -1,19 +1,17 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial005",
+ pytest.param("tutorial005_py39"),
pytest.param("tutorial005_py310", marks=needs_py310),
- "tutorial005_an",
- pytest.param("tutorial005_an_py39", marks=needs_py39),
+ pytest.param("tutorial005_an_py39"),
pytest.param("tutorial005_an_py310", marks=needs_py310),
],
)
@@ -59,16 +57,7 @@ def test_openapi_schema(client: TestClient) -> None:
"requestBody": {
"content": {
"application/json": {
- "schema": IsDict({"$ref": "#/components/schemas/Item"})
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- }
- ),
+ "schema": {"$ref": "#/components/schemas/Item"},
"examples": {
"normal": {
"summary": "A normal example",
@@ -135,27 +124,15 @@ def test_openapi_schema(client: TestClient) -> None:
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
+ "description": {
+ "title": "Description",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
"price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
+ "tax": {
+ "title": "Tax",
+ "anyOf": [{"type": "number"}, {"type": "null"}],
+ },
},
},
"ValidationError": {
diff --git a/tests/test_tutorial/test_security/test_tutorial001.py b/tests/test_tutorial/test_security/test_tutorial001.py
index f572d6e3e1..cdaa50b191 100644
--- a/tests/test_tutorial/test_security/test_tutorial001.py
+++ b/tests/test_tutorial/test_security/test_tutorial001.py
@@ -3,15 +3,12 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial001",
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ pytest.param("tutorial001_py39"),
+ pytest.param("tutorial001_an_py39"),
],
)
def get_client(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_security/test_tutorial002.py b/tests/test_tutorial/test_security/test_tutorial002.py
new file mode 100644
index 0000000000..85c076b1d2
--- /dev/null
+++ b/tests/test_tutorial/test_security/test_tutorial002.py
@@ -0,0 +1,71 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial002_py39"),
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ pytest.param("tutorial002_an_py39"),
+ pytest.param("tutorial002_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.security.{request.param}")
+ client = TestClient(mod.app)
+ return client
+
+
+def test_no_token(client: TestClient):
+ response = client.get("/users/me")
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+def test_token(client: TestClient):
+ response = client.get("/users/me", headers={"Authorization": "Bearer testtoken"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "username": "testtokenfakedecoded",
+ "email": "john@example.com",
+ "full_name": "John Doe",
+ "disabled": None,
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/users/me": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Read Users Me",
+ "operationId": "read_users_me_users_me_get",
+ "security": [{"OAuth2PasswordBearer": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "OAuth2PasswordBearer": {
+ "type": "oauth2",
+ "flows": {"password": {"scopes": {}, "tokenUrl": "token"}},
+ }
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_security/test_tutorial003.py b/tests/test_tutorial/test_security/test_tutorial003.py
index 2bbb2e8510..6a786348cf 100644
--- a/tests/test_tutorial/test_security/test_tutorial003.py
+++ b/tests/test_tutorial/test_security/test_tutorial003.py
@@ -1,19 +1,17 @@
import importlib
import pytest
-from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial003",
+ pytest.param("tutorial003_py39"),
pytest.param("tutorial003_py310", marks=needs_py310),
- "tutorial003_an",
- pytest.param("tutorial003_an_py39", marks=needs_py39),
+ pytest.param("tutorial003_an_py39"),
pytest.param("tutorial003_an_py310", marks=needs_py310),
],
)
@@ -66,7 +64,7 @@ def test_token(client: TestClient):
def test_incorrect_token(client: TestClient):
response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.json() == {"detail": "Not authenticated"}
assert response.headers["WWW-Authenticate"] == "Bearer"
@@ -145,23 +143,13 @@ def test_openapi_schema(client: TestClient):
"required": ["username", "password"],
"type": "object",
"properties": {
- "grant_type": IsDict(
- {
- "title": "Grant Type",
- "anyOf": [
- {"pattern": "^password$", "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Grant Type",
- "pattern": "^password$",
- "type": "string",
- }
- ),
+ "grant_type": {
+ "title": "Grant Type",
+ "anyOf": [
+ {"pattern": "^password$", "type": "string"},
+ {"type": "null"},
+ ],
+ },
"username": {"title": "Username", "type": "string"},
"password": {
"title": "Password",
@@ -169,31 +157,15 @@ def test_openapi_schema(client: TestClient):
"format": "password",
},
"scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "format": "password",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Client Secret",
- "type": "string",
- "format": "password",
- }
- ),
+ "client_id": {
+ "title": "Client Id",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "client_secret": {
+ "title": "Client Secret",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "format": "password",
+ },
},
},
"ValidationError": {
diff --git a/tests/test_tutorial/test_security/test_tutorial004.py b/tests/test_tutorial/test_security/test_tutorial004.py
new file mode 100644
index 0000000000..b5e3d39ef7
--- /dev/null
+++ b/tests/test_tutorial/test_security/test_tutorial004.py
@@ -0,0 +1,363 @@
+import importlib
+from types import ModuleType
+from unittest.mock import patch
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@pytest.fixture(
+ name="mod",
+ params=[
+ pytest.param("tutorial004_py39"),
+ pytest.param("tutorial004_py310", marks=needs_py310),
+ pytest.param("tutorial004_an_py39"),
+ pytest.param("tutorial004_an_py310", marks=needs_py310),
+ ],
+)
+def get_mod(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.security.{request.param}")
+
+ return mod
+
+
+def get_access_token(*, username="johndoe", password="secret", client: TestClient):
+ data = {"username": username, "password": password}
+ response = client.post("/token", data=data)
+ content = response.json()
+ access_token = content.get("access_token")
+ return access_token
+
+
+def test_login(mod: ModuleType):
+ client = TestClient(mod.app)
+ response = client.post("/token", data={"username": "johndoe", "password": "secret"})
+ assert response.status_code == 200, response.text
+ content = response.json()
+ assert "access_token" in content
+ assert content["token_type"] == "bearer"
+
+
+def test_login_incorrect_password(mod: ModuleType):
+ client = TestClient(mod.app)
+ response = client.post(
+ "/token", data={"username": "johndoe", "password": "incorrect"}
+ )
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Incorrect username or password"}
+
+
+def test_login_incorrect_username(mod: ModuleType):
+ client = TestClient(mod.app)
+ response = client.post("/token", data={"username": "foo", "password": "secret"})
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Incorrect username or password"}
+
+
+def test_no_token(mod: ModuleType):
+ client = TestClient(mod.app)
+ response = client.get("/users/me")
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+def test_token(mod: ModuleType):
+ client = TestClient(mod.app)
+ access_token = get_access_token(client=client)
+ response = client.get(
+ "/users/me", headers={"Authorization": f"Bearer {access_token}"}
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "disabled": False,
+ }
+
+
+def test_incorrect_token(mod: ModuleType):
+ client = TestClient(mod.app)
+ response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Could not validate credentials"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+def test_incorrect_token_type(mod: ModuleType):
+ client = TestClient(mod.app)
+ response = client.get(
+ "/users/me", headers={"Authorization": "Notexistent testtoken"}
+ )
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+def test_verify_password(mod: ModuleType):
+ assert mod.verify_password(
+ "secret", mod.fake_users_db["johndoe"]["hashed_password"]
+ )
+
+
+def test_get_password_hash(mod: ModuleType):
+ assert mod.get_password_hash("johndoe")
+
+
+def test_create_access_token(mod: ModuleType):
+ access_token = mod.create_access_token(data={"data": "foo"})
+ assert access_token
+
+
+def test_token_no_sub(mod: ModuleType):
+ client = TestClient(mod.app)
+
+ response = client.get(
+ "/users/me",
+ headers={
+ "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE"
+ },
+ )
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Could not validate credentials"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+def test_token_no_username(mod: ModuleType):
+ client = TestClient(mod.app)
+
+ response = client.get(
+ "/users/me",
+ headers={
+ "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y"
+ },
+ )
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Could not validate credentials"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+def test_token_nonexistent_user(mod: ModuleType):
+ client = TestClient(mod.app)
+
+ response = client.get(
+ "/users/me",
+ headers={
+ "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw"
+ },
+ )
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Could not validate credentials"}
+ assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+def test_token_inactive_user(mod: ModuleType):
+ client = TestClient(mod.app)
+ alice_user_data = {
+ "username": "alice",
+ "full_name": "Alice Wonderson",
+ "email": "alice@example.com",
+ "hashed_password": mod.get_password_hash("secretalice"),
+ "disabled": True,
+ }
+ with patch.dict(f"{mod.__name__}.fake_users_db", {"alice": alice_user_data}):
+ access_token = get_access_token(
+ username="alice", password="secretalice", client=client
+ )
+ response = client.get(
+ "/users/me", headers={"Authorization": f"Bearer {access_token}"}
+ )
+ assert response.status_code == 400, response.text
+ assert response.json() == {"detail": "Inactive user"}
+
+
+def test_read_items(mod: ModuleType):
+ client = TestClient(mod.app)
+ access_token = get_access_token(client=client)
+ response = client.get(
+ "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"}
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}]
+
+
+def test_openapi_schema(mod: ModuleType):
+ client = TestClient(mod.app)
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/token": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Token"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Login For Access Token",
+ "operationId": "login_for_access_token_token_post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ },
+ "/users/me/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ "summary": "Read Users Me",
+ "operationId": "read_users_me_users_me__get",
+ "security": [{"OAuth2PasswordBearer": []}],
+ }
+ },
+ "/users/me/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Read Own Items",
+ "operationId": "read_own_items_users_me_items__get",
+ "security": [{"OAuth2PasswordBearer": []}],
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "User": {
+ "title": "User",
+ "required": ["username"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {
+ "title": "Email",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "full_name": {
+ "title": "Full Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "disabled": {
+ "title": "Disabled",
+ "anyOf": [{"type": "boolean"}, {"type": "null"}],
+ },
+ },
+ },
+ "Token": {
+ "title": "Token",
+ "required": ["access_token", "token_type"],
+ "type": "object",
+ "properties": {
+ "access_token": {"title": "Access Token", "type": "string"},
+ "token_type": {"title": "Token Type", "type": "string"},
+ },
+ },
+ "Body_login_for_access_token_token_post": {
+ "title": "Body_login_for_access_token_token_post",
+ "required": ["username", "password"],
+ "type": "object",
+ "properties": {
+ "grant_type": {
+ "title": "Grant Type",
+ "anyOf": [
+ {"pattern": "^password$", "type": "string"},
+ {"type": "null"},
+ ],
+ },
+ "username": {"title": "Username", "type": "string"},
+ "password": {
+ "title": "Password",
+ "type": "string",
+ "format": "password",
+ },
+ "scope": {"title": "Scope", "type": "string", "default": ""},
+ "client_id": {
+ "title": "Client Id",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
+ "client_secret": {
+ "title": "Client Secret",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "format": "password",
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ },
+ "securitySchemes": {
+ "OAuth2PasswordBearer": {
+ "type": "oauth2",
+ "flows": {
+ "password": {
+ "scopes": {},
+ "tokenUrl": "token",
+ }
+ },
+ }
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_security/test_tutorial005.py b/tests/test_tutorial/test_security/test_tutorial005.py
index ad644d61bb..25b47f0adc 100644
--- a/tests/test_tutorial/test_security/test_tutorial005.py
+++ b/tests/test_tutorial/test_security/test_tutorial005.py
@@ -2,20 +2,18 @@ import importlib
from types import ModuleType
import pytest
-from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="mod",
params=[
- "tutorial005",
+ pytest.param("tutorial005_py39"),
pytest.param("tutorial005_py310", marks=needs_py310),
- "tutorial005_an",
- pytest.param("tutorial005_py39", marks=needs_py39),
- pytest.param("tutorial005_an_py39", marks=needs_py39),
+ pytest.param("tutorial005_an_py39"),
pytest.param("tutorial005_an_py310", marks=needs_py310),
],
)
@@ -217,240 +215,200 @@ def test_openapi_schema(mod: ModuleType):
client = TestClient(mod.app)
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/token": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Token"}
- }
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/token": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Token"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
},
},
- "422": {
- "description": "Validation Error",
+ "summary": "Login For Access Token",
+ "operationId": "login_for_access_token_token_post",
+ "requestBody": {
"content": {
- "application/json": {
+ "application/x-www-form-urlencoded": {
"schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
+ "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
}
}
},
+ "required": True,
},
- },
- "summary": "Login For Access Token",
- "operationId": "login_for_access_token_token_post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": {
- "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
- }
+ }
+ },
+ "/users/me/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
}
},
- "required": True,
- },
- }
+ "summary": "Read Users Me",
+ "operationId": "read_users_me_users_me__get",
+ "security": [{"OAuth2PasswordBearer": ["me"]}],
+ }
+ },
+ "/users/me/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Read Own Items",
+ "operationId": "read_own_items_users_me_items__get",
+ "security": [{"OAuth2PasswordBearer": ["items", "me"]}],
+ }
+ },
+ "/status/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Read System Status",
+ "operationId": "read_system_status_status__get",
+ "security": [{"OAuth2PasswordBearer": []}],
+ }
+ },
},
- "/users/me/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/User"}
- }
- },
- }
- },
- "summary": "Read Users Me",
- "operationId": "read_users_me_users_me__get",
- "security": [{"OAuth2PasswordBearer": ["me"]}],
- }
- },
- "/users/me/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Own Items",
- "operationId": "read_own_items_users_me_items__get",
- "security": [{"OAuth2PasswordBearer": ["items", "me"]}],
- }
- },
- "/status/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read System Status",
- "operationId": "read_system_status_status__get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- },
- },
- "components": {
- "schemas": {
- "User": {
- "title": "User",
- "required": IsOneOf(
- ["username", "email", "full_name", "disabled"],
- # TODO: remove when deprecating Pydantic v1
- ["username"],
- ),
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "email": IsDict(
- {
+ "components": {
+ "schemas": {
+ "User": {
+ "title": "User",
+ "required": ["username"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {
"title": "Email",
"anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Email", "type": "string"}
- ),
- "full_name": IsDict(
- {
+ },
+ "full_name": {
"title": "Full Name",
"anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- "disabled": IsDict(
- {
+ },
+ "disabled": {
"title": "Disabled",
"anyOf": [{"type": "boolean"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Disabled", "type": "boolean"}
- ),
+ },
+ },
},
- },
- "Token": {
- "title": "Token",
- "required": ["access_token", "token_type"],
- "type": "object",
- "properties": {
- "access_token": {"title": "Access Token", "type": "string"},
- "token_type": {"title": "Token Type", "type": "string"},
+ "Token": {
+ "title": "Token",
+ "required": ["access_token", "token_type"],
+ "type": "object",
+ "properties": {
+ "access_token": {"title": "Access Token", "type": "string"},
+ "token_type": {"title": "Token Type", "type": "string"},
+ },
},
- },
- "Body_login_for_access_token_token_post": {
- "title": "Body_login_for_access_token_token_post",
- "required": ["username", "password"],
- "type": "object",
- "properties": {
- "grant_type": IsDict(
- {
+ "Body_login_for_access_token_token_post": {
+ "title": "Body_login_for_access_token_token_post",
+ "required": ["username", "password"],
+ "type": "object",
+ "properties": {
+ "grant_type": {
"title": "Grant Type",
"anyOf": [
{"pattern": "^password$", "type": "string"},
{"type": "null"},
],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Grant Type",
- "pattern": "^password$",
+ },
+ "username": {"title": "Username", "type": "string"},
+ "password": {
+ "title": "Password",
"type": "string",
- }
- ),
- "username": {"title": "Username", "type": "string"},
- "password": {
- "title": "Password",
- "type": "string",
- "format": "password",
- },
- "scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
+ "format": "password",
+ },
+ "scope": {
+ "title": "Scope",
+ "type": "string",
+ "default": "",
+ },
+ "client_id": {
"title": "Client Id",
"anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
+ },
+ "client_secret": {
"title": "Client Secret",
"anyOf": [{"type": "string"}, {"type": "null"}],
"format": "password",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Client Secret",
- "type": "string",
- "format": "password",
- }
- ),
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
},
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- },
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {
- "password": {
- "scopes": {
- "me": "Read information about the current user.",
- "items": "Read items.",
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
},
- "tokenUrl": "token",
- }
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
},
- }
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
+ },
+ },
+ },
+ "securitySchemes": {
+ "OAuth2PasswordBearer": {
+ "type": "oauth2",
+ "flows": {
+ "password": {
+ "scopes": {
+ "me": "Read information about the current user.",
+ "items": "Read items.",
+ },
+ "tokenUrl": "token",
+ }
+ },
+ }
+ },
},
- },
- }
+ }
+ )
diff --git a/tests/test_tutorial/test_security/test_tutorial006.py b/tests/test_tutorial/test_security/test_tutorial006.py
index 40b4138062..a4b3104bbc 100644
--- a/tests/test_tutorial/test_security/test_tutorial006.py
+++ b/tests/test_tutorial/test_security/test_tutorial006.py
@@ -4,15 +4,12 @@ from base64 import b64encode
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39
-
@pytest.fixture(
name="client",
params=[
- "tutorial006",
- "tutorial006_an",
- pytest.param("tutorial006_an_py39", marks=needs_py39),
+ pytest.param("tutorial006_py39"),
+ pytest.param("tutorial006_an_py39"),
],
)
def get_client(request: pytest.FixtureRequest):
@@ -41,7 +38,7 @@ def test_security_http_basic_invalid_credentials(client: TestClient):
)
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == "Basic"
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.json() == {"detail": "Not authenticated"}
def test_security_http_basic_non_basic_credentials(client: TestClient):
@@ -50,7 +47,7 @@ def test_security_http_basic_non_basic_credentials(client: TestClient):
response = client.get("/users/me", headers={"Authorization": auth_header})
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == "Basic"
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.json() == {"detail": "Not authenticated"}
def test_openapi_schema(client: TestClient):
diff --git a/tests/test_tutorial/test_security/test_tutorial007.py b/tests/test_tutorial/test_security/test_tutorial007.py
new file mode 100644
index 0000000000..28b70a2d43
--- /dev/null
+++ b/tests/test_tutorial/test_security/test_tutorial007.py
@@ -0,0 +1,89 @@
+import importlib
+from base64 import b64encode
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial007_py39"),
+ pytest.param("tutorial007_an_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.security.{request.param}")
+ return TestClient(mod.app)
+
+
+def test_security_http_basic(client: TestClient):
+ response = client.get("/users/me", auth=("stanleyjobson", "swordfish"))
+ assert response.status_code == 200, response.text
+ assert response.json() == {"username": "stanleyjobson"}
+
+
+def test_security_http_basic_no_credentials(client: TestClient):
+ response = client.get("/users/me")
+ assert response.json() == {"detail": "Not authenticated"}
+ assert response.status_code == 401, response.text
+ assert response.headers["WWW-Authenticate"] == "Basic"
+
+
+def test_security_http_basic_invalid_credentials(client: TestClient):
+ response = client.get(
+ "/users/me", headers={"Authorization": "Basic notabase64token"}
+ )
+ assert response.status_code == 401, response.text
+ assert response.headers["WWW-Authenticate"] == "Basic"
+ assert response.json() == {"detail": "Not authenticated"}
+
+
+def test_security_http_basic_non_basic_credentials(client: TestClient):
+ payload = b64encode(b"johnsecret").decode("ascii")
+ auth_header = f"Basic {payload}"
+ response = client.get("/users/me", headers={"Authorization": auth_header})
+ assert response.status_code == 401, response.text
+ assert response.headers["WWW-Authenticate"] == "Basic"
+ assert response.json() == {"detail": "Not authenticated"}
+
+
+def test_security_http_basic_invalid_username(client: TestClient):
+ response = client.get("/users/me", auth=("alice", "swordfish"))
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Incorrect username or password"}
+ assert response.headers["WWW-Authenticate"] == "Basic"
+
+
+def test_security_http_basic_invalid_password(client: TestClient):
+ response = client.get("/users/me", auth=("stanleyjobson", "wrongpassword"))
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Incorrect username or password"}
+ assert response.headers["WWW-Authenticate"] == "Basic"
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/users/me": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Read Current User",
+ "operationId": "read_current_user_users_me_get",
+ "security": [{"HTTPBasic": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}}
+ },
+ }
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py
index 059fb889b3..275b234877 100644
--- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py
+++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py
@@ -3,15 +3,14 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310, needs_pydanticv2
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial001",
+ pytest.param("tutorial001_py39"),
pytest.param("tutorial001_py310", marks=needs_py310),
- pytest.param("tutorial001_py39", marks=needs_py39),
],
)
def get_client(request: pytest.FixtureRequest) -> TestClient:
@@ -39,7 +38,6 @@ def test_read_items(client: TestClient) -> None:
]
-@needs_pydanticv2
def test_openapi_schema(client: TestClient) -> None:
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py
index cc9afeab75..8230e39226 100644
--- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py
+++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py
@@ -3,15 +3,14 @@ import importlib
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_py39, needs_py310, needs_pydanticv2
+from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
- "tutorial002",
+ pytest.param("tutorial002_py39"),
pytest.param("tutorial002_py310", marks=needs_py310),
- pytest.param("tutorial002_py39", marks=needs_py39),
],
)
def get_client(request: pytest.FixtureRequest) -> TestClient:
@@ -39,7 +38,6 @@ def test_read_items(client: TestClient) -> None:
]
-@needs_pydanticv2
def test_openapi_schema(client: TestClient) -> None:
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
diff --git a/tests/test_tutorial/test_settings/test_app01.py b/tests/test_tutorial/test_settings/test_app01.py
new file mode 100644
index 0000000000..0c5e440f1a
--- /dev/null
+++ b/tests/test_tutorial/test_settings/test_app01.py
@@ -0,0 +1,78 @@
+import importlib
+import sys
+
+import pytest
+from dirty_equals import IsAnyStr
+from fastapi.testclient import TestClient
+from pydantic import ValidationError
+from pytest import MonkeyPatch
+
+
+@pytest.fixture(
+ name="mod_name",
+ params=[
+ pytest.param("app01_py39"),
+ ],
+)
+def get_mod_name(request: pytest.FixtureRequest):
+ return f"docs_src.settings.{request.param}.main"
+
+
+@pytest.fixture(name="client")
+def get_test_client(mod_name: str, monkeypatch: MonkeyPatch) -> TestClient:
+ if mod_name in sys.modules:
+ del sys.modules[mod_name]
+ monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
+ main_mod = importlib.import_module(mod_name)
+ return TestClient(main_mod.app)
+
+
+def test_settings_validation_error(mod_name: str, monkeypatch: MonkeyPatch):
+ monkeypatch.delenv("ADMIN_EMAIL", raising=False)
+ if mod_name in sys.modules:
+ del sys.modules[mod_name] # pragma: no cover
+
+ with pytest.raises(ValidationError) as exc_info:
+ importlib.import_module(mod_name)
+ assert exc_info.value.errors() == [
+ {
+ "loc": ("admin_email",),
+ "msg": "Field required",
+ "type": "missing",
+ "input": {},
+ "url": IsAnyStr,
+ }
+ ]
+
+
+def test_app(client: TestClient):
+ response = client.get("/info")
+ data = response.json()
+ assert data == {
+ "app_name": "Awesome API",
+ "admin_email": "admin@example.com",
+ "items_per_user": 50,
+ }
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/info": {
+ "get": {
+ "operationId": "info_info_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Info",
+ }
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_settings/test_app02.py b/tests/test_tutorial/test_settings/test_app02.py
index eced88c044..9cbed4fd1b 100644
--- a/tests/test_tutorial/test_settings/test_app02.py
+++ b/tests/test_tutorial/test_settings/test_app02.py
@@ -1,20 +1,40 @@
+import importlib
+from types import ModuleType
+
+import pytest
from pytest import MonkeyPatch
-from ...utils import needs_pydanticv2
+
+@pytest.fixture(
+ name="mod_path",
+ params=[
+ pytest.param("app02_py39"),
+ pytest.param("app02_an_py39"),
+ ],
+)
+def get_mod_path(request: pytest.FixtureRequest):
+ mod_path = f"docs_src.settings.{request.param}"
+ return mod_path
-@needs_pydanticv2
-def test_settings(monkeypatch: MonkeyPatch):
- from docs_src.settings.app02 import main
+@pytest.fixture(name="main_mod")
+def get_main_mod(mod_path: str) -> ModuleType:
+ main_mod = importlib.import_module(f"{mod_path}.main")
+ return main_mod
+
+@pytest.fixture(name="test_main_mod")
+def get_test_main_mod(mod_path: str) -> ModuleType:
+ test_main_mod = importlib.import_module(f"{mod_path}.test_main")
+ return test_main_mod
+
+
+def test_settings(main_mod: ModuleType, monkeypatch: MonkeyPatch):
monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
- settings = main.get_settings()
+ settings = main_mod.get_settings()
assert settings.app_name == "Awesome API"
assert settings.items_per_user == 50
-@needs_pydanticv2
-def test_override_settings():
- from docs_src.settings.app02 import test_main
-
- test_main.test_app()
+def test_override_settings(test_main_mod: ModuleType):
+ test_main_mod.test_app()
diff --git a/tests/test_tutorial/test_settings/test_app03.py b/tests/test_tutorial/test_settings/test_app03.py
new file mode 100644
index 0000000000..72de497967
--- /dev/null
+++ b/tests/test_tutorial/test_settings/test_app03.py
@@ -0,0 +1,44 @@
+import importlib
+from types import ModuleType
+
+import pytest
+from fastapi.testclient import TestClient
+from pytest import MonkeyPatch
+
+
+@pytest.fixture(
+ name="mod_path",
+ params=[
+ pytest.param("app03_py39"),
+ pytest.param("app03_an_py39"),
+ ],
+)
+def get_mod_path(request: pytest.FixtureRequest):
+ mod_path = f"docs_src.settings.{request.param}"
+ return mod_path
+
+
+@pytest.fixture(name="main_mod")
+def get_main_mod(mod_path: str) -> ModuleType:
+ main_mod = importlib.import_module(f"{mod_path}.main")
+ return main_mod
+
+
+def test_settings(main_mod: ModuleType, monkeypatch: MonkeyPatch):
+ monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
+ settings = main_mod.get_settings()
+ assert settings.app_name == "Awesome API"
+ assert settings.admin_email == "admin@example.com"
+ assert settings.items_per_user == 50
+
+
+def test_endpoint(main_mod: ModuleType, monkeypatch: MonkeyPatch):
+ monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
+ client = TestClient(main_mod.app)
+ response = client.get("/info")
+ assert response.status_code == 200
+ assert response.json() == {
+ "app_name": "Awesome API",
+ "admin_email": "admin@example.com",
+ "items_per_user": 50,
+ }
diff --git a/tests/test_tutorial/test_settings/test_tutorial001.py b/tests/test_tutorial/test_settings/test_tutorial001.py
index 92a5782d4d..f4576a0d21 100644
--- a/tests/test_tutorial/test_settings/test_tutorial001.py
+++ b/tests/test_tutorial/test_settings/test_tutorial001.py
@@ -4,16 +4,8 @@ import pytest
from fastapi.testclient import TestClient
from pytest import MonkeyPatch
-from ...utils import needs_pydanticv1, needs_pydanticv2
-
-@pytest.fixture(
- name="app",
- params=[
- pytest.param("tutorial001", marks=needs_pydanticv2),
- pytest.param("tutorial001_pv1", marks=needs_pydanticv1),
- ],
-)
+@pytest.fixture(name="app", params=[pytest.param("tutorial001_py39")])
def get_app(request: pytest.FixtureRequest, monkeypatch: MonkeyPatch):
monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
mod = importlib.import_module(f"docs_src.settings.{request.param}")
diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_sql_databases/test_tutorial001.py
index 6604a2fd38..2c628f5257 100644
--- a/tests/test_tutorial/test_sql_databases/test_tutorial001.py
+++ b/tests/test_tutorial/test_sql_databases/test_tutorial001.py
@@ -2,14 +2,14 @@ import importlib
import warnings
import pytest
-from dirty_equals import IsDict, IsInt
+from dirty_equals import IsInt
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from sqlalchemy import StaticPool
from sqlmodel import SQLModel, create_engine
from sqlmodel.main import default_registry
-from tests.utils import needs_py39, needs_py310
+from tests.utils import needs_py310
def clear_sqlmodel():
@@ -22,11 +22,9 @@ def clear_sqlmodel():
@pytest.fixture(
name="client",
params=[
- "tutorial001",
- pytest.param("tutorial001_py39", marks=needs_py39),
+ pytest.param("tutorial001_py39"),
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ pytest.param("tutorial001_an_py39"),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
@@ -45,7 +43,7 @@ def get_client(request: pytest.FixtureRequest):
with TestClient(mod.app) as c:
yield c
- # Clean up connection explicitely to avoid resource warning
+ # Clean up connection explicitly to avoid resource warning
mod.engine.dispose()
@@ -320,33 +318,15 @@ def test_openapi_schema(client: TestClient):
},
"Hero": {
"properties": {
- "id": IsDict(
- {
- "anyOf": [{"type": "integer"}, {"type": "null"}],
- "title": "Id",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "integer",
- "title": "Id",
- }
- ),
+ "id": {
+ "anyOf": [{"type": "integer"}, {"type": "null"}],
+ "title": "Id",
+ },
"name": {"type": "string", "title": "Name"},
- "age": IsDict(
- {
- "anyOf": [{"type": "integer"}, {"type": "null"}],
- "title": "Age",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "integer",
- "title": "Age",
- }
- ),
+ "age": {
+ "anyOf": [{"type": "integer"}, {"type": "null"}],
+ "title": "Age",
+ },
"secret_name": {"type": "string", "title": "Secret Name"},
},
"type": "object",
diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial002.py b/tests/test_tutorial/test_sql_databases/test_tutorial002.py
index 2c4e0988ce..c72c16e9ae 100644
--- a/tests/test_tutorial/test_sql_databases/test_tutorial002.py
+++ b/tests/test_tutorial/test_sql_databases/test_tutorial002.py
@@ -2,14 +2,14 @@ import importlib
import warnings
import pytest
-from dirty_equals import IsDict, IsInt
+from dirty_equals import IsInt
from fastapi.testclient import TestClient
from inline_snapshot import Is, snapshot
from sqlalchemy import StaticPool
from sqlmodel import SQLModel, create_engine
from sqlmodel.main import default_registry
-from tests.utils import needs_py39, needs_py310
+from tests.utils import needs_py310
def clear_sqlmodel():
@@ -22,11 +22,9 @@ def clear_sqlmodel():
@pytest.fixture(
name="client",
params=[
- "tutorial002",
- pytest.param("tutorial002_py39", marks=needs_py39),
+ pytest.param("tutorial002_py39"),
pytest.param("tutorial002_py310", marks=needs_py310),
- "tutorial002_an",
- pytest.param("tutorial002_an_py39", marks=needs_py39),
+ pytest.param("tutorial002_an_py39"),
pytest.param("tutorial002_an_py310", marks=needs_py310),
],
)
@@ -45,7 +43,7 @@ def get_client(request: pytest.FixtureRequest):
with TestClient(mod.app) as c:
yield c
- # Clean up connection explicitely to avoid resource warning
+ # Clean up connection explicitly to avoid resource warning
mod.engine.dispose()
@@ -375,19 +373,10 @@ def test_openapi_schema(client: TestClient):
"HeroCreate": {
"properties": {
"name": {"type": "string", "title": "Name"},
- "age": IsDict(
- {
- "anyOf": [{"type": "integer"}, {"type": "null"}],
- "title": "Age",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "integer",
- "title": "Age",
- }
- ),
+ "age": {
+ "anyOf": [{"type": "integer"}, {"type": "null"}],
+ "title": "Age",
+ },
"secret_name": {"type": "string", "title": "Secret Name"},
},
"type": "object",
@@ -397,19 +386,10 @@ def test_openapi_schema(client: TestClient):
"HeroPublic": {
"properties": {
"name": {"type": "string", "title": "Name"},
- "age": IsDict(
- {
- "anyOf": [{"type": "integer"}, {"type": "null"}],
- "title": "Age",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "integer",
- "title": "Age",
- }
- ),
+ "age": {
+ "anyOf": [{"type": "integer"}, {"type": "null"}],
+ "title": "Age",
+ },
"id": {"type": "integer", "title": "Id"},
},
"type": "object",
@@ -418,45 +398,18 @@ def test_openapi_schema(client: TestClient):
},
"HeroUpdate": {
"properties": {
- "name": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Name",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Name",
- }
- ),
- "age": IsDict(
- {
- "anyOf": [{"type": "integer"}, {"type": "null"}],
- "title": "Age",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "integer",
- "title": "Age",
- }
- ),
- "secret_name": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Secret Name",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "type": "string",
- "title": "Secret Name",
- }
- ),
+ "name": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Name",
+ },
+ "age": {
+ "anyOf": [{"type": "integer"}, {"type": "null"}],
+ "title": "Age",
+ },
+ "secret_name": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Secret Name",
+ },
},
"type": "object",
"title": "HeroUpdate",
diff --git a/tests/test_tutorial/test_static_files/__init__.py b/tests/test_tutorial/test_static_files/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_static_files/test_tutorial001.py b/tests/test_tutorial/test_static_files/test_tutorial001.py
new file mode 100644
index 0000000000..4fbf19ae82
--- /dev/null
+++ b/tests/test_tutorial/test_static_files/test_tutorial001.py
@@ -0,0 +1,40 @@
+import os
+from pathlib import Path
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(scope="module")
+def client():
+ static_dir: Path = Path(os.getcwd()) / "static"
+ static_dir.mkdir(exist_ok=True)
+ sample_file = static_dir / "sample.txt"
+ sample_file.write_text("This is a sample static file.")
+ from docs_src.static_files.tutorial001_py39 import app
+
+ with TestClient(app) as client:
+ yield client
+ sample_file.unlink()
+ static_dir.rmdir()
+
+
+def test_static_files(client: TestClient):
+ response = client.get("/static/sample.txt")
+ assert response.status_code == 200, response.text
+ assert response.text == "This is a sample static file."
+
+
+def test_static_files_not_found(client: TestClient):
+ response = client.get("/static/non_existent_file.txt")
+ assert response.status_code == 404, response.text
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {},
+ }
diff --git a/tests/test_tutorial/test_sub_applications/test_tutorial001.py b/tests/test_tutorial/test_sub_applications/test_tutorial001.py
index 0790d207be..ef1f80164b 100644
--- a/tests/test_tutorial/test_sub_applications/test_tutorial001.py
+++ b/tests/test_tutorial/test_sub_applications/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.sub_applications.tutorial001 import app
+from docs_src.sub_applications.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_templates/test_tutorial001.py b/tests/test_tutorial/test_templates/test_tutorial001.py
index 4d4729425e..818508037d 100644
--- a/tests/test_tutorial/test_templates/test_tutorial001.py
+++ b/tests/test_tutorial/test_templates/test_tutorial001.py
@@ -11,7 +11,7 @@ def test_main():
shutil.rmtree("./templates")
shutil.copytree("./docs_src/templates/templates/", "./templates")
shutil.copytree("./docs_src/templates/static/", "./static")
- from docs_src.templates.tutorial001 import app
+ from docs_src.templates.tutorial001_py39 import app
client = TestClient(app)
response = client.get("/items/foo")
diff --git a/tests/test_tutorial/test_testing/test_main.py b/tests/test_tutorial/test_testing/test_main_a.py
similarity index 90%
rename from tests/test_tutorial/test_testing/test_main.py
rename to tests/test_tutorial/test_testing/test_main_a.py
index fe34980813..9b3c796bdc 100644
--- a/tests/test_tutorial/test_testing/test_main.py
+++ b/tests/test_tutorial/test_testing/test_main_a.py
@@ -1,4 +1,4 @@
-from docs_src.app_testing.test_main import client, test_read_main
+from docs_src.app_testing.app_a_py39.test_main import client, test_read_main
def test_main():
diff --git a/tests/test_tutorial/test_testing/test_main_b.py b/tests/test_tutorial/test_testing/test_main_b.py
index aa7f969c6d..3d679cd5a6 100644
--- a/tests/test_tutorial/test_testing/test_main_b.py
+++ b/tests/test_tutorial/test_testing/test_main_b.py
@@ -3,16 +3,15 @@ from types import ModuleType
import pytest
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="test_module",
params=[
- "app_b.test_main",
+ "app_b_py39.test_main",
pytest.param("app_b_py310.test_main", marks=needs_py310),
- "app_b_an.test_main",
- pytest.param("app_b_an_py39.test_main", marks=needs_py39),
+ "app_b_an_py39.test_main",
pytest.param("app_b_an_py310.test_main", marks=needs_py310),
],
)
diff --git a/tests/test_tutorial/test_testing/test_tutorial001.py b/tests/test_tutorial/test_testing/test_tutorial001.py
index 471e896c93..5f6533306e 100644
--- a/tests/test_tutorial/test_testing/test_tutorial001.py
+++ b/tests/test_tutorial/test_testing/test_tutorial001.py
@@ -1,4 +1,4 @@
-from docs_src.app_testing.tutorial001 import client, test_read_main
+from docs_src.app_testing.tutorial001_py39 import client, test_read_main
def test_main():
diff --git a/tests/test_tutorial/test_testing/test_tutorial002.py b/tests/test_tutorial/test_testing/test_tutorial002.py
index ec4f91ee7f..cc9b5ba27c 100644
--- a/tests/test_tutorial/test_testing/test_tutorial002.py
+++ b/tests/test_tutorial/test_testing/test_tutorial002.py
@@ -1,4 +1,4 @@
-from docs_src.app_testing.tutorial002 import test_read_main, test_websocket
+from docs_src.app_testing.tutorial002_py39 import test_read_main, test_websocket
def test_main():
diff --git a/tests/test_tutorial/test_testing/test_tutorial003.py b/tests/test_tutorial/test_testing/test_tutorial003.py
index 2a5d670712..4faa820e98 100644
--- a/tests/test_tutorial/test_testing/test_tutorial003.py
+++ b/tests/test_tutorial/test_testing/test_tutorial003.py
@@ -3,5 +3,5 @@ import pytest
def test_main():
with pytest.warns(DeprecationWarning):
- from docs_src.app_testing.tutorial003 import test_read_items
+ from docs_src.app_testing.tutorial003_py39 import test_read_items
test_read_items()
diff --git a/tests/test_tutorial/test_testing/test_tutorial004.py b/tests/test_tutorial/test_testing/test_tutorial004.py
index 812ee44c1f..c95214ffe3 100644
--- a/tests/test_tutorial/test_testing/test_tutorial004.py
+++ b/tests/test_tutorial/test_testing/test_tutorial004.py
@@ -1,4 +1,4 @@
-from docs_src.app_testing.tutorial004 import test_read_items
+from docs_src.app_testing.tutorial004_py39 import test_read_items
def test_main():
diff --git a/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py b/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py
index 00ee6ab1ea..6e9656bf55 100644
--- a/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py
+++ b/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py
@@ -3,16 +3,15 @@ from types import ModuleType
import pytest
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="test_module",
params=[
- "tutorial001",
+ pytest.param("tutorial001_py39"),
pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
+ pytest.param("tutorial001_an_py39"),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
diff --git a/tests/test_tutorial/test_using_request_directly/__init__.py b/tests/test_tutorial/test_using_request_directly/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_using_request_directly/test_tutorial001.py b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py
new file mode 100644
index 0000000000..33e661b164
--- /dev/null
+++ b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py
@@ -0,0 +1,112 @@
+from fastapi.testclient import TestClient
+
+from docs_src.using_request_directly.tutorial001_py39 import app
+
+client = TestClient(app)
+
+
+def test_path_operation():
+ response = client.get("/items/foo")
+ assert response.status_code == 200
+ assert response.json() == {"client_host": "testclient", "item_id": "foo"}
+
+
+def test_openapi():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == {
+ "info": {
+ "title": "FastAPI",
+ "version": "0.1.0",
+ },
+ "openapi": "3.1.0",
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "operationId": "read_root_items__item_id__get",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "item_id",
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "string",
+ },
+ },
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {},
+ },
+ },
+ "description": "Successful Response",
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError",
+ },
+ },
+ },
+ "description": "Validation Error",
+ },
+ },
+ "summary": "Read Root",
+ },
+ },
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError",
+ },
+ "title": "Detail",
+ "type": "array",
+ },
+ },
+ "title": "HTTPValidationError",
+ "type": "object",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ },
+ {
+ "type": "integer",
+ },
+ ],
+ },
+ "title": "Location",
+ "type": "array",
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string",
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string",
+ },
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type",
+ ],
+ "title": "ValidationError",
+ "type": "object",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_websockets/test_tutorial001.py b/tests/test_tutorial/test_websockets/test_tutorial001.py
index 7dbecb2650..4f8368db28 100644
--- a/tests/test_tutorial/test_websockets/test_tutorial001.py
+++ b/tests/test_tutorial/test_websockets/test_tutorial001.py
@@ -2,7 +2,7 @@ import pytest
from fastapi.testclient import TestClient
from fastapi.websockets import WebSocketDisconnect
-from docs_src.websockets.tutorial001 import app
+from docs_src.websockets.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_tutorial/test_websockets/test_tutorial002.py b/tests/test_tutorial/test_websockets/test_tutorial002.py
index 51aa5752a6..ebf1fc8e81 100644
--- a/tests/test_tutorial/test_websockets/test_tutorial002.py
+++ b/tests/test_tutorial/test_websockets/test_tutorial002.py
@@ -5,16 +5,15 @@ from fastapi import FastAPI
from fastapi.testclient import TestClient
from fastapi.websockets import WebSocketDisconnect
-from ...utils import needs_py39, needs_py310
+from ...utils import needs_py310
@pytest.fixture(
name="app",
params=[
- "tutorial002",
+ pytest.param("tutorial002_py39"),
pytest.param("tutorial002_py310", marks=needs_py310),
- "tutorial002_an",
- pytest.param("tutorial002_an_py39", marks=needs_py39),
+ pytest.param("tutorial002_an_py39"),
pytest.param("tutorial002_an_py310", marks=needs_py310),
],
)
diff --git a/tests/test_tutorial/test_websockets/test_tutorial003.py b/tests/test_tutorial/test_websockets/test_tutorial003.py
index dbcad3b02c..0be1fc81d6 100644
--- a/tests/test_tutorial/test_websockets/test_tutorial003.py
+++ b/tests/test_tutorial/test_websockets/test_tutorial003.py
@@ -1,19 +1,44 @@
+import importlib
+from types import ModuleType
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.websockets.tutorial003 import app, html
-client = TestClient(app)
+@pytest.fixture(
+ name="mod",
+ params=[
+ pytest.param("tutorial003_py39"),
+ ],
+)
+def get_mod(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.websockets.{request.param}")
+
+ return mod
-def test_get():
+@pytest.fixture(name="html")
+def get_html(mod: ModuleType):
+ return mod.html
+
+
+@pytest.fixture(name="client")
+def get_client(mod: ModuleType):
+ client = TestClient(mod.app)
+
+ return client
+
+
+def test_get(client: TestClient, html: str):
response = client.get("/")
assert response.text == html
-def test_websocket_handle_disconnection():
- with client.websocket_connect("/ws/1234") as connection, client.websocket_connect(
- "/ws/5678"
- ) as connection_two:
+def test_websocket_handle_disconnection(client: TestClient):
+ with (
+ client.websocket_connect("/ws/1234") as connection,
+ client.websocket_connect("/ws/5678") as connection_two,
+ ):
connection.send_text("Hello from 1234")
data1 = connection.receive_text()
assert data1 == "You wrote: Hello from 1234"
diff --git a/tests/test_tutorial/test_websockets/test_tutorial003_py39.py b/tests/test_tutorial/test_websockets/test_tutorial003_py39.py
deleted file mode 100644
index 06c4a92796..0000000000
--- a/tests/test_tutorial/test_websockets/test_tutorial003_py39.py
+++ /dev/null
@@ -1,50 +0,0 @@
-import pytest
-from fastapi import FastAPI
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="app")
-def get_app():
- from docs_src.websockets.tutorial003_py39 import app
-
- return app
-
-
-@pytest.fixture(name="html")
-def get_html():
- from docs_src.websockets.tutorial003_py39 import html
-
- return html
-
-
-@pytest.fixture(name="client")
-def get_client(app: FastAPI):
- client = TestClient(app)
-
- return client
-
-
-@needs_py39
-def test_get(client: TestClient, html: str):
- response = client.get("/")
- assert response.text == html
-
-
-@needs_py39
-def test_websocket_handle_disconnection(client: TestClient):
- with client.websocket_connect("/ws/1234") as connection, client.websocket_connect(
- "/ws/5678"
- ) as connection_two:
- connection.send_text("Hello from 1234")
- data1 = connection.receive_text()
- assert data1 == "You wrote: Hello from 1234"
- data2 = connection_two.receive_text()
- client1_says = "Client #1234 says: Hello from 1234"
- assert data2 == client1_says
- data1 = connection.receive_text()
- assert data1 == client1_says
- connection_two.close()
- data1 = connection.receive_text()
- assert data1 == "Client #5678 left the chat"
diff --git a/tests/test_tutorial/test_wsgi/test_tutorial001.py b/tests/test_tutorial/test_wsgi/test_tutorial001.py
index 4f82252739..9fe8c2a4b8 100644
--- a/tests/test_tutorial/test_wsgi/test_tutorial001.py
+++ b/tests/test_tutorial/test_wsgi/test_tutorial001.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from docs_src.wsgi.tutorial001 import app
+from docs_src.wsgi.tutorial001_py39 import app
client = TestClient(app)
diff --git a/tests/test_union_body.py b/tests/test_union_body.py
index c15acacd18..ee7fcc4231 100644
--- a/tests/test_union_body.py
+++ b/tests/test_union_body.py
@@ -1,6 +1,5 @@
from typing import Optional, Union
-from dirty_equals import IsDict
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -91,18 +90,12 @@ def test_openapi_schema():
"Item": {
"title": "Item",
"type": "object",
- "properties": IsDict(
- {
- "name": {
- "title": "Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
+ "properties": {
+ "name": {
+ "title": "Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
}
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"name": {"title": "Name", "type": "string"}}
- ),
+ },
},
"ValidationError": {
"title": "ValidationError",
diff --git a/tests/test_union_body_discriminator.py b/tests/test_union_body_discriminator.py
index 6af9e1d226..6c31649bcc 100644
--- a/tests/test_union_body_discriminator.py
+++ b/tests/test_union_body_discriminator.py
@@ -1,16 +1,12 @@
-from typing import Any, Dict, Union
+from typing import Annotated, Any, Union
-from dirty_equals import IsDict
from fastapi import FastAPI
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from pydantic import BaseModel, Field
-from typing_extensions import Annotated, Literal
-
-from .utils import needs_pydanticv2
+from typing_extensions import Literal
-@needs_pydanticv2
def test_discriminator_pydantic_v2() -> None:
from pydantic import Tag
@@ -32,7 +28,7 @@ def test_discriminator_pydantic_v2() -> None:
@app.post("/items/")
def save_union_body_discriminator(
item: Item, q: Annotated[str, Field(description="Query string")]
- ) -> Dict[str, Any]:
+ ) -> dict[str, Any]:
return {"item": item}
client = TestClient(app)
@@ -93,21 +89,11 @@ def test_discriminator_pydantic_v2() -> None:
"description": "Successful Response",
"content": {
"application/json": {
- "schema": IsDict(
- {
- # Pydantic 2.10, in Python 3.8
- # TODO: remove when dropping support for Python 3.8
- "type": "object",
- "title": "Response Save Union Body Discriminator Items Post",
- }
- )
- | IsDict(
- {
- "type": "object",
- "additionalProperties": True,
- "title": "Response Save Union Body Discriminator Items Post",
- }
- )
+ "schema": {
+ "type": "object",
+ "additionalProperties": True,
+ "title": "Response Save Union Body Discriminator Items Post",
+ }
}
},
},
diff --git a/tests/test_union_body_discriminator_annotated.py b/tests/test_union_body_discriminator_annotated.py
new file mode 100644
index 0000000000..42a6aed24c
--- /dev/null
+++ b/tests/test_union_body_discriminator_annotated.py
@@ -0,0 +1,201 @@
+# Ref: https://github.com/fastapi/fastapi/discussions/14495
+
+from typing import Annotated, Union
+
+import pytest
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+from pydantic import BaseModel
+
+
+@pytest.fixture(name="client")
+def client_fixture() -> TestClient:
+ from fastapi import Body
+ from pydantic import Discriminator, Tag
+
+ class Cat(BaseModel):
+ pet_type: str = "cat"
+ meows: int
+
+ class Dog(BaseModel):
+ pet_type: str = "dog"
+ barks: float
+
+ def get_pet_type(v):
+ assert isinstance(v, dict)
+ return v.get("pet_type", "")
+
+ Pet = Annotated[
+ Union[Annotated[Cat, Tag("cat")], Annotated[Dog, Tag("dog")]],
+ Discriminator(get_pet_type),
+ ]
+
+ app = FastAPI()
+
+ @app.post("/pet/assignment")
+ async def create_pet_assignment(pet: Pet = Body()):
+ return pet
+
+ @app.post("/pet/annotated")
+ async def create_pet_annotated(pet: Annotated[Pet, Body()]):
+ return pet
+
+ client = TestClient(app)
+ return client
+
+
+def test_union_body_discriminator_assignment(client: TestClient) -> None:
+ response = client.post("/pet/assignment", json={"pet_type": "cat", "meows": 5})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"pet_type": "cat", "meows": 5}
+
+
+def test_union_body_discriminator_annotated(client: TestClient) -> None:
+ response = client.post("/pet/annotated", json={"pet_type": "dog", "barks": 3.5})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"pet_type": "dog", "barks": 3.5}
+
+
+def test_openapi_schema(client: TestClient) -> None:
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/pet/assignment": {
+ "post": {
+ "summary": "Create Pet Assignment",
+ "operationId": "create_pet_assignment_pet_assignment_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/Cat"},
+ {"$ref": "#/components/schemas/Dog"},
+ ],
+ "title": "Pet",
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/pet/annotated": {
+ "post": {
+ "summary": "Create Pet Annotated",
+ "operationId": "create_pet_annotated_pet_annotated_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {"$ref": "#/components/schemas/Cat"},
+ {"$ref": "#/components/schemas/Dog"},
+ ],
+ "title": "Pet",
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Cat": {
+ "properties": {
+ "pet_type": {
+ "type": "string",
+ "title": "Pet Type",
+ "default": "cat",
+ },
+ "meows": {"type": "integer", "title": "Meows"},
+ },
+ "type": "object",
+ "required": ["meows"],
+ "title": "Cat",
+ },
+ "Dog": {
+ "properties": {
+ "pet_type": {
+ "type": "string",
+ "title": "Pet Type",
+ "default": "dog",
+ },
+ "barks": {"type": "number", "title": "Barks"},
+ },
+ "type": "object",
+ "required": ["barks"],
+ "title": "Dog",
+ },
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
+ )
diff --git a/tests/test_union_forms.py b/tests/test_union_forms.py
index cbe98ea825..018949f0c7 100644
--- a/tests/test_union_forms.py
+++ b/tests/test_union_forms.py
@@ -1,9 +1,8 @@
-from typing import Union
+from typing import Annotated, Union
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel
-from typing_extensions import Annotated
app = FastAPI()
diff --git a/tests/test_union_inherited_body.py b/tests/test_union_inherited_body.py
index ef75d459ea..3c062e7f5a 100644
--- a/tests/test_union_inherited_body.py
+++ b/tests/test_union_inherited_body.py
@@ -1,6 +1,5 @@
from typing import Optional, Union
-from dirty_equals import IsDict
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -86,16 +85,10 @@ def test_openapi_schema():
"title": "Item",
"type": "object",
"properties": {
- "name": IsDict(
- {
- "title": "Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Name", "type": "string"}
- )
+ "name": {
+ "title": "Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ }
},
},
"ExtendedItem": {
@@ -103,16 +96,10 @@ def test_openapi_schema():
"required": ["age"],
"type": "object",
"properties": {
- "name": IsDict(
- {
- "title": "Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Name", "type": "string"}
- ),
+ "name": {
+ "title": "Name",
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ },
"age": {"title": "Age", "type": "integer"},
},
},
diff --git a/tests/test_validate_response.py b/tests/test_validate_response.py
index cd97007a44..938d419566 100644
--- a/tests/test_validate_response.py
+++ b/tests/test_validate_response.py
@@ -1,4 +1,4 @@
-from typing import List, Optional, Union
+from typing import Optional, Union
import pytest
from fastapi import FastAPI
@@ -12,7 +12,7 @@ app = FastAPI()
class Item(BaseModel):
name: str
price: Optional[float] = None
- owner_ids: Optional[List[int]] = None
+ owner_ids: Optional[list[int]] = None
@app.get("/items/invalid", response_model=Item)
@@ -38,7 +38,7 @@ def get_innerinvalid():
return {"name": "double invalid", "price": "foo", "owner_ids": ["foo", "bar"]}
-@app.get("/items/invalidlist", response_model=List[Item])
+@app.get("/items/invalidlist", response_model=list[Item])
def get_invalidlist():
return [
{"name": "foo"},
diff --git a/tests/test_validate_response_dataclass.py b/tests/test_validate_response_dataclass.py
index 0415988a0b..67282bcde1 100644
--- a/tests/test_validate_response_dataclass.py
+++ b/tests/test_validate_response_dataclass.py
@@ -1,4 +1,4 @@
-from typing import List, Optional
+from typing import Optional
import pytest
from fastapi import FastAPI
@@ -13,7 +13,7 @@ app = FastAPI()
class Item:
name: str
price: Optional[float] = None
- owner_ids: Optional[List[int]] = None
+ owner_ids: Optional[list[int]] = None
@app.get("/items/invalid", response_model=Item)
@@ -26,7 +26,7 @@ def get_innerinvalid():
return {"name": "double invalid", "price": "foo", "owner_ids": ["foo", "bar"]}
-@app.get("/items/invalidlist", response_model=List[Item])
+@app.get("/items/invalidlist", response_model=list[Item])
def get_invalidlist():
return [
{"name": "foo"},
diff --git a/tests/test_validate_response_recursive/app.py b/tests/test_validate_response_recursive/app.py
index d23d279808..d51aa848d0 100644
--- a/tests/test_validate_response_recursive/app.py
+++ b/tests/test_validate_response_recursive/app.py
@@ -1,34 +1,27 @@
-from typing import List
-
from fastapi import FastAPI
-from fastapi._compat import PYDANTIC_V2
from pydantic import BaseModel
app = FastAPI()
class RecursiveItem(BaseModel):
- sub_items: List["RecursiveItem"] = []
+ sub_items: list["RecursiveItem"] = []
name: str
class RecursiveSubitemInSubmodel(BaseModel):
- sub_items2: List["RecursiveItemViaSubmodel"] = []
+ sub_items2: list["RecursiveItemViaSubmodel"] = []
name: str
class RecursiveItemViaSubmodel(BaseModel):
- sub_items1: List[RecursiveSubitemInSubmodel] = []
+ sub_items1: list[RecursiveSubitemInSubmodel] = []
name: str
-if PYDANTIC_V2:
- RecursiveItem.model_rebuild()
- RecursiveSubitemInSubmodel.model_rebuild()
- RecursiveItemViaSubmodel.model_rebuild()
-else:
- RecursiveItem.update_forward_refs()
- RecursiveSubitemInSubmodel.update_forward_refs()
+RecursiveItem.model_rebuild()
+RecursiveSubitemInSubmodel.model_rebuild()
+RecursiveItemViaSubmodel.model_rebuild()
@app.get("/items/recursive", response_model=RecursiveItem)
diff --git a/tests/test_validation_error_context.py b/tests/test_validation_error_context.py
new file mode 100644
index 0000000000..844b8a64f9
--- /dev/null
+++ b/tests/test_validation_error_context.py
@@ -0,0 +1,168 @@
+from fastapi import FastAPI, Request, WebSocket
+from fastapi.exceptions import (
+ RequestValidationError,
+ ResponseValidationError,
+ WebSocketRequestValidationError,
+)
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ id: int
+ name: str
+
+
+class ExceptionCapture:
+ def __init__(self):
+ self.exception = None
+
+ def capture(self, exc):
+ self.exception = exc
+ return exc
+
+
+app = FastAPI()
+sub_app = FastAPI()
+captured_exception = ExceptionCapture()
+
+app.mount(path="/sub", app=sub_app)
+
+
+@app.exception_handler(RequestValidationError)
+@sub_app.exception_handler(RequestValidationError)
+async def request_validation_handler(request: Request, exc: RequestValidationError):
+ captured_exception.capture(exc)
+ raise exc
+
+
+@app.exception_handler(ResponseValidationError)
+@sub_app.exception_handler(ResponseValidationError)
+async def response_validation_handler(_: Request, exc: ResponseValidationError):
+ captured_exception.capture(exc)
+ raise exc
+
+
+@app.exception_handler(WebSocketRequestValidationError)
+@sub_app.exception_handler(WebSocketRequestValidationError)
+async def websocket_validation_handler(
+ websocket: WebSocket, exc: WebSocketRequestValidationError
+):
+ captured_exception.capture(exc)
+ raise exc
+
+
+@app.get("/users/{user_id}")
+def get_user(user_id: int):
+ return {"user_id": user_id} # pragma: no cover
+
+
+@app.get("/items/", response_model=Item)
+def get_item():
+ return {"name": "Widget"}
+
+
+@sub_app.get("/items/", response_model=Item)
+def get_sub_item():
+ return {"name": "Widget"} # pragma: no cover
+
+
+@app.websocket("/ws/{item_id}")
+async def websocket_endpoint(websocket: WebSocket, item_id: int):
+ await websocket.accept() # pragma: no cover
+ await websocket.send_text(f"Item: {item_id}") # pragma: no cover
+ await websocket.close() # pragma: no cover
+
+
+@sub_app.websocket("/ws/{item_id}")
+async def subapp_websocket_endpoint(websocket: WebSocket, item_id: int):
+ await websocket.accept() # pragma: no cover
+ await websocket.send_text(f"Item: {item_id}") # pragma: no cover
+ await websocket.close() # pragma: no cover
+
+
+client = TestClient(app)
+
+
+def test_request_validation_error_includes_endpoint_context():
+ captured_exception.exception = None
+ try:
+ client.get("/users/invalid")
+ except Exception:
+ pass
+
+ assert captured_exception.exception is not None
+ error_str = str(captured_exception.exception)
+ assert "get_user" in error_str
+ assert "/users/" in error_str
+
+
+def test_response_validation_error_includes_endpoint_context():
+ captured_exception.exception = None
+ try:
+ client.get("/items/")
+ except Exception:
+ pass
+
+ assert captured_exception.exception is not None
+ error_str = str(captured_exception.exception)
+ assert "get_item" in error_str
+ assert "/items/" in error_str
+
+
+def test_websocket_validation_error_includes_endpoint_context():
+ captured_exception.exception = None
+ try:
+ with client.websocket_connect("/ws/invalid"):
+ pass # pragma: no cover
+ except Exception:
+ pass
+
+ assert captured_exception.exception is not None
+ error_str = str(captured_exception.exception)
+ assert "websocket_endpoint" in error_str
+ assert "/ws/" in error_str
+
+
+def test_subapp_request_validation_error_includes_endpoint_context():
+ captured_exception.exception = None
+ try:
+ client.get("/sub/items/")
+ except Exception:
+ pass
+
+ assert captured_exception.exception is not None
+ error_str = str(captured_exception.exception)
+ assert "get_sub_item" in error_str
+ assert "/sub/items/" in error_str
+
+
+def test_subapp_websocket_validation_error_includes_endpoint_context():
+ captured_exception.exception = None
+ try:
+ with client.websocket_connect("/sub/ws/invalid"):
+ pass # pragma: no cover
+ except Exception:
+ pass
+
+ assert captured_exception.exception is not None
+ error_str = str(captured_exception.exception)
+ assert "subapp_websocket_endpoint" in error_str
+ assert "/sub/ws/" in error_str
+
+
+def test_validation_error_with_only_path():
+ errors = [{"type": "missing", "loc": ("body", "name"), "msg": "Field required"}]
+ exc = RequestValidationError(errors, endpoint_ctx={"path": "GET /api/test"})
+ error_str = str(exc)
+ assert "Endpoint: GET /api/test" in error_str
+ assert 'File "' not in error_str
+
+
+def test_validation_error_with_no_context():
+ errors = [{"type": "missing", "loc": ("body", "name"), "msg": "Field required"}]
+ exc = RequestValidationError(errors, endpoint_ctx={})
+ error_str = str(exc)
+ assert "1 validation error:" in error_str
+ assert "Endpoint" not in error_str
+ assert 'File "' not in error_str
diff --git a/tests/test_webhooks_security.py b/tests/test_webhooks_security.py
index 21a694cb54..982ae1e21d 100644
--- a/tests/test_webhooks_security.py
+++ b/tests/test_webhooks_security.py
@@ -1,10 +1,10 @@
from datetime import datetime
+from typing import Annotated
from fastapi import FastAPI, Security
from fastapi.security import HTTPBearer
from fastapi.testclient import TestClient
from pydantic import BaseModel
-from typing_extensions import Annotated
app = FastAPI()
diff --git a/tests/test_wrapped_method_forward_reference.py b/tests/test_wrapped_method_forward_reference.py
new file mode 100644
index 0000000000..7403f6002c
--- /dev/null
+++ b/tests/test_wrapped_method_forward_reference.py
@@ -0,0 +1,31 @@
+import functools
+
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+
+from .forward_reference_type import forwardref_method
+
+
+def passthrough(f):
+ @functools.wraps(f)
+ def method(*args, **kwargs):
+ return f(*args, **kwargs)
+
+ return method
+
+
+def test_wrapped_method_type_inference():
+ """
+ Regression test ensuring that when a method imported from another module
+ is decorated with something that sets the __wrapped__ attribute (functools.wraps),
+ then the types are still processed correctly, including dereferencing of forward
+ references.
+ """
+ app = FastAPI()
+ client = TestClient(app)
+ app.post("/endpoint")(passthrough(forwardref_method))
+ app.post("/endpoint2")(passthrough(passthrough(forwardref_method)))
+ with client:
+ response = client.post("/endpoint", json={"input": {"x": 0}})
+ response2 = client.post("/endpoint2", json={"input": {"x": 0}})
+ assert response.json() == response2.json() == {"x": 1}
diff --git a/tests/test_ws_dependencies.py b/tests/test_ws_dependencies.py
index ccb1c4b7da..51b982c00e 100644
--- a/tests/test_ws_dependencies.py
+++ b/tests/test_ws_dependencies.py
@@ -1,16 +1,15 @@
import json
-from typing import List
+from typing import Annotated
from fastapi import APIRouter, Depends, FastAPI, WebSocket
from fastapi.testclient import TestClient
-from typing_extensions import Annotated
-def dependency_list() -> List[str]:
+def dependency_list() -> list[str]:
return []
-DepList = Annotated[List[str], Depends(dependency_list)]
+DepList = Annotated[list[str], Depends(dependency_list)]
def create_dependency(name: str):
diff --git a/tests/utils.py b/tests/utils.py
index 691e92bbfb..efa0bfd52b 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,43 +1,17 @@
import sys
import pytest
-from fastapi._compat import PYDANTIC_V2
-from inline_snapshot import Snapshot
needs_py39 = pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9+")
needs_py310 = pytest.mark.skipif(
sys.version_info < (3, 10), reason="requires python3.10+"
)
needs_py_lt_314 = pytest.mark.skipif(
- sys.version_info > (3, 13), reason="requires python3.13-"
+ sys.version_info >= (3, 14), reason="requires python3.13-"
)
-needs_pydanticv2 = pytest.mark.skipif(not PYDANTIC_V2, reason="requires Pydantic v2")
-needs_pydanticv1 = pytest.mark.skipif(PYDANTIC_V2, reason="requires Pydantic v1")
def skip_module_if_py_gte_314():
"""Skip entire module on Python 3.14+ at import time."""
if sys.version_info >= (3, 14):
pytest.skip("requires python3.13-", allow_module_level=True)
-
-
-def pydantic_snapshot(
- *,
- v2: Snapshot,
- v1: Snapshot, # TODO: remove v1 argument when deprecating Pydantic v1
-):
- """
- This function should be used like this:
-
- >>> assert value == pydantic_snapshot(v2=snapshot(),v1=snapshot())
-
- inline-snapshot will create the snapshots when pytest is executed for each versions of pydantic.
-
- It is also possible to use the function inside snapshots for version-specific values.
-
- >>> assert value == snapshot({
- "data": "some data",
- "version_specific": pydantic_snapshot(v2=snapshot(),v1=snapshot()),
- })
- """
- return v2 if PYDANTIC_V2 else v1
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000000..9ae2220e0c
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,5339 @@
+version = 1
+revision = 3
+requires-python = ">=3.9"
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+ "python_full_version < '3.10'",
+]
+
+[[package]]
+name = "ag-ui-protocol"
+version = "0.1.10"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pydantic" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/67/bb/5a5ec893eea5805fb9a3db76a9888c3429710dfb6f24bbb37568f2cf7320/ag_ui_protocol-0.1.10.tar.gz", hash = "sha256:3213991c6b2eb24bb1a8c362ee270c16705a07a4c5962267a083d0959ed894f4", size = 6945, upload-time = "2025-11-06T15:17:17.068Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8f/78/eb55fabaab41abc53f52c0918a9a8c0f747807e5306273f51120fd695957/ag_ui_protocol-0.1.10-py3-none-any.whl", hash = "sha256:c81e6981f30aabdf97a7ee312bfd4df0cd38e718d9fc10019c7d438128b93ab5", size = 7889, upload-time = "2025-11-06T15:17:15.325Z" },
+]
+
+[[package]]
+name = "annotated-doc"
+version = "0.0.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
+]
+
+[[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 = "anthropic"
+version = "0.75.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "distro" },
+ { name = "docstring-parser" },
+ { name = "httpx" },
+ { name = "jiter" },
+ { name = "pydantic" },
+ { name = "sniffio" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/04/1f/08e95f4b7e2d35205ae5dcbb4ae97e7d477fc521c275c02609e2931ece2d/anthropic-0.75.0.tar.gz", hash = "sha256:e8607422f4ab616db2ea5baacc215dd5f028da99ce2f022e33c7c535b29f3dfb", size = 439565, upload-time = "2025-11-24T20:41:45.28Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/60/1c/1cd02b7ae64302a6e06724bf80a96401d5313708651d277b1458504a1730/anthropic-0.75.0-py3-none-any.whl", hash = "sha256:ea8317271b6c15d80225a9f3c670152746e88805a7a61e14d4a374577164965b", size = 388164, upload-time = "2025-11-24T20:41:43.587Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.12.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "idna" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
+]
+
+[package.optional-dependencies]
+trio = [
+ { name = "trio", version = "0.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "trio", version = "0.32.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+
+[[package]]
+name = "argcomplete"
+version = "3.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" },
+]
+
+[[package]]
+name = "argon2-cffi"
+version = "23.1.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "argon2-cffi-bindings", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798, upload-time = "2023-08-15T14:13:12.711Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124, upload-time = "2023-08-15T14:13:10.752Z" },
+]
+
+[[package]]
+name = "argon2-cffi"
+version = "25.1.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "argon2-cffi-bindings", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" },
+]
+
+[[package]]
+name = "argon2-cffi-bindings"
+version = "25.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" },
+ { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" },
+ { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" },
+ { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" },
+ { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" },
+ { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" },
+ { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" },
+ { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" },
+ { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" },
+ { url = "https://files.pythonhosted.org/packages/11/2d/ba4e4ca8d149f8dcc0d952ac0967089e1d759c7e5fcf0865a317eb680fbb/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6dca33a9859abf613e22733131fc9194091c1fa7cb3e131c143056b4856aa47e", size = 24549, upload-time = "2025-07-30T10:02:00.101Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/82/9b2386cc75ac0bd3210e12a44bfc7fd1632065ed8b80d573036eecb10442/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21378b40e1b8d1655dd5310c84a40fc19a9aa5e6366e835ceb8576bf0fea716d", size = 25539, upload-time = "2025-07-30T10:02:00.929Z" },
+ { url = "https://files.pythonhosted.org/packages/31/db/740de99a37aa727623730c90d92c22c9e12585b3c98c54b7960f7810289f/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d588dec224e2a83edbdc785a5e6f3c6cd736f46bfd4b441bbb5aa1f5085e584", size = 28467, upload-time = "2025-07-30T10:02:02.08Z" },
+ { url = "https://files.pythonhosted.org/packages/71/7a/47c4509ea18d755f44e2b92b7178914f0c113946d11e16e626df8eaa2b0b/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5acb4e41090d53f17ca1110c3427f0a130f944b896fc8c83973219c97f57b690", size = 27355, upload-time = "2025-07-30T10:02:02.867Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/82/82745642d3c46e7cea25e1885b014b033f4693346ce46b7f47483cf5d448/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:da0c79c23a63723aa5d782250fbf51b768abca630285262fb5144ba5ae01e520", size = 29187, upload-time = "2025-07-30T10:02:03.674Z" },
+]
+
+[[package]]
+name = "asttokens"
+version = "3.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" },
+]
+
+[[package]]
+name = "attrs"
+version = "25.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
+]
+
+[[package]]
+name = "babel"
+version = "2.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
+]
+
+[[package]]
+name = "backrefs"
+version = "6.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" },
+ { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726, upload-time = "2025-11-15T14:52:04.093Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584, upload-time = "2025-11-15T14:52:05.233Z" },
+ { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" },
+]
+
+[[package]]
+name = "black"
+version = "25.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "mypy-extensions" },
+ { name = "packaging" },
+ { name = "pathspec" },
+ { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419, upload-time = "2025-01-29T05:37:06.642Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080, upload-time = "2025-01-29T05:37:09.321Z" },
+ { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886, upload-time = "2025-01-29T04:18:24.432Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404, upload-time = "2025-01-29T04:19:04.296Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" },
+ { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" },
+ { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" },
+ { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" },
+ { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" },
+ { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" },
+ { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" },
+ { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/b6/ae7507470a4830dbbfe875c701e84a4a5fb9183d1497834871a715716a92/black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", size = 1628593, upload-time = "2025-01-29T05:37:23.672Z" },
+ { url = "https://files.pythonhosted.org/packages/24/c1/ae36fa59a59f9363017ed397750a0cd79a470490860bc7713967d89cdd31/black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f", size = 1460000, upload-time = "2025-01-29T05:37:25.829Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/b6/98f832e7a6c49aa3a464760c67c7856363aa644f2f3c74cf7d624168607e/black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", size = 1765963, upload-time = "2025-01-29T04:18:38.116Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/e9/2cb0a017eb7024f70e0d2e9bdb8c5a5b078c5740c7f8816065d06f04c557/black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", size = 1419419, upload-time = "2025-01-29T04:18:30.191Z" },
+ { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" },
+]
+
+[[package]]
+name = "blinker"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
+]
+
+[[package]]
+name = "boto3"
+version = "1.42.24"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "botocore" },
+ { name = "jmespath" },
+ { name = "s3transfer" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ee/21/8be0e3685c3a4868be48d8d2f6e5b4641727e1d8a5d396b8b401d2b5f06e/boto3-1.42.24.tar.gz", hash = "sha256:c47a2f40df933e3861fc66fd8d6b87ee36d4361663a7e7ba39a87f5a78b2eae1", size = 112788, upload-time = "2026-01-07T20:30:51.019Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a7/75/bbfccb268f9faa4f59030888e859dca9797a980b77d6a074113af73bd4bf/boto3-1.42.24-py3-none-any.whl", hash = "sha256:8ed6ad670a5a2d7f66c1b0d3362791b48392c7a08f78479f5d8ab319a4d9118f", size = 140572, upload-time = "2026-01-07T20:30:49.431Z" },
+]
+
+[[package]]
+name = "botocore"
+version = "1.42.24"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jmespath" },
+ { name = "python-dateutil" },
+ { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/12/d7/bb4a4e839b238ffb67b002d7326b328ebe5eb23ed5180f2ca10399a802de/botocore-1.42.24.tar.gz", hash = "sha256:be8d1bea64fb91eea08254a1e5fea057e4428d08e61f4e11083a02cafc1f8cc6", size = 14878455, upload-time = "2026-01-07T20:30:40.379Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ff/d4/f2655d777eed8b069ecab3761454cb83f830f8be8b5b0d292e4b3a980d00/botocore-1.42.24-py3-none-any.whl", hash = "sha256:8fca9781d7c84f7ad070fceffaff7179c4aa7a5ffb27b43df9d1d957801e0a8d", size = 14551806, upload-time = "2026-01-07T20:30:38.103Z" },
+]
+
+[[package]]
+name = "cairocffi"
+version = "1.7.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/70/c5/1a4dc131459e68a173cbdab5fad6b524f53f9c1ef7861b7698e998b837cc/cairocffi-1.7.1.tar.gz", hash = "sha256:2e48ee864884ec4a3a34bfa8c9ab9999f688286eb714a15a43ec9d068c36557b", size = 88096, upload-time = "2024-06-18T10:56:06.741Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/93/d8/ba13451aa6b745c49536e87b6bf8f629b950e84bd0e8308f7dc6883b67e2/cairocffi-1.7.1-py3-none-any.whl", hash = "sha256:9803a0e11f6c962f3b0ae2ec8ba6ae45e957a146a004697a1ac1bbf16b073b3f", size = 75611, upload-time = "2024-06-18T10:55:59.489Z" },
+]
+
+[[package]]
+name = "cairosvg"
+version = "2.8.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cairocffi" },
+ { name = "cssselect2" },
+ { name = "defusedxml" },
+ { name = "pillow" },
+ { name = "tinycss2", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "tinycss2", version = "1.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ab/b9/5106168bd43d7cd8b7cc2a2ee465b385f14b63f4c092bb89eee2d48c8e67/cairosvg-2.8.2.tar.gz", hash = "sha256:07cbf4e86317b27a92318a4cac2a4bb37a5e9c1b8a27355d06874b22f85bef9f", size = 8398590, upload-time = "2025-05-15T06:56:32.653Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/67/48/816bd4aaae93dbf9e408c58598bc32f4a8c65f4b86ab560864cb3ee60adb/cairosvg-2.8.2-py3-none-any.whl", hash = "sha256:eab46dad4674f33267a671dce39b64be245911c901c70d65d2b7b0821e852bf5", size = 45773, upload-time = "2025-05-15T06:56:28.552Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2026.1.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
+]
+
+[[package]]
+name = "cffi"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycparser", marker = "implementation_name != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" },
+ { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" },
+ { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" },
+ { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" },
+ { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" },
+ { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" },
+ { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" },
+ { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" },
+ { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" },
+ { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" },
+ { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" },
+ { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
+ { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" },
+ { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
+ { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
+ { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
+ { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
+ { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
+ { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
+ { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
+ { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
+ { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
+ { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
+ { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
+ { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288, upload-time = "2025-09-08T23:23:48.404Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509, upload-time = "2025-09-08T23:23:49.73Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" },
+ { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498, upload-time = "2025-09-08T23:23:52.494Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", size = 203243, upload-time = "2025-09-08T23:23:53.836Z" },
+ { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", size = 203158, upload-time = "2025-09-08T23:23:55.169Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548, upload-time = "2025-09-08T23:23:56.506Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897, upload-time = "2025-09-08T23:23:57.825Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249, upload-time = "2025-09-08T23:23:59.139Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041, upload-time = "2025-09-08T23:24:00.496Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138, upload-time = "2025-09-08T23:24:01.7Z" },
+ { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" },
+ { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" },
+ { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" },
+ { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" },
+ { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" },
+ { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" },
+ { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" },
+ { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" },
+ { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" },
+ { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" },
+ { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" },
+ { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" },
+ { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" },
+ { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" },
+ { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" },
+ { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" },
+ { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" },
+ { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" },
+ { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
+ { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
+ { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
+ { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
+ { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
+ { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
+ { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
+ { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
+ { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
+ { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
+ { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
+ { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
+ { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
+ { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
+ { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" },
+ { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" },
+ { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" },
+ { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" },
+ { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" },
+ { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" },
+ { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.1.8"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.3.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
+]
+
+[[package]]
+name = "cohere"
+version = "5.20.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "fastavro" },
+ { name = "httpx" },
+ { name = "pydantic" },
+ { name = "pydantic-core" },
+ { name = "requests" },
+ { name = "tokenizers" },
+ { name = "types-requests", version = "2.31.0.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "types-requests", version = "2.32.4.20260107", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4b/ed/bb02083654bdc089ae4ef1cd7691fd2233f1fd9f32bcbfacc80ff57d9775/cohere-5.20.1.tar.gz", hash = "sha256:50973f63d2c6138ff52ce37d8d6f78ccc539af4e8c43865e960d68e0bf835b6f", size = 180820, upload-time = "2025-12-18T16:39:50.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7a/e3/94eb11ac3ebaaa3a6afb5d2ff23db95d58bc468ae538c388edf49f2f20b5/cohere-5.20.1-py3-none-any.whl", hash = "sha256:d230fd13d95ba92ae927fce3dd497599b169883afc7954fe29b39fb8d5df5fc7", size = 318973, upload-time = "2025-12-18T16:39:49.504Z" },
+]
+
+[[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 = "coverage"
+version = "7.10.7"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" },
+ { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" },
+ { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" },
+ { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" },
+ { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" },
+ { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" },
+ { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" },
+ { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" },
+ { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" },
+ { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" },
+ { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" },
+ { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" },
+ { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" },
+ { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" },
+ { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" },
+ { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" },
+ { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" },
+ { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" },
+ { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" },
+ { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" },
+ { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" },
+ { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" },
+ { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" },
+ { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" },
+ { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" },
+ { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" },
+ { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" },
+ { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" },
+ { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" },
+ { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" },
+ { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" },
+ { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" },
+ { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" },
+ { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" },
+ { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" },
+ { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" },
+ { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" },
+ { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" },
+ { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" },
+ { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" },
+ { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" },
+ { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" },
+ { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" },
+]
+
+[package.optional-dependencies]
+toml = [
+ { name = "tomli", marker = "python_full_version < '3.10'" },
+]
+
+[[package]]
+name = "coverage"
+version = "7.13.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2d/9a/3742e58fd04b233df95c012ee9f3dfe04708a5e1d32613bd2d47d4e1be0d/coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147", size = 218633, upload-time = "2025-12-28T15:40:10.165Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/45/7e6bdc94d89cd7c8017ce735cf50478ddfe765d4fbf0c24d71d30ea33d7a/coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d", size = 219147, upload-time = "2025-12-28T15:40:12.069Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/38/0d6a258625fd7f10773fe94097dc16937a5f0e3e0cdf3adef67d3ac6baef/coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0", size = 245894, upload-time = "2025-12-28T15:40:13.556Z" },
+ { url = "https://files.pythonhosted.org/packages/27/58/409d15ea487986994cbd4d06376e9860e9b157cfbfd402b1236770ab8dd2/coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90", size = 247721, upload-time = "2025-12-28T15:40:15.37Z" },
+ { url = "https://files.pythonhosted.org/packages/da/bf/6e8056a83fd7a96c93341f1ffe10df636dd89f26d5e7b9ca511ce3bcf0df/coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d", size = 249585, upload-time = "2025-12-28T15:40:17.226Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/15/e1daff723f9f5959acb63cbe35b11203a9df77ee4b95b45fffd38b318390/coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b", size = 246597, upload-time = "2025-12-28T15:40:19.028Z" },
+ { url = "https://files.pythonhosted.org/packages/74/a6/1efd31c5433743a6ddbc9d37ac30c196bb07c7eab3d74fbb99b924c93174/coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6", size = 247626, upload-time = "2025-12-28T15:40:20.846Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/9f/1609267dd3e749f57fdd66ca6752567d1c13b58a20a809dc409b263d0b5f/coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e", size = 245629, upload-time = "2025-12-28T15:40:22.397Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/f6/6815a220d5ec2466383d7cc36131b9fa6ecbe95c50ec52a631ba733f306a/coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae", size = 245901, upload-time = "2025-12-28T15:40:23.836Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/58/40576554cd12e0872faf6d2c0eb3bc85f71d78427946ddd19ad65201e2c0/coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29", size = 246505, upload-time = "2025-12-28T15:40:25.421Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/77/9233a90253fba576b0eee81707b5781d0e21d97478e5377b226c5b096c0f/coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f", size = 221257, upload-time = "2025-12-28T15:40:27.217Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/43/e842ff30c1a0a623ec80db89befb84a3a7aad7bfe44a6ea77d5a3e61fedd/coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1", size = 222191, upload-time = "2025-12-28T15:40:28.916Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" },
+ { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" },
+ { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" },
+ { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" },
+ { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" },
+ { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" },
+ { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" },
+ { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" },
+ { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" },
+ { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" },
+ { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" },
+ { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" },
+ { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" },
+ { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" },
+ { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" },
+ { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" },
+ { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" },
+ { url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" },
+ { url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" },
+ { url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" },
+ { url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" },
+ { url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" },
+ { url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" },
+ { url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" },
+ { url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" },
+ { url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" },
+ { url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" },
+ { url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" },
+ { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" },
+ { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" },
+ { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" },
+ { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" },
+ { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" },
+ { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" },
+ { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" },
+]
+
+[package.optional-dependencies]
+toml = [
+ { name = "tomli", marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" },
+]
+
+[[package]]
+name = "cross-web"
+version = "0.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/93/4f/bdb62e969649ee76d4741ef8eee34384ec2bc21cc66eb7fd244e6ad62be8/cross_web-0.4.0.tar.gz", hash = "sha256:4ae65619ddfcd06d6803432c0366342d7e8aeba10194b4e144d73a662e75370c", size = 157111, upload-time = "2025-12-25T20:45:21.989Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6a/d6/6c6a036655e5091b26b9f350dcf43821895325aa4727396b14c67679a957/cross_web-0.4.0-py3-none-any.whl", hash = "sha256:0c675bd26e91428cab31e3e927929b42da94aa96da92974e57c78f9a732d0e9b", size = 14200, upload-time = "2025-12-25T20:45:23.075Z" },
+]
+
+[[package]]
+name = "cryptography"
+version = "46.0.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" },
+ { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" },
+ { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" },
+ { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" },
+ { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" },
+ { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" },
+ { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" },
+ { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" },
+ { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" },
+ { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" },
+ { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" },
+ { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" },
+ { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" },
+ { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" },
+ { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" },
+ { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" },
+ { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" },
+ { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", size = 3689163, upload-time = "2025-10-15T23:18:13.821Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", size = 3429474, upload-time = "2025-10-15T23:18:15.477Z" },
+ { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" },
+ { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" },
+ { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" },
+]
+
+[[package]]
+name = "cssselect2"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "tinycss2", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "tinycss2", version = "1.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "webencodings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9f/86/fd7f58fc498b3166f3a7e8e0cddb6e620fe1da35b02248b1bd59e95dbaaa/cssselect2-0.8.0.tar.gz", hash = "sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a", size = 35716, upload-time = "2025-03-05T14:46:07.988Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0f/e7/aa315e6a749d9b96c2504a1ba0ba031ba2d0517e972ce22682e3fccecb09/cssselect2-0.8.0-py3-none-any.whl", hash = "sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e", size = 15454, upload-time = "2025-03-05T14:46:06.463Z" },
+]
+
+[[package]]
+name = "cyclic"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bf/9f/becc4fea44301f232e4eba17752001bd708e3c042fef37a72b9af7ddf4b5/cyclic-1.0.0.tar.gz", hash = "sha256:ecddd56cb831ee3e6b79f61ecb0ad71caee606c507136867782911aa01c3e5eb", size = 2167, upload-time = "2018-09-26T16:47:07.285Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c0/c0/9f59d2ebd9d585e1681c51767eb138bcd9d0ea770f6fc003cd875c7f5e62/cyclic-1.0.0-py3-none-any.whl", hash = "sha256:32d8181d7698f426bce6f14f4c3921ef95b6a84af9f96192b59beb05bc00c3ed", size = 2547, upload-time = "2018-09-26T16:47:05.609Z" },
+]
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
+]
+
+[[package]]
+name = "dirty-equals"
+version = "0.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/99/133892f401ced5a27e641a473c547d5fbdb39af8f85dac8a9d633ea3e7a7/dirty_equals-0.9.0.tar.gz", hash = "sha256:17f515970b04ed7900b733c95fd8091f4f85e52f1fb5f268757f25c858eb1f7b", size = 50412, upload-time = "2025-01-11T23:23:40.491Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/77/0c/03cc99bf3b6328604b10829de3460f2b2ad3373200c45665c38508e550c6/dirty_equals-0.9.0-py3-none-any.whl", hash = "sha256:ff4d027f5cfa1b69573af00f7ba9043ea652dbdce3fe5cbe828e478c7346db9c", size = 28226, upload-time = "2025-01-11T23:23:37.489Z" },
+]
+
+[[package]]
+name = "distro"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
+]
+
+[[package]]
+name = "dnspython"
+version = "2.7.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" },
+]
+
+[[package]]
+name = "dnspython"
+version = "2.8.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
+]
+
+[[package]]
+name = "docstring-parser"
+version = "0.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" },
+]
+
+[[package]]
+name = "email-validator"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "dnspython", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "dnspython", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" },
+]
+
+[[package]]
+name = "eval-type-backport"
+version = "0.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fb/a3/cafafb4558fd638aadfe4121dc6cefb8d743368c085acb2f521df0f3d9d7/eval_type_backport-0.3.1.tar.gz", hash = "sha256:57e993f7b5b69d271e37482e62f74e76a0276c82490cf8e4f0dffeb6b332d5ed", size = 9445, upload-time = "2025-12-02T11:51:42.987Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cf/22/fdc2e30d43ff853720042fa15baa3e6122722be1a7950a98233ebb55cd71/eval_type_backport-0.3.1-py3-none-any.whl", hash = "sha256:279ab641905e9f11129f56a8a78f493518515b83402b860f6f06dd7c011fdfa8", size = 6063, upload-time = "2025-12-02T11:51:41.665Z" },
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
+]
+
+[[package]]
+name = "executing"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" },
+]
+
+[[package]]
+name = "fastapi"
+source = { editable = "." }
+dependencies = [
+ { name = "annotated-doc" },
+ { name = "pydantic" },
+ { name = "starlette", version = "0.49.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions" },
+]
+
+[package.optional-dependencies]
+all = [
+ { name = "email-validator" },
+ { name = "fastapi-cli", extra = ["standard"] },
+ { name = "httpx" },
+ { name = "itsdangerous" },
+ { name = "jinja2" },
+ { name = "orjson" },
+ { name = "pydantic-extra-types" },
+ { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "python-multipart", version = "0.0.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "python-multipart", version = "0.0.21", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pyyaml" },
+ { name = "ujson" },
+ { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" },
+ { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" },
+]
+standard = [
+ { name = "email-validator" },
+ { name = "fastapi-cli", extra = ["standard"] },
+ { name = "httpx" },
+ { name = "jinja2" },
+ { name = "pydantic-extra-types" },
+ { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "python-multipart", version = "0.0.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "python-multipart", version = "0.0.21", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" },
+ { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" },
+]
+standard-no-fastapi-cloud-cli = [
+ { name = "email-validator" },
+ { name = "fastapi-cli", extra = ["standard-no-fastapi-cloud-cli"] },
+ { name = "httpx" },
+ { name = "jinja2" },
+ { name = "pydantic-extra-types" },
+ { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "python-multipart", version = "0.0.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "python-multipart", version = "0.0.21", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" },
+ { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "anyio", extra = ["trio"] },
+ { name = "black" },
+ { name = "cairosvg" },
+ { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" },
+ { name = "coverage", version = "7.13.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" },
+ { name = "dirty-equals" },
+ { name = "flask" },
+ { name = "gitpython" },
+ { name = "griffe-typingdoc" },
+ { name = "griffe-warnings-deprecated" },
+ { name = "httpx" },
+ { name = "inline-snapshot" },
+ { name = "jieba" },
+ { name = "markdown-include-variants" },
+ { name = "mdx-include" },
+ { name = "mkdocs-macros-plugin" },
+ { name = "mkdocs-material" },
+ { name = "mkdocs-redirects" },
+ { name = "mkdocstrings", extra = ["python"] },
+ { name = "mypy" },
+ { name = "pillow" },
+ { name = "playwright" },
+ { name = "prek" },
+ { name = "pwdlib", version = "0.2.1", source = { registry = "https://pypi.org/simple" }, extra = ["argon2"], marker = "python_full_version < '3.10'" },
+ { name = "pwdlib", version = "0.3.0", source = { registry = "https://pypi.org/simple" }, extra = ["argon2"], marker = "python_full_version >= '3.10'" },
+ { name = "pydantic-ai" },
+ { name = "pygithub" },
+ { name = "pyjwt" },
+ { name = "pytest" },
+ { name = "pytest-codspeed" },
+ { name = "python-slugify" },
+ { name = "pyyaml" },
+ { name = "ruff" },
+ { name = "sqlmodel" },
+ { name = "strawberry-graphql", version = "0.283.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "strawberry-graphql", version = "0.288.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "typer" },
+ { name = "types-orjson" },
+ { name = "types-ujson" },
+]
+docs = [
+ { name = "black" },
+ { name = "cairosvg" },
+ { name = "griffe-typingdoc" },
+ { name = "griffe-warnings-deprecated" },
+ { name = "httpx" },
+ { name = "jieba" },
+ { name = "markdown-include-variants" },
+ { name = "mdx-include" },
+ { name = "mkdocs-macros-plugin" },
+ { name = "mkdocs-material" },
+ { name = "mkdocs-redirects" },
+ { name = "mkdocstrings", extra = ["python"] },
+ { name = "pillow" },
+ { name = "python-slugify" },
+ { name = "pyyaml" },
+ { name = "ruff" },
+ { name = "typer" },
+]
+docs-tests = [
+ { name = "httpx" },
+ { name = "ruff" },
+]
+github-actions = [
+ { name = "httpx" },
+ { name = "pydantic" },
+ { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pygithub" },
+ { name = "pyyaml" },
+ { name = "smokeshow" },
+]
+tests = [
+ { name = "anyio", extra = ["trio"] },
+ { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" },
+ { name = "coverage", version = "7.13.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" },
+ { name = "dirty-equals" },
+ { name = "flask" },
+ { name = "httpx" },
+ { name = "inline-snapshot" },
+ { name = "mypy" },
+ { name = "pwdlib", version = "0.2.1", source = { registry = "https://pypi.org/simple" }, extra = ["argon2"], marker = "python_full_version < '3.10'" },
+ { name = "pwdlib", version = "0.3.0", source = { registry = "https://pypi.org/simple" }, extra = ["argon2"], marker = "python_full_version >= '3.10'" },
+ { name = "pyjwt" },
+ { name = "pytest" },
+ { name = "pytest-codspeed" },
+ { name = "pyyaml" },
+ { name = "ruff" },
+ { name = "sqlmodel" },
+ { name = "strawberry-graphql", version = "0.283.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "strawberry-graphql", version = "0.288.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "types-orjson" },
+ { name = "types-ujson" },
+]
+translations = [
+ { name = "gitpython" },
+ { name = "pydantic-ai" },
+ { name = "pygithub" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "annotated-doc", specifier = ">=0.0.2" },
+ { name = "email-validator", marker = "extra == 'all'", specifier = ">=2.0.0" },
+ { name = "email-validator", marker = "extra == 'standard'", specifier = ">=2.0.0" },
+ { name = "email-validator", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=2.0.0" },
+ { name = "fastapi-cli", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.0.8" },
+ { name = "fastapi-cli", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.0.8" },
+ { name = "fastapi-cli", extras = ["standard-no-fastapi-cloud-cli"], marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.0.8" },
+ { name = "httpx", marker = "extra == 'all'", specifier = ">=0.23.0,<1.0.0" },
+ { name = "httpx", marker = "extra == 'standard'", specifier = ">=0.23.0,<1.0.0" },
+ { name = "httpx", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.23.0,<1.0.0" },
+ { name = "itsdangerous", marker = "extra == 'all'", specifier = ">=1.1.0" },
+ { name = "jinja2", marker = "extra == 'all'", specifier = ">=3.1.5" },
+ { name = "jinja2", marker = "extra == 'standard'", specifier = ">=3.1.5" },
+ { name = "jinja2", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=3.1.5" },
+ { name = "orjson", marker = "extra == 'all'", specifier = ">=3.2.1" },
+ { name = "pydantic", specifier = ">=2.7.0" },
+ { name = "pydantic-extra-types", marker = "extra == 'all'", specifier = ">=2.0.0" },
+ { name = "pydantic-extra-types", marker = "extra == 'standard'", specifier = ">=2.0.0" },
+ { name = "pydantic-extra-types", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=2.0.0" },
+ { name = "pydantic-settings", marker = "extra == 'all'", specifier = ">=2.0.0" },
+ { name = "pydantic-settings", marker = "extra == 'standard'", specifier = ">=2.0.0" },
+ { name = "pydantic-settings", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=2.0.0" },
+ { name = "python-multipart", marker = "extra == 'all'", specifier = ">=0.0.18" },
+ { name = "python-multipart", marker = "extra == 'standard'", specifier = ">=0.0.18" },
+ { name = "python-multipart", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.0.18" },
+ { name = "pyyaml", marker = "extra == 'all'", specifier = ">=5.3.1" },
+ { name = "starlette", specifier = ">=0.40.0,<0.51.0" },
+ { name = "typing-extensions", specifier = ">=4.8.0" },
+ { name = "ujson", marker = "extra == 'all'", specifier = ">=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0" },
+ { name = "uvicorn", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.12.0" },
+ { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.12.0" },
+ { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.12.0" },
+]
+provides-extras = ["standard", "standard-no-fastapi-cloud-cli", "all"]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "anyio", extras = ["trio"], specifier = ">=3.2.1,<5.0.0" },
+ { name = "black", specifier = "==25.1.0" },
+ { name = "cairosvg", specifier = "==2.8.2" },
+ { name = "coverage", extras = ["toml"], specifier = ">=6.5.0,<8.0" },
+ { name = "dirty-equals", specifier = "==0.9.0" },
+ { name = "flask", specifier = ">=1.1.2,<4.0.0" },
+ { name = "gitpython", specifier = "==3.1.45" },
+ { name = "griffe-typingdoc", specifier = "==0.3.0" },
+ { name = "griffe-warnings-deprecated", specifier = "==1.1.0" },
+ { name = "httpx", specifier = ">=0.23.0,<1.0.0" },
+ { name = "inline-snapshot", specifier = ">=0.21.1" },
+ { name = "jieba", specifier = "==0.42.1" },
+ { name = "markdown-include-variants", specifier = "==0.0.8" },
+ { name = "mdx-include", specifier = ">=1.4.1,<2.0.0" },
+ { name = "mkdocs-macros-plugin", specifier = "==1.4.1" },
+ { name = "mkdocs-material", specifier = "==9.7.0" },
+ { name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" },
+ { name = "mkdocstrings", extras = ["python"], specifier = "==0.30.1" },
+ { name = "mypy", specifier = "==1.14.1" },
+ { name = "pillow", specifier = "==11.3.0" },
+ { name = "playwright", specifier = ">=1.57.0" },
+ { name = "prek", specifier = "==0.2.22" },
+ { name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" },
+ { name = "pydantic-ai", specifier = "==0.4.10" },
+ { name = "pygithub", specifier = "==2.8.1" },
+ { name = "pyjwt", specifier = "==2.9.0" },
+ { name = "pytest", specifier = ">=7.1.3,<9.0.0" },
+ { name = "pytest-codspeed", specifier = "==4.2.0" },
+ { name = "python-slugify", specifier = "==8.0.4" },
+ { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" },
+ { name = "ruff", specifier = "==0.14.3" },
+ { name = "sqlmodel", specifier = "==0.0.27" },
+ { name = "strawberry-graphql", specifier = ">=0.200.0,<1.0.0" },
+ { name = "typer", specifier = "==0.16.0" },
+ { name = "types-orjson", specifier = "==3.6.2" },
+ { name = "types-ujson", specifier = "==5.10.0.20240515" },
+]
+docs = [
+ { name = "black", specifier = "==25.1.0" },
+ { name = "cairosvg", specifier = "==2.8.2" },
+ { name = "griffe-typingdoc", specifier = "==0.3.0" },
+ { name = "griffe-warnings-deprecated", specifier = "==1.1.0" },
+ { name = "httpx", specifier = ">=0.23.0,<1.0.0" },
+ { name = "jieba", specifier = "==0.42.1" },
+ { name = "markdown-include-variants", specifier = "==0.0.8" },
+ { name = "mdx-include", specifier = ">=1.4.1,<2.0.0" },
+ { name = "mkdocs-macros-plugin", specifier = "==1.4.1" },
+ { name = "mkdocs-material", specifier = "==9.7.0" },
+ { name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" },
+ { name = "mkdocstrings", extras = ["python"], specifier = "==0.30.1" },
+ { name = "pillow", specifier = "==11.3.0" },
+ { name = "python-slugify", specifier = "==8.0.4" },
+ { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" },
+ { name = "ruff", specifier = "==0.14.3" },
+ { name = "typer", specifier = "==0.16.0" },
+]
+docs-tests = [
+ { name = "httpx", specifier = ">=0.23.0,<1.0.0" },
+ { name = "ruff", specifier = "==0.14.3" },
+]
+github-actions = [
+ { name = "httpx", specifier = ">=0.27.0,<1.0.0" },
+ { name = "pydantic", specifier = ">=2.5.3,<3.0.0" },
+ { name = "pydantic-settings", specifier = ">=2.1.0,<3.0.0" },
+ { name = "pygithub", specifier = ">=2.3.0,<3.0.0" },
+ { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" },
+ { name = "smokeshow", specifier = ">=0.5.0" },
+]
+tests = [
+ { name = "anyio", extras = ["trio"], specifier = ">=3.2.1,<5.0.0" },
+ { name = "coverage", extras = ["toml"], specifier = ">=6.5.0,<8.0" },
+ { name = "dirty-equals", specifier = "==0.9.0" },
+ { name = "flask", specifier = ">=1.1.2,<4.0.0" },
+ { name = "httpx", specifier = ">=0.23.0,<1.0.0" },
+ { name = "inline-snapshot", specifier = ">=0.21.1" },
+ { name = "mypy", specifier = "==1.14.1" },
+ { name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" },
+ { name = "pyjwt", specifier = "==2.9.0" },
+ { name = "pytest", specifier = ">=7.1.3,<9.0.0" },
+ { name = "pytest-codspeed", specifier = "==4.2.0" },
+ { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" },
+ { name = "ruff", specifier = "==0.14.3" },
+ { name = "sqlmodel", specifier = "==0.0.27" },
+ { name = "strawberry-graphql", specifier = ">=0.200.0,<1.0.0" },
+ { name = "types-orjson", specifier = "==3.6.2" },
+ { name = "types-ujson", specifier = "==5.10.0.20240515" },
+]
+translations = [
+ { name = "gitpython", specifier = "==3.1.45" },
+ { name = "pydantic-ai", specifier = "==0.4.10" },
+ { name = "pygithub", specifier = "==2.8.1" },
+]
+
+[[package]]
+name = "fastapi-cli"
+version = "0.0.20"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "rich-toolkit" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+ { name = "typer" },
+ { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" },
+ { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d3/ca/d90fb3bfbcbd6e56c77afd9d114dd6ce8955d8bb90094399d1c70e659e40/fastapi_cli-0.0.20.tar.gz", hash = "sha256:d17c2634f7b96b6b560bc16b0035ed047d523c912011395f49f00a421692bc3a", size = 19786, upload-time = "2025-12-22T17:13:33.794Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/08/89/5c4eef60524d0fd704eb0706885b82cd5623a43396b94e4a5b17d3a3f516/fastapi_cli-0.0.20-py3-none-any.whl", hash = "sha256:e58b6a0038c0b1532b7a0af690656093dee666201b6b19d3c87175b358e9f783", size = 12390, upload-time = "2025-12-22T17:13:31.708Z" },
+]
+
+[package.optional-dependencies]
+standard = [
+ { name = "fastapi-cloud-cli" },
+ { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" },
+ { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" },
+]
+standard-no-fastapi-cloud-cli = [
+ { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" },
+ { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" },
+]
+
+[[package]]
+name = "fastapi-cloud-cli"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "fastar" },
+ { name = "httpx" },
+ { name = "pydantic", extra = ["email"] },
+ { name = "rich-toolkit" },
+ { name = "rignore" },
+ { name = "sentry-sdk" },
+ { name = "typer" },
+ { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" },
+ { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/51/5d/3b33438de35521fab4968b232caa9a4bd568a5078f2b2dfb7bb8a4528603/fastapi_cloud_cli-0.8.0.tar.gz", hash = "sha256:cf07c502528bfd9e6b184776659f05d9212811d76bbec9fbb6bf34bed4c7456f", size = 30257, upload-time = "2025-12-23T12:08:33.904Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/dd/8e/abb95ef59e91bb5adaa2d18fbf9ea70fd524010bb03f406a2dd2a4775ef9/fastapi_cloud_cli-0.8.0-py3-none-any.whl", hash = "sha256:e9f40bee671d985fd25d7a5409b56d4f103777bf8a0c6d746ea5fbf97a8186d9", size = 22306, upload-time = "2025-12-23T12:08:32.68Z" },
+]
+
+[[package]]
+name = "fastar"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/69/e7/f89d54fb04104114dd0552836dc2b47914f416cc0e200b409dd04a33de5e/fastar-0.8.0.tar.gz", hash = "sha256:f4d4d68dbf1c4c2808f0e730fac5843493fc849f70fe3ad3af60dfbaf68b9a12", size = 68524, upload-time = "2025-11-26T02:36:00.72Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c3/e2/51d9ee443aabcd5aa581d45b18b6198ced364b5cd97e5504c5d782ceb82c/fastar-0.8.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c9f930cff014cf79d396d0541bd9f3a3f170c9b5e45d10d634d98f9ed08788c3", size = 708536, upload-time = "2025-11-26T02:34:35.236Z" },
+ { url = "https://files.pythonhosted.org/packages/07/2a/edfc6274768b8a3859a5ca4f8c29cb7f614d7f27d2378e2c88aa91cda54e/fastar-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07b70f712d20622346531a4b46bb332569bea621f61314c0b7e80903a16d14cf", size = 632235, upload-time = "2025-11-26T02:34:19.367Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/1e/3cfbaaec464caef196700ee2ffae1c03f94f7c5e2a85d0ec0ea9cdd1da81/fastar-0.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:330639db3bfba4c6d132421a2a4aeb81e7bea8ce9159cdb6e247fbc5fae97686", size = 871386, upload-time = "2025-11-26T02:33:47.613Z" },
+ { url = "https://files.pythonhosted.org/packages/82/50/224a674ad541054179e4e6e0b54bb6e162f04f698a2512b42a8085fc6b6f/fastar-0.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ea7ceb6231e48d7bb0d7dc13e946baa29c7f6873eaf4afb69725d6da349033", size = 764955, upload-time = "2025-11-26T02:32:44.279Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/5e/4608184aa57cb6a54f62c1eb3e5133ba8d461fc7f13193c0255effbec12a/fastar-0.8.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a90695a601a78bbca910fdf2efcdf3103c55d0de5a5c6e93556d707bf886250b", size = 765987, upload-time = "2025-11-26T02:32:59.701Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/53/6afd2b680dddfa10df9a16bbcf6cabfee0d92435d5c7e3f4cfe3b1712662/fastar-0.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d0bf655ff4c9320b0ca8a5b128063d5093c0c8c1645a2b5f7167143fd8531aa", size = 930900, upload-time = "2025-11-26T02:33:16.059Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/1e/b7a304bfcc1d06845cbfa4b464516f6fff9c8c6692f6ef80a3a86b04e199/fastar-0.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8df22cdd8d58e7689aa89b2e4a07e8e5fa4f88d2d9c2621f0e88a49be97ccea", size = 821523, upload-time = "2025-11-26T02:33:30.897Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/da/9ef8605c6d233cd6ca3a95f7f518ac22aa064903afe6afa57733bfb7c31b/fastar-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a5e6ad722685128521c8fb44cf25bd38669650ba3a4b466b8903e5aa28e1a0", size = 821268, upload-time = "2025-11-26T02:34:04.003Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/22/ed37c78a6b4420de1677d82e79742787975c34847229c33dc376334c7283/fastar-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:31cd541231a2456e32104da891cf9962c3b40234d0465cbf9322a6bc8a1b05d5", size = 986286, upload-time = "2025-11-26T02:34:50.279Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/a6/366b15f432d85d4089e6e4b52a09cc2a2bcf4d7a1f0771e3d3194deccb1e/fastar-0.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:175db2a98d67ced106468e8987975484f8bbbd5ad99201da823b38bafb565ed5", size = 1041921, upload-time = "2025-11-26T02:35:07.292Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/45/45f8e6991e3ce9f8aeefdc8d4c200daada41097a36808643d1703464c3e2/fastar-0.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ada877ab1c65197d772ce1b1c2e244d4799680d8b3f136a4308360f3d8661b23", size = 1047302, upload-time = "2025-11-26T02:35:24.995Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/e2/a587796111a3cd4b78cd61ec3fc1252d8517d81f763f4164ed5680f84810/fastar-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:01084cb75f13ca6a8e80bd41584322523189f8e81b472053743d6e6c3062b5a6", size = 995141, upload-time = "2025-11-26T02:35:42.449Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c0/7a8ec86695b0b77168e220cf2af1aa30592f5ecdbd0ce6d641d29c4a8bae/fastar-0.8.0-cp310-cp310-win32.whl", hash = "sha256:ca639b9909805e44364ea13cca2682b487e74826e4ad75957115ec693228d6b6", size = 456544, upload-time = "2025-11-26T02:36:23.801Z" },
+ { url = "https://files.pythonhosted.org/packages/be/a9/8da4deb840121c59deabd939ce2dca3d6beec85576f3743d1144441938b5/fastar-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:fbc0f2ed0f4add7fb58034c576584d44d7eaaf93dee721dfb26dbed6e222dbac", size = 490701, upload-time = "2025-11-26T02:36:09.625Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/15/1c764530b81b266f6d27d78d49b6bef22a73b3300cd83a280bfd244908c5/fastar-0.8.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cd9c0d3ebf7a0a6f642f771cf41b79f7c98d40a3072a8abe1174fbd9bd615bd3", size = 708427, upload-time = "2025-11-26T02:34:36.502Z" },
+ { url = "https://files.pythonhosted.org/packages/41/fc/75d42c008516543219e4293e4d8ac55da57a5c63147484f10468bd1bc24e/fastar-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2875a077340fe4f8099bd3ed8fa90d9595e1ac3cd62ae19ab690d5bf550eeb35", size = 631740, upload-time = "2025-11-26T02:34:20.718Z" },
+ { url = "https://files.pythonhosted.org/packages/50/8d/9632984f7824ed2210157dcebd8e9821ef6d4f2b28510d0516db6625ff9b/fastar-0.8.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a999263d9f87184bf2801833b2ecf105e03c0dd91cac78685673b70da564fd64", size = 871628, upload-time = "2025-11-26T02:33:49.279Z" },
+ { url = "https://files.pythonhosted.org/packages/05/97/3eb6ea71b7544d45cd29cacb764ca23cde8ce0aed1a6a02251caa4c0a818/fastar-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c41111da56430f638cbfc498ebdcc7d30f63416e904b27b7695c29bd4889cb8", size = 765005, upload-time = "2025-11-26T02:32:45.833Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/45/3eb0ee945a0b5d5f9df7e7c25c037ce7fa441cd0b4d44f76d286e2f4396a/fastar-0.8.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3719541a12bb09ab1eae91d2c987a9b2b7d7149c52e7109ba6e15b74aabc49b1", size = 765587, upload-time = "2025-11-26T02:33:01.174Z" },
+ { url = "https://files.pythonhosted.org/packages/51/bb/7defd6ec0d9570b1987d8ebde52d07d97f3f26e10b592fb3e12738eba39a/fastar-0.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a9b0fff8079b18acdface7ef1b7f522fd9a589f65ca4a1a0dd7c92a0886c2a2", size = 931150, upload-time = "2025-11-26T02:33:17.374Z" },
+ { url = "https://files.pythonhosted.org/packages/28/54/62e51e684dab347c61878afbf09e177029c1a91eb1e39ef244e6b3ef9efa/fastar-0.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac073576c1931959191cb20df38bab21dd152f66c940aa3ca8b22e39f753b2f3", size = 821354, upload-time = "2025-11-26T02:33:32.083Z" },
+ { url = "https://files.pythonhosted.org/packages/53/a8/12708ea4d21e3cf9f485b2a67d44ce84d949a6eddcc9aa5b3d324585ab43/fastar-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:003b59a7c3e405b6a7bff8fab17d31e0ccbc7f06730a8f8ca1694eeea75f3c76", size = 821626, upload-time = "2025-11-26T02:34:05.685Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/c4/1b4d3347c7a759853f963410bf6baf42fe014d587c50c39c8e145f4bf1a0/fastar-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a7b96748425efd9fc155cd920d65088a1b0d754421962418ea73413d02ff515a", size = 986187, upload-time = "2025-11-26T02:34:52.047Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/59/2dbe0dc2570764475e60030403738faa261a9d3bff16b08629c378ab939a/fastar-0.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:90957a30e64418b02df5b4d525bea50403d98a4b1f29143ce5914ddfa7e54ee4", size = 1041536, upload-time = "2025-11-26T02:35:08.926Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/0f/639b295669c7ca6fbc2b4be2a7832aaeac1a5e06923f15a8a6d6daecbc7d/fastar-0.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f6e784a8015623fbb7ccca1af372fd82cb511b408ddd2348dc929fc6e415df73", size = 1047149, upload-time = "2025-11-26T02:35:26.597Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/e7/23e3a19e06d261d1894f98eca9458f98c090c505a0c712dafc0ff1fc2965/fastar-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a03eaf287bbc93064688a1220580ce261e7557c8898f687f4d0b281c85b28d3c", size = 994992, upload-time = "2025-11-26T02:35:44.009Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/7a/3ea4726bae3ac9358d02107ae48f3e10ee186dbed554af79e00b7b498c44/fastar-0.8.0-cp311-cp311-win32.whl", hash = "sha256:661a47ed90762f419406c47e802f46af63a08254ba96abd1c8191e4ce967b665", size = 456449, upload-time = "2025-11-26T02:36:25.291Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/3c/0142bee993c431ee91cf5535e6e4b079ad491f620c215fcd79b7e5ffeb2b/fastar-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:b48abd6056fef7bc3d414aafb453c5b07fdf06d2df5a2841d650288a3aa1e9d3", size = 490863, upload-time = "2025-11-26T02:36:11.114Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/18/d119944f6bdbf6e722e204e36db86390ea45684a1bf6be6e3aa42abd471f/fastar-0.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:50c18788b3c6ffb85e176dcb8548bb8e54616a0519dcdbbfba66f6bbc4316933", size = 462230, upload-time = "2025-11-26T02:36:01.917Z" },
+ { url = "https://files.pythonhosted.org/packages/58/f1/5b2ff898abac7f1a418284aad285e3a4f68d189c572ab2db0f6c9079dd16/fastar-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f10d2adfe40f47ff228f4efaa32d409d732ded98580e03ed37c9535b5fc923d", size = 706369, upload-time = "2025-11-26T02:34:37.783Z" },
+ { url = "https://files.pythonhosted.org/packages/23/60/8046a386dca39154f80c927cbbeeb4b1c1267a3271bffe61552eb9995757/fastar-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b930da9d598e3bc69513d131f397e6d6be4643926ef3de5d33d1e826631eb036", size = 629097, upload-time = "2025-11-26T02:34:21.888Z" },
+ { url = "https://files.pythonhosted.org/packages/22/7e/1ae005addc789924a9268da2394d3bb5c6f96836f7e37b7e3d23c2362675/fastar-0.8.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9d210da2de733ca801de83e931012349d209f38b92d9630ccaa94bd445bdc9b8", size = 868938, upload-time = "2025-11-26T02:33:51.119Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/77/290a892b073b84bf82e6b2259708dfe79c54f356e252c2dd40180b16fe07/fastar-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa02270721517078a5bd61a38719070ac2537a4aa6b6c48cf369cf2abc59174a", size = 765204, upload-time = "2025-11-26T02:32:47.02Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/00/c3155171b976003af3281f5258189f1935b15d1221bfc7467b478c631216/fastar-0.8.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:83c391e5b789a720e4d0029b9559f5d6dee3226693c5b39c0eab8eaece997e0f", size = 764717, upload-time = "2025-11-26T02:33:02.453Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/43/405b7ad76207b2c11b7b59335b70eac19e4a2653977f5588a1ac8fed54f4/fastar-0.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3258d7a78a72793cdd081545da61cabe85b1f37634a1d0b97ffee0ff11d105ef", size = 931502, upload-time = "2025-11-26T02:33:18.619Z" },
+ { url = "https://files.pythonhosted.org/packages/da/8a/a3dde6d37cc3da4453f2845cdf16675b5686b73b164f37e2cc579b057c2c/fastar-0.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6eab95dd985cdb6a50666cbeb9e4814676e59cfe52039c880b69d67cfd44767", size = 821454, upload-time = "2025-11-26T02:33:33.427Z" },
+ { url = "https://files.pythonhosted.org/packages/da/c1/904fe2468609c8990dce9fe654df3fbc7324a8d8e80d8240ae2c89757064/fastar-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:829b1854166141860887273c116c94e31357213fa8e9fe8baeb18bd6c38aa8d9", size = 821647, upload-time = "2025-11-26T02:34:07Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/73/a0642ab7a400bc07528091785e868ace598fde06fcd139b8f865ec1b6f3c/fastar-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1667eae13f9457a3c737f4376d68e8c3e548353538b28f7e4273a30cb3965cd", size = 986342, upload-time = "2025-11-26T02:34:53.371Z" },
+ { url = "https://files.pythonhosted.org/packages/af/af/60c1bfa6edab72366461a95f053d0f5f7ab1825fe65ca2ca367432cd8629/fastar-0.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b864a95229a7db0814cd9ef7987cb713fd43dce1b0d809dd17d9cd6f02fdde3e", size = 1040207, upload-time = "2025-11-26T02:35:10.65Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/a0/0d624290dec622e7fa084b6881f456809f68777d54a314f5dde932714506/fastar-0.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c05fbc5618ce17675a42576fa49858d79734627f0a0c74c0875ab45ee8de340c", size = 1045031, upload-time = "2025-11-26T02:35:28.108Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/74/cf663af53c4706ba88e6b4af44a6b0c3bd7d7ca09f079dc40647a8f06585/fastar-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7f41c51ee96f338662ee3c3df4840511ba3f9969606840f1b10b7cb633a3c716", size = 994877, upload-time = "2025-11-26T02:35:45.797Z" },
+ { url = "https://files.pythonhosted.org/packages/52/17/444c8be6e77206050e350da7c338102b6cab384be937fa0b1d6d1f9ede73/fastar-0.8.0-cp312-cp312-win32.whl", hash = "sha256:d949a1a2ea7968b734632c009df0571c94636a5e1622c87a6e2bf712a7334f47", size = 455996, upload-time = "2025-11-26T02:36:26.938Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/34/fc3b5e56d71a17b1904800003d9251716e8fd65f662e1b10a26881698a74/fastar-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:fc645994d5b927d769121094e8a649b09923b3c13a8b0b98696d8f853f23c532", size = 490429, upload-time = "2025-11-26T02:36:12.707Z" },
+ { url = "https://files.pythonhosted.org/packages/35/a8/5608cc837417107c594e2e7be850b9365bcb05e99645966a5d6a156285fe/fastar-0.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:d81ee82e8dc78a0adb81728383bd39611177d642a8fa2d601d4ad5ad59e5f3bd", size = 461297, upload-time = "2025-11-26T02:36:03.546Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/a5/79ecba3646e22d03eef1a66fb7fc156567213e2e4ab9faab3bbd4489e483/fastar-0.8.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a3253a06845462ca2196024c7a18f5c0ba4de1532ab1c4bad23a40b332a06a6a", size = 706112, upload-time = "2025-11-26T02:34:39.237Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/03/4f883bce878218a8676c2d7ca09b50c856a5470bb3b7f63baf9521ea6995/fastar-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5cbeb3ebfa0980c68ff8b126295cc6b208ccd81b638aebc5a723d810a7a0e5d2", size = 628954, upload-time = "2025-11-26T02:34:23.705Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/f1/892e471f156b03d10ba48ace9384f5a896702a54506137462545f38e40b8/fastar-0.8.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1c0d5956b917daac77d333d48b3f0f3ff927b8039d5b32d8125462782369f761", size = 868685, upload-time = "2025-11-26T02:33:53.077Z" },
+ { url = "https://files.pythonhosted.org/packages/39/ba/e24915045852e30014ec6840446975c03f4234d1c9270394b51d3ad18394/fastar-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27b404db2b786b65912927ce7f3790964a4bcbde42cdd13091b82a89cd655e1c", size = 765044, upload-time = "2025-11-26T02:32:48.187Z" },
+ { url = "https://files.pythonhosted.org/packages/14/2c/1aa11ac21a99984864c2fca4994e094319ff3a2046e7a0343c39317bd5b9/fastar-0.8.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0902fc89dcf1e7f07b8563032a4159fe2b835e4c16942c76fd63451d0e5f76a3", size = 764322, upload-time = "2025-11-26T02:33:03.859Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/f0/4b91902af39fe2d3bae7c85c6d789586b9fbcf618d7fdb3d37323915906d/fastar-0.8.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:069347e2f0f7a8b99bbac8cd1bc0e06c7b4a31dc964fc60d84b95eab3d869dc1", size = 931016, upload-time = "2025-11-26T02:33:19.902Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/97/8fc43a5a9c0a2dc195730f6f7a0f367d171282cd8be2511d0e87c6d2dad0/fastar-0.8.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd135306f6bfe9a835918280e0eb440b70ab303e0187d90ab51ca86e143f70d", size = 821308, upload-time = "2025-11-26T02:33:34.664Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/e9/058615b63a7fd27965e8c5966f393ed0c169f7ff5012e1674f21684de3ba/fastar-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d06d6897f43c27154b5f2d0eb930a43a81b7eec73f6f0b0114814d4a10ab38", size = 821171, upload-time = "2025-11-26T02:34:08.498Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/cf/69e16a17961570a755c37ffb5b5aa7610d2e77807625f537989da66f2a9d/fastar-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a922f8439231fa0c32b15e8d70ff6d415619b9d40492029dabbc14a0c53b5f18", size = 986227, upload-time = "2025-11-26T02:34:55.06Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/83/2100192372e59b56f4ace37d7d9cabda511afd71b5febad1643d1c334271/fastar-0.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a739abd51eb766384b4caff83050888e80cd75bbcfec61e6d1e64875f94e4a40", size = 1039395, upload-time = "2025-11-26T02:35:12.166Z" },
+ { url = "https://files.pythonhosted.org/packages/75/15/cdd03aca972f55872efbb7cf7540c3fa7b97a75d626303a3ea46932163dc/fastar-0.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5a65f419d808b23ac89d5cd1b13a2f340f15bc5d1d9af79f39fdb77bba48ff1b", size = 1044766, upload-time = "2025-11-26T02:35:29.62Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/29/945e69e4e2652329ace545999334ec31f1431fbae3abb0105587e11af2ae/fastar-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7bb2ae6c0cce58f0db1c9f20495e7557cca2c1ee9c69bbd90eafd54f139171c5", size = 994740, upload-time = "2025-11-26T02:35:47.887Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/5d/dbfe28f8cd1eb484bba0c62e5259b2cf6fea229d6ef43e05c06b5a78c034/fastar-0.8.0-cp313-cp313-win32.whl", hash = "sha256:b28753e0d18a643272597cb16d39f1053842aa43131ad3e260c03a2417d38401", size = 455990, upload-time = "2025-11-26T02:36:28.502Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/01/e965740bd36e60ef4c5aa2cbe42b6c4eb1dc3551009238a97c2e5e96bd23/fastar-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:620e5d737dce8321d49a5ebb7997f1fd0047cde3512082c27dc66d6ac8c1927a", size = 490227, upload-time = "2025-11-26T02:36:14.363Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/10/c99202719b83e5249f26902ae53a05aea67d840eeb242019322f20fc171c/fastar-0.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:c4c4bd08df563120cd33e854fe0a93b81579e8571b11f9b7da9e84c37da2d6b6", size = 461078, upload-time = "2025-11-26T02:36:04.94Z" },
+ { url = "https://files.pythonhosted.org/packages/96/4a/9573b87a0ef07580ed111e7230259aec31bb33ca3667963ebee77022ec61/fastar-0.8.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:50b36ce654ba44b0e13fae607ae17ee6e1597b69f71df1bee64bb8328d881dfc", size = 706041, upload-time = "2025-11-26T02:34:40.638Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/19/f95444a1d4f375333af49300aa75ee93afa3335c0e40fda528e460ed859c/fastar-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63a892762683d7ab00df0227d5ea9677c62ff2cde9b875e666c0be569ed940f3", size = 628617, upload-time = "2025-11-26T02:34:24.893Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/c9/b51481b38b7e3f16ef2b9e233b1a3623386c939d745d6e41bbd389eaae30/fastar-0.8.0-cp314-cp314-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4ae6a145c1bff592644bde13f2115e0239f4b7babaf506d14e7d208483cf01a5", size = 869299, upload-time = "2025-11-26T02:33:54.274Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/02/3ba1267ee5ba7314e29c431cf82eaa68586f2c40cdfa08be3632b7d07619/fastar-0.8.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ae0ff7c0a1c7e1428404b81faee8aebef466bfd0be25bfe4dabf5d535c68741", size = 764667, upload-time = "2025-11-26T02:32:49.606Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/84/bf33530fd015b5d7c2cc69e0bce4a38d736754a6955487005aab1af6adcd/fastar-0.8.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dbfd87dbd217b45c898b2dbcd0169aae534b2c1c5cbe3119510881f6a5ac8ef5", size = 763993, upload-time = "2025-11-26T02:33:05.782Z" },
+ { url = "https://files.pythonhosted.org/packages/da/e0/9564d24e7cea6321a8d921c6d2a457044a476ef197aa4708e179d3d97f0d/fastar-0.8.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5abd99fcba83ef28c8fe6ae2927edc79053db43a0457a962ed85c9bf150d37", size = 930153, upload-time = "2025-11-26T02:33:21.53Z" },
+ { url = "https://files.pythonhosted.org/packages/35/b1/6f57fcd8d6e192cfebf97e58eb27751640ad93784c857b79039e84387b51/fastar-0.8.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91d4c685620c3a9d6b5ae091dbabab4f98b20049b7ecc7976e19cc9016c0d5d6", size = 821177, upload-time = "2025-11-26T02:33:35.839Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/78/9e004ea9f3aa7466f5ddb6f9518780e1d2f0ed3ca55f093632982598bace/fastar-0.8.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f77c2f2cad76e9dc7b6701297adb1eba87d0485944b416fc2ccf5516c01219a3", size = 820652, upload-time = "2025-11-26T02:34:09.776Z" },
+ { url = "https://files.pythonhosted.org/packages/42/95/b604ed536544005c9f1aee7c4c74b00150db3d8d535cd8232dc20f947063/fastar-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e7f07c4a3dada7757a8fc430a5b4a29e6ef696d2212747213f57086ffd970316", size = 985961, upload-time = "2025-11-26T02:34:56.401Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/7b/fa9d4d96a5d494bdb8699363bb9de8178c0c21a02e1d89cd6f913d127018/fastar-0.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:90c0c3fe55105c0aed8a83135dbdeb31e683455dbd326a1c48fa44c378b85616", size = 1039316, upload-time = "2025-11-26T02:35:13.807Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/f9/8462789243bc3f33e8401378ec6d54de4e20cfa60c96a0e15e3e9d1389bb/fastar-0.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fb9ee51e5bffe0dab3d3126d3a4fac8d8f7235cedcb4b8e74936087ce1c157f3", size = 1045028, upload-time = "2025-11-26T02:35:31.079Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/71/9abb128777e616127194b509e98fcda3db797d76288c1a8c23dd22afc14f/fastar-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e380b1e8d30317f52406c43b11e98d11e1d68723bbd031e18049ea3497b59a6d", size = 994677, upload-time = "2025-11-26T02:35:49.391Z" },
+ { url = "https://files.pythonhosted.org/packages/de/c1/b81b3f194853d7ad232a67a1d768f5f51a016f165cfb56cb31b31bbc6177/fastar-0.8.0-cp314-cp314-win32.whl", hash = "sha256:1c4ffc06e9c4a8ca498c07e094670d8d8c0d25b17ca6465b9774da44ea997ab1", size = 456687, upload-time = "2025-11-26T02:36:30.205Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/87/9e0cd4768a98181d56f0cdbab2363404cc15deb93f4aad3b99cd2761bbaa/fastar-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:5517a8ad4726267c57a3e0e2a44430b782e00b230bf51c55b5728e758bb3a692", size = 490578, upload-time = "2025-11-26T02:36:16.218Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/1e/580a76cf91847654f2ad6520e956e93218f778540975bc4190d363f709e2/fastar-0.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:58030551046ff4a8616931e52a36c83545ff05996db5beb6e0cd2b7e748aa309", size = 461473, upload-time = "2025-11-26T02:36:06.373Z" },
+ { url = "https://files.pythonhosted.org/packages/58/4c/bdb5c6efe934f68708529c8c9d4055ebef5c4be370621966438f658b29bd/fastar-0.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:1e7d29b6bfecb29db126a08baf3c04a5ab667f6cea2b7067d3e623a67729c4a6", size = 705570, upload-time = "2025-11-26T02:34:42.01Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/78/f01ac7e71d5a37621bd13598a26e948a12b85ca8042f7ee1a0a8c9f59cda/fastar-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05eb7b96940f9526b485f1d0b02393839f0f61cac4b1f60024984f8b326d2640", size = 627761, upload-time = "2025-11-26T02:34:26.152Z" },
+ { url = "https://files.pythonhosted.org/packages/06/45/6df0ecda86ea9d2e95053c1a655d153dee55fc121b6e13ea6d1e246a50b6/fastar-0.8.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:619352d8ac011794e2345c462189dc02ba634750d23cd9d86a9267dd71b1f278", size = 869414, upload-time = "2025-11-26T02:33:55.618Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/72/486421f5a8c0c377cc82e7a50c8a8ea899a6ec2aa72bde8f09fb667a2dc8/fastar-0.8.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74ebfecef3fe6d7a90355fac1402fd30636988332a1d33f3e80019a10782bb24", size = 763863, upload-time = "2025-11-26T02:32:51.051Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/64/39f654dbb41a3867fb1f2c8081c014d8f1d32ea10585d84cacbef0b32995/fastar-0.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2975aca5a639e26a3ab0d23b4b0628d6dd6d521146c3c11486d782be621a35aa", size = 763065, upload-time = "2025-11-26T02:33:07.274Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/bd/c011a34fb3534c4c3301f7c87c4ffd7e47f6113c904c092ddc8a59a303ea/fastar-0.8.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afc438eaed8ff0dcdd9308268be5cb38c1db7e94c3ccca7c498ca13a4a4535a3", size = 930530, upload-time = "2025-11-26T02:33:23.117Z" },
+ { url = "https://files.pythonhosted.org/packages/55/9d/aa6e887a7033c571b1064429222bbe09adc9a3c1e04f3d1788ba5838ebd5/fastar-0.8.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ced0a5399cc0a84a858ef0a31ca2d0c24d3bbec4bcda506a9192d8119f3590a", size = 820572, upload-time = "2025-11-26T02:33:37.542Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/9c/7a3a2278a1052e1a5d98646de7c095a00cffd2492b3b84ce730e2f1cd93a/fastar-0.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec9b23da8c4c039da3fe2e358973c66976a0c8508aa06d6626b4403cb5666c19", size = 820649, upload-time = "2025-11-26T02:34:11.108Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/d38edc1f4438cd047e56137c26d94783ffade42e1b3bde620ccf17b771ef/fastar-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:dfba078fcd53478032fd0ceed56960ec6b7ff0511cfc013a8a3a4307e3a7bac4", size = 985653, upload-time = "2025-11-26T02:34:57.884Z" },
+ { url = "https://files.pythonhosted.org/packages/69/d9/2147d0c19757e165cd62d41cec3f7b38fad2ad68ab784978b5f81716c7ea/fastar-0.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ade56c94c14be356d295fecb47a3fcd473dd43a8803ead2e2b5b9e58feb6dcfa", size = 1038140, upload-time = "2025-11-26T02:35:15.778Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/1d/ec4c717ffb8a308871e9602ec3197d957e238dc0227127ac573ec9bca952/fastar-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e48d938f9366db5e59441728f70b7f6c1ccfab7eff84f96f9b7e689b07786c52", size = 1045195, upload-time = "2025-11-26T02:35:32.865Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/9f/637334dc8c8f3bb391388b064ae13f0ad9402bc5a6c3e77b8887d0c31921/fastar-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:79c441dc1482ff51a54fb3f57ae6f7bb3d2cff88fa2cc5d196c519f8aab64a56", size = 994686, upload-time = "2025-11-26T02:35:51.392Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/e2/dfa19a4b260b8ab3581b7484dcb80c09b25324f4daa6b6ae1c7640d1607a/fastar-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:187f61dc739afe45ac8e47ed7fd1adc45d52eac110cf27d579155720507d6fbe", size = 455767, upload-time = "2025-11-26T02:36:34.758Z" },
+ { url = "https://files.pythonhosted.org/packages/51/47/df65c72afc1297797b255f90c4778b5d6f1f0f80282a134d5ab610310ed9/fastar-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:40e9d763cf8bf85ce2fa256e010aa795c0fe3d3bd1326d5c3084e6ce7857127e", size = 489971, upload-time = "2025-11-26T02:36:22.081Z" },
+ { url = "https://files.pythonhosted.org/packages/85/11/0aa8455af26f0ae89e42be67f3a874255ee5d7f0f026fc86e8d56f76b428/fastar-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:e59673307b6a08210987059a2bdea2614fe26e3335d0e5d1a3d95f49a05b1418", size = 460467, upload-time = "2025-11-26T02:36:07.978Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/0d/462ce073b1f87cc6bf33b656c340c103eeb67487d118b761a697f1ee454f/fastar-0.8.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:923afc2db5192e56e71952a88e3fe5965c7c9c910d385d2db7573136f064f2fa", size = 709519, upload-time = "2025-11-26T02:34:44.783Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/f1/ceca1e04fd3266f866efdbb5d343fee1d8ff8fe94c64c8d1aab68b483ad0/fastar-0.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4fbe775356930f3aab0ce709fdf8ecf90c10882f5bbdcea215c89a3b14090c50", size = 632509, upload-time = "2025-11-26T02:34:29.428Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/db/fdb07e9dce80ebb38138f166a30dc482c82cc8dfcfda1024e8b100a53d1c/fastar-0.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ff516154e77f4bf78c31a0c11aa78a8a80e11b6964ec6f28982e42ffcbb543c", size = 871400, upload-time = "2025-11-26T02:33:58.461Z" },
+ { url = "https://files.pythonhosted.org/packages/54/6d/d0a342528002cf7527339fd275bdee5e5cc90c2f193e8418d3510ffedcd7/fastar-0.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d2fdd1c987ff2300bdf39baed556f8e155f8577018775e794a268ecf1707610", size = 765781, upload-time = "2025-11-26T02:32:53.929Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/96/dd2721e7160eed7bc9c3b695e942868c7ba2fab41947278fa380c18569be/fastar-0.8.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d80e4dad8ee2362a71870b1e735800bb5e97f12ebbee4bd0cf15a81ad2428b5a", size = 766126, upload-time = "2025-11-26T02:33:10.075Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/ac/6533d2608254def722cd8c852f4ac8fb413fc43ad3251b7fb99863ffaa83/fastar-0.8.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a17abee1febf5363ed2633f5e13de4be481ba1ab5f77860d39470eccdc4b65af", size = 932549, upload-time = "2025-11-26T02:33:25.866Z" },
+ { url = "https://files.pythonhosted.org/packages/97/dc/aac042d0d0017b8d75ff34381cc28b482ace0d78a26d4eef9a8674f850c2/fastar-0.8.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64cbde8e0ece3d799090a4727f936f66c5990d3ac59416f3de76a2c676e8e568", size = 821860, upload-time = "2025-11-26T02:33:41.423Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/ef/c31057572e3a5a2c90da4986d8594d0ff33097b4a44058f9f52103c33677/fastar-0.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63d98b26590d293a9d9a379bae88367a8f3a6137c28819ed6dd6e11aca4a5c6e", size = 821660, upload-time = "2025-11-26T02:34:13.84Z" },
+ { url = "https://files.pythonhosted.org/packages/39/00/d42fc84e8ce8587eb746406aa74ec231f97ce659a48bc064d5ff5cd78889/fastar-0.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf440983d4d64582bddf2f0bd3c43ea1db93a8c31cf7c20e473bffaf6d9c0b6d", size = 987026, upload-time = "2025-11-26T02:35:01.181Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/d9/5e4d37faca6f2df6a0bf1efc340192a871356162a7cc53626dc117645ad6/fastar-0.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1d90cbf984a39afda27afe08e40c2d8eddc49c5e80590af641610c7b6dc20161", size = 1042165, upload-time = "2025-11-26T02:35:18.729Z" },
+ { url = "https://files.pythonhosted.org/packages/77/fd/bb4731243e42f385e8db9da824971f5d0380e6b31466c36eb089d631c589/fastar-0.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ca0db5e563d84b639fe15385eeca940777b6d2f0a1f3bb7cd5b55ab7124f0554", size = 1046990, upload-time = "2025-11-26T02:35:36.158Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/50/2ac066f771858ea45d885cfde7262b286a223af9538d3f85d29d5159b4fb/fastar-0.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:42ff3052d74684a636423d4f040db88eebd4caf20842fa5f06020e0130c01f69", size = 995637, upload-time = "2025-11-26T02:35:54.714Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/04/c884ea3fa7c154ac0e89219a337c9d6e0d52d0eb5547b3f99b896eca417e/fastar-0.8.0-cp39-cp39-win32.whl", hash = "sha256:15e3dfaa769d2117ef707e5f47c62126d1b63f8e9c85133112f33f1fbdf8942f", size = 456817, upload-time = "2025-11-26T02:36:33.198Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/0f/3a5758cca852bb221e78950fbfece0b932c6de6981ee36fb0ac57cf66c13/fastar-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:5153aa1c194316d0f67b6884a62d122d51fce4196263e92e4bca2a6c47cd44c0", size = 490633, upload-time = "2025-11-26T02:36:19.976Z" },
+ { url = "https://files.pythonhosted.org/packages/25/9f/6eaa810c240236eff2edf736cd50a17c97dbab1693cda4f7bcea09d13418/fastar-0.8.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2127cf2e80ffd49744a160201e0e2f55198af6c028a7b3f750026e0b1f1caa4e", size = 710544, upload-time = "2025-11-26T02:34:46.195Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/a5/58ff9e49a1cd5fbfc8f1238226cbf83b905376a391a6622cdd396b2cfa29/fastar-0.8.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ff85094f10003801339ac4fa9b20a3410c2d8f284d4cba2dc99de6e98c877812", size = 634020, upload-time = "2025-11-26T02:34:31.085Z" },
+ { url = "https://files.pythonhosted.org/packages/80/94/f839257c6600a83fbdb5a7fcc06319599086137b25ba38ca3d2c0fe14562/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3dbca235f0bd804cca6602fe055d3892bebf95fb802e6c6c7d872fb10f7abc6c", size = 871735, upload-time = "2025-11-26T02:34:00.088Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/79/4124c54260f7ee5cb7034bfe499eff2f8512b052d54be4671e59d4f25a4f/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e54bfdee6c81a0005e147319e93d8797f442308032c92fa28d03ef8fda076", size = 766779, upload-time = "2025-11-26T02:32:55.109Z" },
+ { url = "https://files.pythonhosted.org/packages/36/b6/043b263c4126bf6557c942d099503989af9c5c7ee5cca9a04e00f754816f/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a78e5221b94a80800930b7fd0d0e797ae73aadf7044c05ed46cb9bdf870f022", size = 766755, upload-time = "2025-11-26T02:33:11.595Z" },
+ { url = "https://files.pythonhosted.org/packages/57/ff/29a5dc06f2940439ebf98661ecc98d48d3f22fed8d6a2d5dc985d1e8da24/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997092d31ff451de8d0568f6773f3517cb87dcd0bc76184edb65d7154390a6f8", size = 932732, upload-time = "2025-11-26T02:33:27.122Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/e8/2218830f422b37aad52c24b53cb84b5d88bd6fd6ad411bd6689b1a32500d/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:558e8fcf8fe574541df5db14a46cd98bfbed14a811b7014a54f2b714c0cfac42", size = 822571, upload-time = "2025-11-26T02:33:42.986Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/fd/ba6dfeff77cddfe58d85c490b1735c002b81c0d6f826916a8b6c4f8818bc/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1d2a54f87e2908cc19e1a6ee249620174fbefc54a219aba1eaa6f31657683c3", size = 822440, upload-time = "2025-11-26T02:34:15.439Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/57/54d5740c84b35de0eb12975397ecc16785b5ad8bed2dbac38b8c8a7c1edd/fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:ef94901537be277f9ec59db939eb817960496c6351afede5b102699b5098604d", size = 987424, upload-time = "2025-11-26T02:35:02.742Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/c7/18115927f16deb1ddffdbd4ae992e7e33064bc6defa2b92a147948f8bc0c/fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:0afbb92f78bf29d5e9db76fb46cbabc429e49015cddf72ab9e761afbe88ac100", size = 1042675, upload-time = "2025-11-26T02:35:20.252Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/1a/ca884fc7973ec6d765e87af23a4dd25784fb0a36ac2df825f18c3630bbab/fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:fb59c7925e7710ad178d9e1a3e65edf295d9a042a0cdcb673b4040949eb8ad0a", size = 1047098, upload-time = "2025-11-26T02:35:37.643Z" },
+ { url = "https://files.pythonhosted.org/packages/44/ee/25cd645db749b206bb95e1512e57e75d56ccbbb8ec3536f52a7979deab6b/fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e6c4d6329da568ec36b1347b0c09c4d27f9dfdeddf9f438ddb16799ecf170098", size = 997397, upload-time = "2025-11-26T02:35:56.215Z" },
+ { url = "https://files.pythonhosted.org/packages/98/6e/6c46aa7f8c8734e7f96ee5141acd3877667ce66f34eea10703aa7571d191/fastar-0.8.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:998e3fa4b555b63eb134e6758437ed739ad1652fdd2a61dfe1dacbfddc35fe66", size = 710662, upload-time = "2025-11-26T02:34:47.593Z" },
+ { url = "https://files.pythonhosted.org/packages/70/27/fd622442f2fbd4ff5459677987481ef1c60e077cb4e63a2ed4d8dce6f869/fastar-0.8.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5f83e60d845091f3a12bc37f412774264d161576eaf810ed8b43567eb934b7e5", size = 634049, upload-time = "2025-11-26T02:34:32.365Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/ee/aa4d08aea25b5419a7277132e738ab1cd775f26aebddce11413b07e2fdff/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:299672e1c74d8b73c61684fac9159cfc063d35f4b165996a88facb0e26862cb5", size = 872055, upload-time = "2025-11-26T02:34:01.377Z" },
+ { url = "https://files.pythonhosted.org/packages/92/9a/2bf2f77aade575e67997e0c759fd55cb1c66b7a5b437b1cd0e97d8b241bc/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3d3a27066b84d015deab5faee78565509bb33b137896443e4144cb1be1a5f90", size = 766787, upload-time = "2025-11-26T02:32:57.161Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/90/23a3f6c252f11b10c70f854bce09abc61f71b5a0e6a4b0eac2bcb9a2c583/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef0bcf4385bbdd3c1acecce2d9ea7dab7cc9b8ee0581bbccb7ab11908a7ce288", size = 766861, upload-time = "2025-11-26T02:33:12.824Z" },
+ { url = "https://files.pythonhosted.org/packages/76/bb/beeb9078380acd4484db5c957d066171695d9340e3526398eb230127b0c2/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f10ef62b6eda6cb6fd9ba8e1fe08a07d7b2bdcc8eaa00eb91566143b92ed7eee", size = 932667, upload-time = "2025-11-26T02:33:28.405Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/6d/b034cc637bd0ee638d5a85d08e941b0b8ffd44cf391fb751ba98233734f7/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c4f6c82a8ee98c17aa48585ee73b51c89c1b010e5c951af83e07c3436180e3fc", size = 822712, upload-time = "2025-11-26T02:33:44.27Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/2b/7d183c63f59227c4689792042d6647f2586a5e7273b55e81745063088d81/fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6129067fcb86276635b5857010f4e9b9c7d5d15dd571bb03c6c1ed73c40fd92", size = 822659, upload-time = "2025-11-26T02:34:16.815Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/f9/716e0cd9de2427fdf766bc68176f76226cd01fffef3a56c5046fa863f5f0/fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4cc9e77019e489f1ddac446b6a5b9dfb5c3d9abd142652c22a1d9415dbcc0e47", size = 987412, upload-time = "2025-11-26T02:35:04.259Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/b9/9a8c3fd59958c1c8027bc075af11722cdc62c4968bb277e841d131232289/fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:382bfe82c026086487cb17fee12f4c1e2b4e67ce230f2e04487d3e7ddfd69031", size = 1042911, upload-time = "2025-11-26T02:35:21.857Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/2f/c3f30963b47022134b8a231c12845f4d7cfba520f59bbc1a82468aea77c7/fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:908d2b9a1ff3d549cc304b32f95706a536da8f0bcb0bc0f9e4c1cce39b80e218", size = 1047464, upload-time = "2025-11-26T02:35:39.376Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/8a/218ab6d9a2bab3b07718e6cd8405529600edc1e9c266320e8524c8f63251/fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1aa7dbde2d2d73eb5b6203d0f74875cb66350f0f1b4325b4839fc8fbbf5d074e", size = 997309, upload-time = "2025-11-26T02:35:57.722Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/ef/da0cbac6da78b22ddb6ec0bbf3ad1813f8dd991a911342fc20e5feabaa15/fastar-0.8.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:284036bae786a520456ad3f58e72aaf1bd5d74e309132e568343564daa4ae383", size = 710367, upload-time = "2025-11-26T02:34:48.853Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/f0/951d368b4538477fe44651aaa7436318c22bf89e8e18086a68bd89e14a82/fastar-0.8.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5aba0942b4f56acdb8fa8aa7cb506f70c1a17bf13dcab318a17ffb467cb2e7ec", size = 633744, upload-time = "2025-11-26T02:34:33.723Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/d2/5f37b634aff1e27191874b7f0fb7306e60566d984df06ff5c2f901ef123b/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:52eda6230799db7bbd44461c622161e9bcd43603399da19b0daab2782e0030b0", size = 871412, upload-time = "2025-11-26T02:34:02.738Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/cd/41148c74cc164aacb8b5e7cb938a7ecc5a4e595f95e312aafcc8dbe36e48/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f860566b9f3cb1900980f46a4c3f003990c0009c11730f988f758542c17a2364", size = 766470, upload-time = "2025-11-26T02:32:58.503Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/89/b5715d4f969f15fe78b184ae42295e71f0ad2e0e083f0625212271cbda55/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:78f3fe5f45437c66d1dbece5f31aa487e48ef46d76b2082b873d5fa18013ebe1", size = 765285, upload-time = "2025-11-26T02:33:14.148Z" },
+ { url = "https://files.pythonhosted.org/packages/39/4a/88223721278a663684c4fa6cb95ca296e0ba385362de4809a5e85cacfb36/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:82bc445202bbc53f067bb15e3b8639f01fd54d3096a0f9601240690cfd7c9684", size = 933881, upload-time = "2025-11-26T02:33:29.637Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/73/a13d6d57029378477c4d9232eddfb81bb2f74e0e91bdbca864c74223d877/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b1208b5453cfe7192e54765f73844b80d684bd8dc6d6acbbb60ead42590b13e", size = 822511, upload-time = "2025-11-26T02:33:45.882Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/96/79000571a10d93e2110e72b5e7155e6bf138c8cb896117cfb768c74d01ff/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8922754c66699e27d4f1ce07c9c256228054cdc9bb36363e8bb5b503453a6da", size = 822204, upload-time = "2025-11-26T02:34:18.164Z" },
+ { url = "https://files.pythonhosted.org/packages/35/e4/c2066024f336349ce36f85b0df9d082981d453cfdb5c139af2c5eebcc49a/fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:92cad46dfbb9969359823c9f61165ec32d5d675d86e863889416e9b64efea95c", size = 987454, upload-time = "2025-11-26T02:35:05.827Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/b7/e3a062534af02610dea226d2c14e70beb90b91a23562fed8f76bfa35e6cd/fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:f4eb9560a447ff6a4b377f08b6e5d3a31909a612b028f2c57810ffaf570eceb8", size = 1041310, upload-time = "2025-11-26T02:35:23.305Z" },
+ { url = "https://files.pythonhosted.org/packages/91/73/1640476553705ee29ff9350a3b6c707ac150be53094d326e0d81d62a65ec/fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:52455794e6cc2b6a6dbf141a1c4312a1a1215d75e8849a35fcff694454da880f", size = 1047020, upload-time = "2025-11-26T02:35:40.95Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/79/9b492c4da885a6699bc76032dbc285c565429ca1b6dc6b5aa5c908111d22/fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8de5decfa18a03807ae26ba5af095c2c04ac31ae915e9a849363a4495463171f", size = 996379, upload-time = "2025-11-26T02:35:59.297Z" },
+]
+
+[[package]]
+name = "fastavro"
+version = "1.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/65/8b/fa2d3287fd2267be6261d0177c6809a7fa12c5600ddb33490c8dc29e77b2/fastavro-1.12.1.tar.gz", hash = "sha256:2f285be49e45bc047ab2f6bed040bb349da85db3f3c87880e4b92595ea093b2b", size = 1025661, upload-time = "2025-10-10T15:40:55.41Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/40/a0/077fd7cbfc143152cb96780cb592ed6cb6696667d8bc1b977745eb2255a8/fastavro-1.12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:00650ca533907361edda22e6ffe8cf87ab2091c5d8aee5c8000b0f2dcdda7ed3", size = 1000335, upload-time = "2025-10-10T15:40:59.834Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/ae/a115e027f3a75df237609701b03ecba0b7f0aa3d77fe0161df533fde1eb7/fastavro-1.12.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac76d6d95f909c72ee70d314b460b7e711d928845771531d823eb96a10952d26", size = 3221067, upload-time = "2025-10-10T15:41:04.399Z" },
+ { url = "https://files.pythonhosted.org/packages/94/4e/c4991c3eec0175af9a8a0c161b88089cb7bf7fe353b3e3be1bc4cf9036b2/fastavro-1.12.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55eef18c41d4476bd32a82ed5dd86aabc3f614e1b66bdb09ffa291612e1670", size = 3228979, upload-time = "2025-10-10T15:41:06.738Z" },
+ { url = "https://files.pythonhosted.org/packages/21/0c/f2afb8eaea38799ccb1ed07d68bf2659f2e313f1902bbd36774cf6a1bef9/fastavro-1.12.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81563e1f93570e6565487cdb01ba241a36a00e58cff9c5a0614af819d1155d8f", size = 3160740, upload-time = "2025-10-10T15:41:08.731Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/1a/f4d367924b40b86857862c1fa65f2afba94ddadf298b611e610a676a29e5/fastavro-1.12.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bec207360f76f0b3de540758a297193c5390e8e081c43c3317f610b1414d8c8f", size = 3235787, upload-time = "2025-10-10T15:41:10.869Z" },
+ { url = "https://files.pythonhosted.org/packages/90/ec/8db9331896e3dfe4f71b2b3c23f2e97fbbfd90129777467ca9f8bafccb74/fastavro-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:c0390bfe4a9f8056a75ac6785fbbff8f5e317f5356481d2e29ec980877d2314b", size = 449350, upload-time = "2025-10-10T15:41:12.104Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/e9/31c64b47cefc0951099e7c0c8c8ea1c931edd1350f34d55c27cbfbb08df1/fastavro-1.12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b632b713bc5d03928a87d811fa4a11d5f25cd43e79c161e291c7d3f7aa740fd", size = 1016585, upload-time = "2025-10-10T15:41:13.717Z" },
+ { url = "https://files.pythonhosted.org/packages/10/76/111560775b548f5d8d828c1b5285ff90e2d2745643fb80ecbf115344eea4/fastavro-1.12.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa7ab3769beadcebb60f0539054c7755f63bd9cf7666e2c15e615ab605f89a8", size = 3404629, upload-time = "2025-10-10T15:41:15.642Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/07/6bb93cb963932146c2b6c5c765903a0a547ad9f0f8b769a4a9aad8c06369/fastavro-1.12.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123fb221df3164abd93f2d042c82f538a1d5a43ce41375f12c91ce1355a9141e", size = 3428594, upload-time = "2025-10-10T15:41:17.779Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/67/8115ec36b584197ea737ec79e3499e1f1b640b288d6c6ee295edd13b80f6/fastavro-1.12.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:632a4e3ff223f834ddb746baae0cc7cee1068eb12c32e4d982c2fee8a5b483d0", size = 3344145, upload-time = "2025-10-10T15:41:19.89Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/9e/a7cebb3af967e62539539897c10138fa0821668ec92525d1be88a9cd3ee6/fastavro-1.12.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e6caf4e7a8717d932a3b1ff31595ad169289bbe1128a216be070d3a8391671", size = 3431942, upload-time = "2025-10-10T15:41:22.076Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/d1/7774ddfb8781c5224294c01a593ebce2ad3289b948061c9701bd1903264d/fastavro-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:b91a0fe5a173679a6c02d53ca22dcaad0a2c726b74507e0c1c2e71a7c3f79ef9", size = 450542, upload-time = "2025-10-10T15:41:23.333Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f0/10bd1a3d08667fa0739e2b451fe90e06df575ec8b8ba5d3135c70555c9bd/fastavro-1.12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:509818cb24b98a804fc80be9c5fed90f660310ae3d59382fc811bfa187122167", size = 1009057, upload-time = "2025-10-10T15:41:24.556Z" },
+ { url = "https://files.pythonhosted.org/packages/78/ad/0d985bc99e1fa9e74c636658000ba38a5cd7f5ab2708e9c62eaf736ecf1a/fastavro-1.12.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089e155c0c76e0d418d7e79144ce000524dd345eab3bc1e9c5ae69d500f71b14", size = 3391866, upload-time = "2025-10-10T15:41:26.882Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/9e/b4951dc84ebc34aac69afcbfbb22ea4a91080422ec2bfd2c06076ff1d419/fastavro-1.12.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44cbff7518901c91a82aab476fcab13d102e4999499df219d481b9e15f61af34", size = 3458005, upload-time = "2025-10-10T15:41:29.017Z" },
+ { url = "https://files.pythonhosted.org/packages/af/f8/5a8df450a9f55ca8441f22ea0351d8c77809fc121498b6970daaaf667a21/fastavro-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a275e48df0b1701bb764b18a8a21900b24cf882263cb03d35ecdba636bbc830b", size = 3295258, upload-time = "2025-10-10T15:41:31.564Z" },
+ { url = "https://files.pythonhosted.org/packages/99/b2/40f25299111d737e58b85696e91138a66c25b7334f5357e7ac2b0e8966f8/fastavro-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2de72d786eb38be6b16d556b27232b1bf1b2797ea09599507938cdb7a9fe3e7c", size = 3430328, upload-time = "2025-10-10T15:41:33.689Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/07/85157a7c57c5f8b95507d7829b5946561e5ee656ff80e9dd9a757f53ddaf/fastavro-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:9090f0dee63fe022ee9cc5147483366cc4171c821644c22da020d6b48f576b4f", size = 444140, upload-time = "2025-10-10T15:41:34.902Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/57/26d5efef9182392d5ac9f253953c856ccb66e4c549fd3176a1e94efb05c9/fastavro-1.12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:78df838351e4dff9edd10a1c41d1324131ffecbadefb9c297d612ef5363c049a", size = 1000599, upload-time = "2025-10-10T15:41:36.554Z" },
+ { url = "https://files.pythonhosted.org/packages/33/cb/8ab55b21d018178eb126007a56bde14fd01c0afc11d20b5f2624fe01e698/fastavro-1.12.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:780476c23175d2ae457c52f45b9ffa9d504593499a36cd3c1929662bf5b7b14b", size = 3335933, upload-time = "2025-10-10T15:41:39.07Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/03/9c94ec9bf873eb1ffb0aa694f4e71940154e6e9728ddfdc46046d7e8ced4/fastavro-1.12.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0714b285160fcd515eb0455540f40dd6dac93bdeacdb03f24e8eac3d8aa51f8d", size = 3402066, upload-time = "2025-10-10T15:41:41.608Z" },
+ { url = "https://files.pythonhosted.org/packages/75/c8/cb472347c5a584ccb8777a649ebb28278fccea39d005fc7df19996f41df8/fastavro-1.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8bc2dcec5843d499f2489bfe0747999108f78c5b29295d877379f1972a3d41a", size = 3240038, upload-time = "2025-10-10T15:41:43.743Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/77/569ce9474c40304b3a09e109494e020462b83e405545b78069ddba5f614e/fastavro-1.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b1921ac35f3d89090a5816b626cf46e67dbecf3f054131f84d56b4e70496f45", size = 3369398, upload-time = "2025-10-10T15:41:45.719Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/1f/9589e35e9ea68035385db7bdbf500d36b8891db474063fb1ccc8215ee37c/fastavro-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:5aa777b8ee595b50aa084104cd70670bf25a7bbb9fd8bb5d07524b0785ee1699", size = 444220, upload-time = "2025-10-10T15:41:47.39Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/d2/78435fe737df94bd8db2234b2100f5453737cffd29adee2504a2b013de84/fastavro-1.12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c3d67c47f177e486640404a56f2f50b165fe892cc343ac3a34673b80cc7f1dd6", size = 1086611, upload-time = "2025-10-10T15:41:48.818Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/be/428f99b10157230ddac77ec8cc167005b29e2bd5cbe228345192bb645f30/fastavro-1.12.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5217f773492bac43dae15ff2931432bce2d7a80be7039685a78d3fab7df910bd", size = 3541001, upload-time = "2025-10-10T15:41:50.871Z" },
+ { url = "https://files.pythonhosted.org/packages/16/08/a2eea4f20b85897740efe44887e1ac08f30dfa4bfc3de8962bdcbb21a5a1/fastavro-1.12.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:469fecb25cba07f2e1bfa4c8d008477cd6b5b34a59d48715e1b1a73f6160097d", size = 3432217, upload-time = "2025-10-10T15:41:53.149Z" },
+ { url = "https://files.pythonhosted.org/packages/87/bb/b4c620b9eb6e9838c7f7e4b7be0762834443adf9daeb252a214e9ad3178c/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d71c8aa841ef65cfab709a22bb887955f42934bced3ddb571e98fdbdade4c609", size = 3366742, upload-time = "2025-10-10T15:41:55.237Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/d1/e69534ccdd5368350646fea7d93be39e5f77c614cca825c990bd9ca58f67/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b81fc04e85dfccf7c028e0580c606e33aa8472370b767ef058aae2c674a90746", size = 3383743, upload-time = "2025-10-10T15:41:57.68Z" },
+ { url = "https://files.pythonhosted.org/packages/58/54/b7b4a0c3fb5fcba38128542da1b26c4e6d69933c923f493548bdfd63ab6a/fastavro-1.12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9445da127751ba65975d8e4bdabf36bfcfdad70fc35b2d988e3950cce0ec0e7c", size = 1001377, upload-time = "2025-10-10T15:41:59.241Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/4f/0e589089c7df0d8f57d7e5293fdc34efec9a3b758a0d4d0c99a7937e2492/fastavro-1.12.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed924233272719b5d5a6a0b4d80ef3345fc7e84fc7a382b6232192a9112d38a6", size = 3320401, upload-time = "2025-10-10T15:42:01.682Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/19/260110d56194ae29d7e423a336fccea8bcd103196d00f0b364b732bdb84e/fastavro-1.12.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3616e2f0e1c9265e92954fa099db79c6e7817356d3ff34f4bcc92699ae99697c", size = 3350894, upload-time = "2025-10-10T15:42:04.073Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/96/58b0411e8be9694d5972bee3167d6c1fd1fdfdf7ce253c1a19a327208f4f/fastavro-1.12.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cb0337b42fd3c047fcf0e9b7597bd6ad25868de719f29da81eabb6343f08d399", size = 3229644, upload-time = "2025-10-10T15:42:06.221Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/db/38660660eac82c30471d9101f45b3acfdcbadfe42d8f7cdb129459a45050/fastavro-1.12.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:64961ab15b74b7c168717bbece5660e0f3d457837c3cc9d9145181d011199fa7", size = 3329704, upload-time = "2025-10-10T15:42:08.384Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/a9/1672910f458ecb30b596c9e59e41b7c00309b602a0494341451e92e62747/fastavro-1.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:792356d320f6e757e89f7ac9c22f481e546c886454a6709247f43c0dd7058004", size = 452911, upload-time = "2025-10-10T15:42:09.795Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/8d/2e15d0938ded1891b33eff252e8500605508b799c2e57188a933f0bd744c/fastavro-1.12.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120aaf82ac19d60a1016afe410935fe94728752d9c2d684e267e5b7f0e70f6d9", size = 3541999, upload-time = "2025-10-10T15:42:11.794Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/1c/6dfd082a205be4510543221b734b1191299e6a1810c452b6bc76dfa6968e/fastavro-1.12.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6a3462934b20a74f9ece1daa49c2e4e749bd9a35fa2657b53bf62898fba80f5", size = 3433972, upload-time = "2025-10-10T15:42:14.485Z" },
+ { url = "https://files.pythonhosted.org/packages/24/90/9de694625a1a4b727b1ad0958d220cab25a9b6cf7f16a5c7faa9ea7b2261/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1f81011d54dd47b12437b51dd93a70a9aa17b61307abf26542fc3c13efbc6c51", size = 3368752, upload-time = "2025-10-10T15:42:16.618Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/93/b44f67589e4d439913dab6720f7e3507b0fa8b8e56d06f6fc875ced26afb/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43ded16b3f4a9f1a42f5970c2aa618acb23ea59c4fcaa06680bdf470b255e5a8", size = 3386636, upload-time = "2025-10-10T15:42:18.974Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/b8/67f5682cd59cb59ec6eecb5479b132caaf69709d1e1ceda4f92d8c69d7f1/fastavro-1.12.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:02281432dcb11c78b3280da996eff61ee0eff39c5de06c6e0fbf19275093e6d4", size = 1002311, upload-time = "2025-10-10T15:42:20.415Z" },
+ { url = "https://files.pythonhosted.org/packages/93/38/2f15a7ad24e4b6e0239016c068f142358732bf8ead0315ee926b88634bec/fastavro-1.12.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4128978b930aaf930332db4b3acc290783183f3be06a241ae4a482f3ed8ce892", size = 3195871, upload-time = "2025-10-10T15:42:22.675Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/e5/6fc0250b3006b1b42c1ab9f0511ccd44e1aeb15a63a77fc780ee97f58797/fastavro-1.12.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:546ffffda6610fca672f0ed41149808e106d8272bb246aa7539fa8bb6f117f17", size = 3204127, upload-time = "2025-10-10T15:42:24.855Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/43/1f3909eb096eb1066e416f0875abe783b73fabe823ad616f6956b3e80e84/fastavro-1.12.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7d840ccd9aacada3ddc80fbcc4ea079b658107fe62e9d289a0de9d54e95d366", size = 3135604, upload-time = "2025-10-10T15:42:28.899Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/61/ec083a3a5d7c2b97d0e2c9e137a6e667583afe884af0e49318f7ee7eaa5a/fastavro-1.12.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3100ad643e7fa658469a2a2db229981c1a000ff16b8037c0b58ce3ec4d2107e8", size = 3210932, upload-time = "2025-10-10T15:42:30.891Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/ba/b814fb09b32c8f3059451c33bb11d55eb23188e6a499f5dde628d13bc076/fastavro-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:a38607444281619eda3a9c1be9f5397634012d1b237142eee1540e810b30ac8b", size = 510328, upload-time = "2025-10-10T15:42:32.415Z" },
+]
+
+[[package]]
+name = "filelock"
+version = "3.19.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" },
+]
+
+[[package]]
+name = "filelock"
+version = "3.20.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c1/e0/a75dbe4bca1e7d41307323dad5ea2efdd95408f74ab2de8bd7dba9b51a1a/filelock-3.20.2.tar.gz", hash = "sha256:a2241ff4ddde2a7cebddf78e39832509cb045d18ec1a09d7248d6bfc6bfbbe64", size = 19510, upload-time = "2026-01-02T15:33:32.582Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl", hash = "sha256:fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8", size = 16697, upload-time = "2026-01-02T15:33:31.133Z" },
+]
+
+[[package]]
+name = "flask"
+version = "3.1.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "blinker" },
+ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "itsdangerous" },
+ { name = "jinja2" },
+ { name = "markupsafe" },
+ { name = "werkzeug" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" },
+]
+
+[[package]]
+name = "fsspec"
+version = "2025.10.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/24/7f/2747c0d332b9acfa75dc84447a066fdf812b5a6b8d30472b74d309bfe8cb/fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59", size = 309285, upload-time = "2025-10-30T14:58:44.036Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966, upload-time = "2025-10-30T14:58:42.53Z" },
+]
+
+[[package]]
+name = "fsspec"
+version = "2025.12.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b6/27/954057b0d1f53f086f681755207dda6de6c660ce133c829158e8e8fe7895/fsspec-2025.12.0.tar.gz", hash = "sha256:c505de011584597b1060ff778bb664c1bc022e87921b0e4f10cc9c44f9635973", size = 309748, upload-time = "2025-12-03T15:23:42.687Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/c7/b64cae5dba3a1b138d7123ec36bb5ccd39d39939f18454407e5468f4763f/fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b", size = 201422, upload-time = "2025-12-03T15:23:41.434Z" },
+]
+
+[[package]]
+name = "ghp-import"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "python-dateutil" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" },
+]
+
+[[package]]
+name = "gitdb"
+version = "4.0.12"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "smmap" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" },
+]
+
+[[package]]
+name = "gitpython"
+version = "3.1.45"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "gitdb" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" },
+]
+
+[[package]]
+name = "google-auth"
+version = "2.47.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyasn1-modules" },
+ { name = "rsa" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/60/3c/ec64b9a275ca22fa1cd3b6e77fefcf837b0732c890aa32d2bd21313d9b33/google_auth-2.47.0.tar.gz", hash = "sha256:833229070a9dfee1a353ae9877dcd2dec069a8281a4e72e72f77d4a70ff945da", size = 323719, upload-time = "2026-01-06T21:55:31.045Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/db/18/79e9008530b79527e0d5f79e7eef08d3b179b7f851cfd3a2f27822fbdfa9/google_auth-2.47.0-py3-none-any.whl", hash = "sha256:c516d68336bfde7cf0da26aab674a36fedcf04b37ac4edd59c597178760c3498", size = 234867, upload-time = "2026-01-06T21:55:28.6Z" },
+]
+
+[package.optional-dependencies]
+requests = [
+ { name = "requests", marker = "python_full_version >= '3.10'" },
+]
+
+[[package]]
+name = "google-genai"
+version = "1.47.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "anyio", marker = "python_full_version < '3.10'" },
+ { name = "google-auth", marker = "python_full_version < '3.10'" },
+ { name = "httpx", marker = "python_full_version < '3.10'" },
+ { name = "pydantic", marker = "python_full_version < '3.10'" },
+ { name = "requests", marker = "python_full_version < '3.10'" },
+ { name = "tenacity", marker = "python_full_version < '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+ { name = "websockets", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9f/97/784fba9bc6c41263ff90cb9063eadfdd755dde79cfa5a8d0e397b067dcf9/google_genai-1.47.0.tar.gz", hash = "sha256:ecece00d0a04e6739ea76cc8dad82ec9593d9380aaabef078990e60574e5bf59", size = 241471, upload-time = "2025-10-29T22:01:02.88Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/89/ef/e080e8d67c270ea320956bb911a9359664fc46d3b87d1f029decd33e5c4c/google_genai-1.47.0-py3-none-any.whl", hash = "sha256:e3851237556cbdec96007d8028b4b1f2425cdc5c099a8dc36b72a57e42821b60", size = 241506, upload-time = "2025-10-29T22:01:00.982Z" },
+]
+
+[[package]]
+name = "google-genai"
+version = "1.57.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "anyio", marker = "python_full_version >= '3.10'" },
+ { name = "distro", marker = "python_full_version >= '3.10'" },
+ { name = "google-auth", extra = ["requests"], marker = "python_full_version >= '3.10'" },
+ { name = "httpx", marker = "python_full_version >= '3.10'" },
+ { name = "pydantic", marker = "python_full_version >= '3.10'" },
+ { name = "requests", marker = "python_full_version >= '3.10'" },
+ { name = "sniffio", marker = "python_full_version >= '3.10'" },
+ { name = "tenacity", marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version >= '3.10'" },
+ { name = "websockets", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2b/b4/8251c2d2576224a4b51a8ab6159820f9200b8da28ff555c78ee15607096e/google_genai-1.57.0.tar.gz", hash = "sha256:0ff9c36b8d68abfbdbd13b703ece926de5f3e67955666b36315ecf669b94a826", size = 485648, upload-time = "2026-01-07T20:38:20.271Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d6/02/858bdae08e2184b6afe0b18bc3113318522c9cf326a5a1698055edd31f88/google_genai-1.57.0-py3-none-any.whl", hash = "sha256:d63c7a89a1f549c4d14032f41a0cdb4b6fe3f565e2eee6b5e0907a0aeceabefd", size = 713323, upload-time = "2026-01-07T20:38:18.051Z" },
+]
+
+[[package]]
+name = "graphql-core"
+version = "3.2.7"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ac/9b/037a640a2983b09aed4a823f9cf1729e6d780b0671f854efa4727a7affbe/graphql_core-3.2.7.tar.gz", hash = "sha256:27b6904bdd3b43f2a0556dad5d579bdfdeab1f38e8e8788e555bdcb586a6f62c", size = 513484, upload-time = "2025-11-01T22:30:40.436Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0a/14/933037032608787fb92e365883ad6a741c235e0ff992865ec5d904a38f1e/graphql_core-3.2.7-py3-none-any.whl", hash = "sha256:17fc8f3ca4a42913d8e24d9ac9f08deddf0a0b2483076575757f6c412ead2ec0", size = 207262, upload-time = "2025-11-01T22:30:38.912Z" },
+]
+
+[[package]]
+name = "greenlet"
+version = "3.2.4"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7d/ed/6bfa4109fcb23a58819600392564fea69cdc6551ffd5e69ccf1d52a40cbc/greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c", size = 271061, upload-time = "2025-08-07T13:17:15.373Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/fc/102ec1a2fc015b3a7652abab7acf3541d58c04d3d17a8d3d6a44adae1eb1/greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590", size = 629475, upload-time = "2025-08-07T13:42:54.009Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/26/80383131d55a4ac0fb08d71660fd77e7660b9db6bdb4e8884f46d9f2cc04/greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c", size = 640802, upload-time = "2025-08-07T13:45:25.52Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/7c/e7833dbcd8f376f3326bd728c845d31dcde4c84268d3921afcae77d90d08/greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b", size = 636703, upload-time = "2025-08-07T13:53:12.622Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/49/547b93b7c0428ede7b3f309bc965986874759f7d89e4e04aeddbc9699acb/greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31", size = 635417, upload-time = "2025-08-07T13:18:25.189Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/91/ae2eb6b7979e2f9b035a9f612cf70f1bf54aad4e1d125129bef1eae96f19/greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d", size = 584358, upload-time = "2025-08-07T13:18:23.708Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/85/433de0c9c0252b22b16d413c9407e6cb3b41df7389afc366ca204dbc1393/greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", size = 1113550, upload-time = "2025-08-07T13:42:37.467Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/8d/88f3ebd2bc96bf7747093696f4335a0a8a4c5acfcf1b757717c0d2474ba3/greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f", size = 1137126, upload-time = "2025-08-07T13:18:20.239Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/29/74242b7d72385e29bcc5563fba67dad94943d7cd03552bac320d597f29b2/greenlet-3.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7", size = 1544904, upload-time = "2025-11-04T12:42:04.763Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/e2/1572b8eeab0f77df5f6729d6ab6b141e4a84ee8eb9bc8c1e7918f94eda6d/greenlet-3.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8", size = 1611228, upload-time = "2025-11-04T12:42:08.423Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/6f/b60b0291d9623c496638c582297ead61f43c4b72eef5e9c926ef4565ec13/greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", size = 298654, upload-time = "2025-08-07T13:50:00.469Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" },
+ { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" },
+ { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" },
+ { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" },
+ { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" },
+ { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" },
+ { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" },
+ { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" },
+ { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" },
+ { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" },
+ { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" },
+ { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" },
+ { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" },
+ { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" },
+ { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" },
+ { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" },
+ { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" },
+ { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/c0/93885c4106d2626bf51fdec377d6aef740dfa5c4877461889a7cf8e565cc/greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c", size = 269859, upload-time = "2025-08-07T13:16:16.003Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/f5/33f05dc3ba10a02dedb1485870cf81c109227d3d3aa280f0e48486cac248/greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d", size = 627610, upload-time = "2025-08-07T13:43:01.345Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/a7/9476decef51a0844195f99ed5dc611d212e9b3515512ecdf7321543a7225/greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58", size = 639417, upload-time = "2025-08-07T13:45:32.094Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/e0/849b9159cbb176f8c0af5caaff1faffdece7a8417fcc6fe1869770e33e21/greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4", size = 634751, upload-time = "2025-08-07T13:53:18.848Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/d3/844e714a9bbd39034144dca8b658dcd01839b72bb0ec7d8014e33e3705f0/greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433", size = 634020, upload-time = "2025-08-07T13:18:36.841Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/4c/f3de2a8de0e840ecb0253ad0dc7e2bb3747348e798ec7e397d783a3cb380/greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df", size = 582817, upload-time = "2025-08-07T13:18:35.48Z" },
+ { url = "https://files.pythonhosted.org/packages/89/80/7332915adc766035c8980b161c2e5d50b2f941f453af232c164cff5e0aeb/greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594", size = 1111985, upload-time = "2025-08-07T13:42:42.425Z" },
+ { url = "https://files.pythonhosted.org/packages/66/71/1928e2c80197353bcb9b50aa19c4d8e26ee6d7a900c564907665cf4b9a41/greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98", size = 1136137, upload-time = "2025-08-07T13:18:26.168Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/bf/7bd33643e48ed45dcc0e22572f650767832bd4e1287f97434943cc402148/greenlet-3.2.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:28a3c6b7cd72a96f61b0e4b2a36f681025b60ae4779cc73c1535eb5f29560b10", size = 1542941, upload-time = "2025-11-04T12:42:27.427Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/74/4bc433f91d0d09a1c22954a371f9df928cb85e72640870158853a83415e5/greenlet-3.2.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:52206cd642670b0b320a1fd1cbfd95bca0e043179c1d8a045f2c6109dfe973be", size = 1609685, upload-time = "2025-11-04T12:42:29.242Z" },
+ { url = "https://files.pythonhosted.org/packages/89/48/a5dc74dde38aeb2b15d418cec76ed50e1dd3d620ccda84d8199703248968/greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b", size = 281400, upload-time = "2025-08-07T14:02:20.263Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/44/342c4591db50db1076b8bda86ed0ad59240e3e1da17806a4cf10a6d0e447/greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb", size = 298533, upload-time = "2025-08-07T13:56:34.168Z" },
+]
+
+[[package]]
+name = "greenlet"
+version = "3.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/6a/33d1702184d94106d3cdd7bfb788e19723206fce152e303473ca3b946c7b/greenlet-3.3.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f8496d434d5cb2dce025773ba5597f71f5410ae499d5dd9533e0653258cdb3d", size = 273658, upload-time = "2025-12-04T14:23:37.494Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/b7/2b5805bbf1907c26e434f4e448cd8b696a0b71725204fa21a211ff0c04a7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b96dc7eef78fd404e022e165ec55327f935b9b52ff355b067eb4a0267fc1cffb", size = 574810, upload-time = "2025-12-04T14:50:04.154Z" },
+ { url = "https://files.pythonhosted.org/packages/94/38/343242ec12eddf3d8458c73f555c084359883d4ddc674240d9e61ec51fd6/greenlet-3.3.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73631cd5cccbcfe63e3f9492aaa664d278fda0ce5c3d43aeda8e77317e38efbd", size = 586248, upload-time = "2025-12-04T14:57:39.35Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/d0/0ae86792fb212e4384041e0ef8e7bc66f59a54912ce407d26a966ed2914d/greenlet-3.3.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b299a0cb979f5d7197442dccc3aee67fce53500cd88951b7e6c35575701c980b", size = 597403, upload-time = "2025-12-04T15:07:10.831Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/a8/15d0aa26c0036a15d2659175af00954aaaa5d0d66ba538345bd88013b4d7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dee147740789a4632cace364816046e43310b59ff8fb79833ab043aefa72fd5", size = 586910, upload-time = "2025-12-04T14:25:59.705Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/9b/68d5e3b7ccaba3907e5532cf8b9bf16f9ef5056a008f195a367db0ff32db/greenlet-3.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:39b28e339fc3c348427560494e28d8a6f3561c8d2bcf7d706e1c624ed8d822b9", size = 1547206, upload-time = "2025-12-04T15:04:21.027Z" },
+ { url = "https://files.pythonhosted.org/packages/66/bd/e3086ccedc61e49f91e2cfb5ffad9d8d62e5dc85e512a6200f096875b60c/greenlet-3.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3c374782c2935cc63b2a27ba8708471de4ad1abaa862ffdb1ef45a643ddbb7d", size = 1613359, upload-time = "2025-12-04T14:27:26.548Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/6b/d4e73f5dfa888364bbf02efa85616c6714ae7c631c201349782e5b428925/greenlet-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b49e7ed51876b459bd645d83db257f0180e345d3f768a35a85437a24d5a49082", size = 300740, upload-time = "2025-12-04T14:47:52.773Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" },
+ { url = "https://files.pythonhosted.org/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" },
+ { url = "https://files.pythonhosted.org/packages/80/d7/db0a5085035d05134f8c089643da2b44cc9b80647c39e93129c5ef170d8f/greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45", size = 601098, upload-time = "2025-12-04T15:07:11.898Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" },
+ { url = "https://files.pythonhosted.org/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/d5/c339b3b4bc8198b7caa4f2bd9fd685ac9f29795816d8db112da3d04175bb/greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71", size = 301164, upload-time = "2025-12-04T14:42:51.577Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" },
+ { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" },
+ { url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" },
+ { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" },
+ { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" },
+ { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" },
+ { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" },
+ { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" },
+ { url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" },
+ { url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload-time = "2025-12-04T15:07:15.789Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" },
+ { url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload-time = "2025-12-04T14:26:51.063Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" },
+ { url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" },
+ { url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload-time = "2025-12-04T15:07:16.906Z" },
+ { url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" },
+]
+
+[[package]]
+name = "griffe"
+version = "1.14.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" },
+]
+
+[[package]]
+name = "griffe"
+version = "1.15.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" },
+]
+
+[[package]]
+name = "griffe-typingdoc"
+version = "0.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/be/77/d5e5fa0a8391bc2890ae45255847197299739833108dd76ee3c9b2ff0bba/griffe_typingdoc-0.3.0.tar.gz", hash = "sha256:59d9ef98d02caa7aed88d8df1119c9e48c02ed049ea50ce4018ace9331d20f8b", size = 33169, upload-time = "2025-10-23T12:01:39.037Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/48/af/aa32c13f753e2625ec895b1f56eee3c9380a2088a88a2c028955e223856e/griffe_typingdoc-0.3.0-py3-none-any.whl", hash = "sha256:4f6483fff7733a679d1dce142fb029f314125f3caaf0d620eb82e7390c8564bb", size = 9923, upload-time = "2025-10-23T12:01:37.601Z" },
+]
+
+[[package]]
+name = "griffe-warnings-deprecated"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7e/0e/f034e1714eb2c694d6196c75f77a02f9c69d19f9961c4804a016397bf3e5/griffe_warnings_deprecated-1.1.0.tar.gz", hash = "sha256:7bf21de327d59c66c7ce08d0166aa4292ce0577ff113de5878f428d102b6f7c5", size = 33260, upload-time = "2024-12-10T21:02:18.395Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/43/4c/b7241f03ad1f22ec2eed33b0f90c4f8c949e3395c4b7488670b07225a20b/griffe_warnings_deprecated-1.1.0-py3-none-any.whl", hash = "sha256:e7b0e8bfd6e5add3945d4d9805b2a41c72409e456733965be276d55f01e8a7a2", size = 5854, upload-time = "2024-12-10T21:02:16.96Z" },
+]
+
+[[package]]
+name = "groq"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "distro" },
+ { name = "httpx" },
+ { name = "pydantic" },
+ { name = "sniffio" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3f/12/f4099a141677fcd2ed79dcc1fcec431e60c52e0e90c9c5d935f0ffaf8c0e/groq-1.0.0.tar.gz", hash = "sha256:66cb7bb729e6eb644daac7ce8efe945e99e4eb33657f733ee6f13059ef0c25a9", size = 146068, upload-time = "2025-12-17T23:34:23.115Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4a/88/3175759d2ef30406ea721f4d837bfa1ba4339fde3b81ba8c5640a96ed231/groq-1.0.0-py3-none-any.whl", hash = "sha256:6e22bf92ffad988f01d2d4df7729add66b8fd5dbfb2154b5bbf3af245b72c731", size = 138292, upload-time = "2025-12-17T23:34:21.957Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+]
+
+[[package]]
+name = "hf-xet"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/a5/85ef910a0aa034a2abcfadc360ab5ac6f6bc4e9112349bd40ca97551cff0/hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649", size = 2861870, upload-time = "2025-10-24T19:04:11.422Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/40/e2e0a7eb9a51fe8828ba2d47fe22a7e74914ea8a0db68a18c3aa7449c767/hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b70218dd548e9840224df5638fdc94bd033552963cfa97f9170829381179c813", size = 2717584, upload-time = "2025-10-24T19:04:09.586Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/7d/daf7f8bc4594fdd59a8a596f9e3886133fdc68e675292218a5e4c1b7e834/hf_xet-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d40b18769bb9a8bc82a9ede575ce1a44c75eb80e7375a01d76259089529b5dc", size = 3315004, upload-time = "2025-10-24T19:04:00.314Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/ba/45ea2f605fbf6d81c8b21e4d970b168b18a53515923010c312c06cd83164/hf_xet-1.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd3a6027d59cfb60177c12d6424e31f4b5ff13d8e3a1247b3a584bf8977e6df5", size = 3222636, upload-time = "2025-10-24T19:03:58.111Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/1d/04513e3cab8f29ab8c109d309ddd21a2705afab9d52f2ba1151e0c14f086/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6de1fc44f58f6dd937956c8d304d8c2dea264c80680bcfa61ca4a15e7b76780f", size = 3408448, upload-time = "2025-10-24T19:04:20.951Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7c/60a2756d7feec7387db3a1176c632357632fbe7849fce576c5559d4520c7/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f182f264ed2acd566c514e45da9f2119110e48a87a327ca271027904c70c5832", size = 3503401, upload-time = "2025-10-24T19:04:22.549Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/64/48fffbd67fb418ab07451e4ce641a70de1c40c10a13e25325e24858ebe5a/hf_xet-1.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:293a7a3787e5c95d7be1857358a9130694a9c6021de3f27fa233f37267174382", size = 2900866, upload-time = "2025-10-24T19:04:33.461Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/51/f7e2caae42f80af886db414d4e9885fac959330509089f97cccb339c6b87/hf_xet-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:10bfab528b968c70e062607f663e21e34e2bba349e8038db546646875495179e", size = 2861861, upload-time = "2025-10-24T19:04:19.01Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/1d/a641a88b69994f9371bd347f1dd35e5d1e2e2460a2e350c8d5165fc62005/hf_xet-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a212e842647b02eb6a911187dc878e79c4aa0aa397e88dd3b26761676e8c1f8", size = 2717699, upload-time = "2025-10-24T19:04:17.306Z" },
+ { url = "https://files.pythonhosted.org/packages/df/e0/e5e9bba7d15f0318955f7ec3f4af13f92e773fbb368c0b8008a5acbcb12f/hf_xet-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30e06daccb3a7d4c065f34fc26c14c74f4653069bb2b194e7f18f17cbe9939c0", size = 3314885, upload-time = "2025-10-24T19:04:07.642Z" },
+ { url = "https://files.pythonhosted.org/packages/21/90/b7fe5ff6f2b7b8cbdf1bd56145f863c90a5807d9758a549bf3d916aa4dec/hf_xet-1.2.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:29c8fc913a529ec0a91867ce3d119ac1aac966e098cf49501800c870328cc090", size = 3221550, upload-time = "2025-10-24T19:04:05.55Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/cb/73f276f0a7ce46cc6a6ec7d6c7d61cbfe5f2e107123d9bbd0193c355f106/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e159cbfcfbb29f920db2c09ed8b660eb894640d284f102ada929b6e3dc410a", size = 3408010, upload-time = "2025-10-24T19:04:28.598Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/1e/d642a12caa78171f4be64f7cd9c40e3ca5279d055d0873188a58c0f5fbb9/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c91d5ae931510107f148874e9e2de8a16052b6f1b3ca3c1b12f15ccb491390f", size = 3503264, upload-time = "2025-10-24T19:04:30.397Z" },
+ { url = "https://files.pythonhosted.org/packages/17/b5/33764714923fa1ff922770f7ed18c2daae034d21ae6e10dbf4347c854154/hf_xet-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:210d577732b519ac6ede149d2f2f34049d44e8622bf14eb3d63bbcd2d4b332dc", size = 2901071, upload-time = "2025-10-24T19:04:37.463Z" },
+ { url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" },
+ { url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c", size = 3229054, upload-time = "2025-10-24T19:04:01.949Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737", size = 3413812, upload-time = "2025-10-24T19:04:24.585Z" },
+ { url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865", size = 3508920, upload-time = "2025-10-24T19:04:26.927Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" },
+]
+
+[[package]]
+name = "hjson"
+version = "3.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/82/e5/0b56d723a76ca67abadbf7fb71609fb0ea7e6926e94fcca6c65a85b36a0e/hjson-3.1.0.tar.gz", hash = "sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75", size = 40541, upload-time = "2022-08-13T02:53:01.919Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1f/7f/13cd798d180af4bf4c0ceddeefba2b864a63c71645abc0308b768d67bb81/hjson-3.1.0-py3-none-any.whl", hash = "sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89", size = 54018, upload-time = "2022-08-13T02:52:59.899Z" },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+]
+
+[[package]]
+name = "httptools"
+version = "0.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/e5/c07e0bcf4ec8db8164e9f6738c048b2e66aabf30e7506f440c4cc6953f60/httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:11d01b0ff1fe02c4c32d60af61a4d613b74fad069e47e06e9067758c01e9ac78", size = 204531, upload-time = "2025-10-10T03:54:20.887Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/4f/35e3a63f863a659f92ffd92bef131f3e81cf849af26e6435b49bd9f6f751/httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d86c1e5afdc479a6fdabf570be0d3eb791df0ae727e8dbc0259ed1249998d4", size = 109408, upload-time = "2025-10-10T03:54:22.455Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/71/b0a9193641d9e2471ac541d3b1b869538a5fb6419d52fd2669fa9c79e4b8/httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8c751014e13d88d2be5f5f14fc8b89612fcfa92a9cc480f2bc1598357a23a05", size = 440889, upload-time = "2025-10-10T03:54:23.753Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/d9/2e34811397b76718750fea44658cb0205b84566e895192115252e008b152/httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654968cb6b6c77e37b832a9be3d3ecabb243bbe7a0b8f65fbc5b6b04c8fcabed", size = 440460, upload-time = "2025-10-10T03:54:25.313Z" },
+ { url = "https://files.pythonhosted.org/packages/01/3f/a04626ebeacc489866bb4d82362c0657b2262bef381d68310134be7f40bb/httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b580968316348b474b020edf3988eecd5d6eec4634ee6561e72ae3a2a0e00a8a", size = 425267, upload-time = "2025-10-10T03:54:26.81Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/99/adcd4f66614db627b587627c8ad6f4c55f18881549bab10ecf180562e7b9/httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d496e2f5245319da9d764296e86c5bb6fcf0cf7a8806d3d000717a889c8c0b7b", size = 424429, upload-time = "2025-10-10T03:54:28.174Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/72/ec8fc904a8fd30ba022dfa85f3bbc64c3c7cd75b669e24242c0658e22f3c/httptools-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cbf8317bfccf0fed3b5680c559d3459cccf1abe9039bfa159e62e391c7270568", size = 86173, upload-time = "2025-10-10T03:54:29.5Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/08/17e07e8d89ab8f343c134616d72eebfe03798835058e2ab579dcc8353c06/httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657", size = 206521, upload-time = "2025-10-10T03:54:31.002Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/06/c9c1b41ff52f16aee526fd10fbda99fa4787938aa776858ddc4a1ea825ec/httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70", size = 110375, upload-time = "2025-10-10T03:54:31.941Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/cc/10935db22fda0ee34c76f047590ca0a8bd9de531406a3ccb10a90e12ea21/httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df", size = 456621, upload-time = "2025-10-10T03:54:33.176Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/84/875382b10d271b0c11aa5d414b44f92f8dd53e9b658aec338a79164fa548/httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e", size = 454954, upload-time = "2025-10-10T03:54:34.226Z" },
+ { url = "https://files.pythonhosted.org/packages/30/e1/44f89b280f7e46c0b1b2ccee5737d46b3bb13136383958f20b580a821ca0/httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274", size = 440175, upload-time = "2025-10-10T03:54:35.942Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/7e/b9287763159e700e335028bc1824359dc736fa9b829dacedace91a39b37e/httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec", size = 440310, upload-time = "2025-10-10T03:54:37.1Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/07/5b614f592868e07f5c94b1f301b5e14a21df4e8076215a3bccb830a687d8/httptools-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb", size = 86875, upload-time = "2025-10-10T03:54:38.421Z" },
+ { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" },
+ { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" },
+ { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" },
+ { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" },
+ { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" },
+ { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" },
+ { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" },
+ { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" },
+ { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" },
+ { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" },
+ { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" },
+ { url = "https://files.pythonhosted.org/packages/90/de/b1fe0e8890f0292c266117d4cd268186758a9c34e576fbd573fdf3beacff/httptools-0.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac50afa68945df63ec7a2707c506bd02239272288add34539a2ef527254626a4", size = 206454, upload-time = "2025-10-10T03:55:01.528Z" },
+ { url = "https://files.pythonhosted.org/packages/57/a7/a675c90b49e550c7635ce209c01bc61daa5b08aef17da27ef4e0e78fcf3f/httptools-0.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de987bb4e7ac95b99b805b99e0aae0ad51ae61df4263459d36e07cf4052d8b3a", size = 110260, upload-time = "2025-10-10T03:55:02.418Z" },
+ { url = "https://files.pythonhosted.org/packages/03/44/fb5ef8136e6e97f7b020e97e40c03a999f97e68574d4998fa52b0a62b01b/httptools-0.7.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d169162803a24425eb5e4d51d79cbf429fd7a491b9e570a55f495ea55b26f0bf", size = 441524, upload-time = "2025-10-10T03:55:03.292Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/62/8496a5425341867796d7e2419695f74a74607054e227bbaeabec8323e87f/httptools-0.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49794f9250188a57fa73c706b46cb21a313edb00d337ca4ce1a011fe3c760b28", size = 440877, upload-time = "2025-10-10T03:55:04.282Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/f1/26c2e5214106bf6ed04d03e518ff28ca0c6b5390c5da7b12bbf94b40ae43/httptools-0.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aeefa0648362bb97a7d6b5ff770bfb774930a327d7f65f8208394856862de517", size = 425775, upload-time = "2025-10-10T03:55:05.341Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/34/7500a19257139725281f7939a7d1aa3701cf1ac4601a1690f9ab6f510e15/httptools-0.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0d92b10dbf0b3da4823cde6a96d18e6ae358a9daa741c71448975f6a2c339cad", size = 425001, upload-time = "2025-10-10T03:55:06.389Z" },
+ { url = "https://files.pythonhosted.org/packages/71/04/31a7949d645ebf33a67f56a0024109444a52a271735e0647a210264f3e61/httptools-0.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:5ddbd045cfcb073db2449563dd479057f2c2b681ebc232380e63ef15edc9c023", size = 86818, upload-time = "2025-10-10T03:55:07.316Z" },
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "certifi" },
+ { name = "httpcore" },
+ { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
+]
+
+[[package]]
+name = "httpx-sse"
+version = "0.4.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" },
+]
+
+[[package]]
+name = "huggingface-hub"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "filelock", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "fsspec", version = "2025.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "fsspec", version = "2025.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
+ { name = "httpx" },
+ { name = "packaging" },
+ { name = "pyyaml" },
+ { name = "shellingham" },
+ { name = "tqdm" },
+ { name = "typer-slim" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c1/c9/d42b5cfa0a50b77cf9165e13edfaf2e3bd4e0def9cb67b6b8a07224a52ab/huggingface_hub-1.3.0.tar.gz", hash = "sha256:289e2a3586fdf01e35882944eaa06fbd57436de24b6e653d1fab248584acd66b", size = 622092, upload-time = "2026-01-09T09:54:44.663Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b1/5b/c5fde1f56b1f072b3028ec5413f3f5bf472c5891ebb34589cddb1689609f/huggingface_hub-1.3.0-py3-none-any.whl", hash = "sha256:763f450169bb05ea3867990e9d3ba9464eb617b874791301dc81be2c6ffb0bf5", size = 533092, upload-time = "2026-01-09T09:54:43.228Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "8.7.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "zipp" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "inline-snapshot"
+version = "0.31.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "asttokens" },
+ { name = "executing" },
+ { name = "pytest" },
+ { name = "rich" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1c/b1/52b5ee59f73ed31d5fe21b10881bf2d121d07d54b23c0b6b74186792e620/inline_snapshot-0.31.1.tar.gz", hash = "sha256:4ea5ed70aa1d652713bbfd750606b94bd8a42483f7d3680433b3e92994495f64", size = 2606338, upload-time = "2025-11-07T07:36:18.932Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ba/52/945db420380efbda8c69a7a4a16c53df9d7ac50d8217286b9d41e5d825ff/inline_snapshot-0.31.1-py3-none-any.whl", hash = "sha256:7875a73c986a03388c7e758fb5cb8a43d2c3a20328aa1d851bfb4ed536c4496f", size = 71965, upload-time = "2025-11-07T07:36:16.836Z" },
+]
+
+[[package]]
+name = "invoke"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/de/bd/b461d3424a24c80490313fd77feeb666ca4f6a28c7e72713e3d9095719b4/invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707", size = 304762, upload-time = "2025-10-11T00:36:35.172Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/4b/b99e37f88336009971405cbb7630610322ed6fbfa31e1d7ab3fbf3049a2d/invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8", size = 160287, upload-time = "2025-10-11T00:36:33.703Z" },
+]
+
+[[package]]
+name = "itsdangerous"
+version = "2.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
+]
+
+[[package]]
+name = "jieba"
+version = "0.42.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c6/cb/18eeb235f833b726522d7ebed54f2278ce28ba9438e3135ab0278d9792a2/jieba-0.42.1.tar.gz", hash = "sha256:055ca12f62674fafed09427f176506079bc135638a14e23e25be909131928db2", size = 19214172, upload-time = "2020-01-20T14:27:23.5Z" }
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+]
+
+[[package]]
+name = "jiter"
+version = "0.12.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/45/9d/e0660989c1370e25848bb4c52d061c71837239738ad937e83edca174c273/jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b", size = 168294, upload-time = "2025-11-09T20:49:23.302Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3b/91/13cb9505f7be74a933f37da3af22e029f6ba64f5669416cb8b2774bc9682/jiter-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e7acbaba9703d5de82a2c98ae6a0f59ab9770ab5af5fa35e43a303aee962cf65", size = 316652, upload-time = "2025-11-09T20:46:41.021Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/76/4e9185e5d9bb4e482cf6dec6410d5f78dfeb374cfcecbbe9888d07c52daa/jiter-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:364f1a7294c91281260364222f535bc427f56d4de1d8ffd718162d21fbbd602e", size = 319829, upload-time = "2025-11-09T20:46:43.281Z" },
+ { url = "https://files.pythonhosted.org/packages/86/af/727de50995d3a153138139f259baae2379d8cb0522c0c00419957bc478a6/jiter-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ee4d25805d4fb23f0a5167a962ef8e002dbfb29c0989378488e32cf2744b62", size = 350568, upload-time = "2025-11-09T20:46:45.075Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/c1/d6e9f4b7a3d5ac63bcbdfddeb50b2dcfbdc512c86cffc008584fdc350233/jiter-0.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:796f466b7942107eb889c08433b6e31b9a7ed31daceaecf8af1be26fb26c0ca8", size = 369052, upload-time = "2025-11-09T20:46:46.818Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/be/00824cd530f30ed73fa8a4f9f3890a705519e31ccb9e929f1e22062e7c76/jiter-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35506cb71f47dba416694e67af996bbdefb8e3608f1f78799c2e1f9058b01ceb", size = 481585, upload-time = "2025-11-09T20:46:48.319Z" },
+ { url = "https://files.pythonhosted.org/packages/74/b6/2ad7990dff9504d4b5052eef64aa9574bd03d722dc7edced97aad0d47be7/jiter-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:726c764a90c9218ec9e4f99a33d6bf5ec169163f2ca0fc21b654e88c2abc0abc", size = 380541, upload-time = "2025-11-09T20:46:49.643Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/c7/f3c26ecbc1adbf1db0d6bba99192143d8fe8504729d9594542ecc4445784/jiter-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa47810c5565274810b726b0dc86d18dce5fd17b190ebdc3890851d7b2a0e74", size = 364423, upload-time = "2025-11-09T20:46:51.731Z" },
+ { url = "https://files.pythonhosted.org/packages/18/51/eac547bf3a2d7f7e556927278e14c56a0604b8cddae75815d5739f65f81d/jiter-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ec0259d3f26c62aed4d73b198c53e316ae11f0f69c8fbe6682c6dcfa0fcce2", size = 389958, upload-time = "2025-11-09T20:46:53.432Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/1f/9ca592e67175f2db156cff035e0d817d6004e293ee0c1d73692d38fcb596/jiter-0.12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:79307d74ea83465b0152fa23e5e297149506435535282f979f18b9033c0bb025", size = 522084, upload-time = "2025-11-09T20:46:54.848Z" },
+ { url = "https://files.pythonhosted.org/packages/83/ff/597d9cdc3028f28224f53e1a9d063628e28b7a5601433e3196edda578cdd/jiter-0.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cf6e6dd18927121fec86739f1a8906944703941d000f0639f3eb6281cc601dca", size = 513054, upload-time = "2025-11-09T20:46:56.487Z" },
+ { url = "https://files.pythonhosted.org/packages/24/6d/1970bce1351bd02e3afcc5f49e4f7ef3dabd7fb688f42be7e8091a5b809a/jiter-0.12.0-cp310-cp310-win32.whl", hash = "sha256:b6ae2aec8217327d872cbfb2c1694489057b9433afce447955763e6ab015b4c4", size = 206368, upload-time = "2025-11-09T20:46:58.638Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/6b/eb1eb505b2d86709b59ec06681a2b14a94d0941db091f044b9f0e16badc0/jiter-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:c7f49ce90a71e44f7e1aa9e7ec415b9686bbc6a5961e57eab511015e6759bc11", size = 204847, upload-time = "2025-11-09T20:47:00.295Z" },
+ { url = "https://files.pythonhosted.org/packages/32/f9/eaca4633486b527ebe7e681c431f529b63fe2709e7c5242fc0f43f77ce63/jiter-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8f8a7e317190b2c2d60eb2e8aa835270b008139562d70fe732e1c0020ec53c9", size = 316435, upload-time = "2025-11-09T20:47:02.087Z" },
+ { url = "https://files.pythonhosted.org/packages/10/c1/40c9f7c22f5e6ff715f28113ebaba27ab85f9af2660ad6e1dd6425d14c19/jiter-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2218228a077e784c6c8f1a8e5d6b8cb1dea62ce25811c356364848554b2056cd", size = 320548, upload-time = "2025-11-09T20:47:03.409Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/1b/efbb68fe87e7711b00d2cfd1f26bb4bfc25a10539aefeaa7727329ffb9cb/jiter-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9354ccaa2982bf2188fd5f57f79f800ef622ec67beb8329903abf6b10da7d423", size = 351915, upload-time = "2025-11-09T20:47:05.171Z" },
+ { url = "https://files.pythonhosted.org/packages/15/2d/c06e659888c128ad1e838123d0638f0efad90cc30860cb5f74dd3f2fc0b3/jiter-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2607185ea89b4af9a604d4c7ec40e45d3ad03ee66998b031134bc510232bb7", size = 368966, upload-time = "2025-11-09T20:47:06.508Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/20/058db4ae5fb07cf6a4ab2e9b9294416f606d8e467fb74c2184b2a1eeacba/jiter-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a585a5e42d25f2e71db5f10b171f5e5ea641d3aa44f7df745aa965606111cc2", size = 482047, upload-time = "2025-11-09T20:47:08.382Z" },
+ { url = "https://files.pythonhosted.org/packages/49/bb/dc2b1c122275e1de2eb12905015d61e8316b2f888bdaac34221c301495d6/jiter-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd9e21d34edff5a663c631f850edcb786719c960ce887a5661e9c828a53a95d9", size = 380835, upload-time = "2025-11-09T20:47:09.81Z" },
+ { url = "https://files.pythonhosted.org/packages/23/7d/38f9cd337575349de16da575ee57ddb2d5a64d425c9367f5ef9e4612e32e/jiter-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a612534770470686cd5431478dc5a1b660eceb410abade6b1b74e320ca98de6", size = 364587, upload-time = "2025-11-09T20:47:11.529Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/a3/b13e8e61e70f0bb06085099c4e2462647f53cc2ca97614f7fedcaa2bb9f3/jiter-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3985aea37d40a908f887b34d05111e0aae822943796ebf8338877fee2ab67725", size = 390492, upload-time = "2025-11-09T20:47:12.993Z" },
+ { url = "https://files.pythonhosted.org/packages/07/71/e0d11422ed027e21422f7bc1883c61deba2d9752b720538430c1deadfbca/jiter-0.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b1207af186495f48f72529f8d86671903c8c10127cac6381b11dddc4aaa52df6", size = 522046, upload-time = "2025-11-09T20:47:14.6Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/59/b968a9aa7102a8375dbbdfbd2aeebe563c7e5dddf0f47c9ef1588a97e224/jiter-0.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef2fb241de583934c9915a33120ecc06d94aa3381a134570f59eed784e87001e", size = 513392, upload-time = "2025-11-09T20:47:16.011Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/e4/7df62002499080dbd61b505c5cb351aa09e9959d176cac2aa8da6f93b13b/jiter-0.12.0-cp311-cp311-win32.whl", hash = "sha256:453b6035672fecce8007465896a25b28a6b59cfe8fbc974b2563a92f5a92a67c", size = 206096, upload-time = "2025-11-09T20:47:17.344Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/60/1032b30ae0572196b0de0e87dce3b6c26a1eff71aad5fe43dee3082d32e0/jiter-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca264b9603973c2ad9435c71a8ec8b49f8f715ab5ba421c85a51cde9887e421f", size = 204899, upload-time = "2025-11-09T20:47:19.365Z" },
+ { url = "https://files.pythonhosted.org/packages/49/d5/c145e526fccdb834063fb45c071df78b0cc426bbaf6de38b0781f45d956f/jiter-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:cb00ef392e7d684f2754598c02c409f376ddcef857aae796d559e6cacc2d78a5", size = 188070, upload-time = "2025-11-09T20:47:20.75Z" },
+ { url = "https://files.pythonhosted.org/packages/92/c9/5b9f7b4983f1b542c64e84165075335e8a236fa9e2ea03a0c79780062be8/jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37", size = 314449, upload-time = "2025-11-09T20:47:22.999Z" },
+ { url = "https://files.pythonhosted.org/packages/98/6e/e8efa0e78de00db0aee82c0cf9e8b3f2027efd7f8a71f859d8f4be8e98ef/jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274", size = 319855, upload-time = "2025-11-09T20:47:24.779Z" },
+ { url = "https://files.pythonhosted.org/packages/20/26/894cd88e60b5d58af53bec5c6759d1292bd0b37a8b5f60f07abf7a63ae5f/jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3", size = 350171, upload-time = "2025-11-09T20:47:26.469Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/27/a7b818b9979ac31b3763d25f3653ec3a954044d5e9f5d87f2f247d679fd1/jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf", size = 365590, upload-time = "2025-11-09T20:47:27.918Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/7e/e46195801a97673a83746170b17984aa8ac4a455746354516d02ca5541b4/jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1", size = 479462, upload-time = "2025-11-09T20:47:29.654Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/75/f833bfb009ab4bd11b1c9406d333e3b4357709ed0570bb48c7c06d78c7dd/jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df", size = 378983, upload-time = "2025-11-09T20:47:31.026Z" },
+ { url = "https://files.pythonhosted.org/packages/71/b3/7a69d77943cc837d30165643db753471aff5df39692d598da880a6e51c24/jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403", size = 361328, upload-time = "2025-11-09T20:47:33.286Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/ac/a78f90caf48d65ba70d8c6efc6f23150bc39dc3389d65bbec2a95c7bc628/jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126", size = 386740, upload-time = "2025-11-09T20:47:34.703Z" },
+ { url = "https://files.pythonhosted.org/packages/39/b6/5d31c2cc8e1b6a6bcf3c5721e4ca0a3633d1ab4754b09bc7084f6c4f5327/jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9", size = 520875, upload-time = "2025-11-09T20:47:36.058Z" },
+ { url = "https://files.pythonhosted.org/packages/30/b5/4df540fae4e9f68c54b8dab004bd8c943a752f0b00efd6e7d64aa3850339/jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86", size = 511457, upload-time = "2025-11-09T20:47:37.932Z" },
+ { url = "https://files.pythonhosted.org/packages/07/65/86b74010e450a1a77b2c1aabb91d4a91dd3cd5afce99f34d75fd1ac64b19/jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44", size = 204546, upload-time = "2025-11-09T20:47:40.47Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/c7/6659f537f9562d963488e3e55573498a442503ced01f7e169e96a6110383/jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb", size = 205196, upload-time = "2025-11-09T20:47:41.794Z" },
+ { url = "https://files.pythonhosted.org/packages/21/f4/935304f5169edadfec7f9c01eacbce4c90bb9a82035ac1de1f3bd2d40be6/jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789", size = 186100, upload-time = "2025-11-09T20:47:43.007Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/a6/97209693b177716e22576ee1161674d1d58029eb178e01866a0422b69224/jiter-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6cc49d5130a14b732e0612bc76ae8db3b49898732223ef8b7599aa8d9810683e", size = 313658, upload-time = "2025-11-09T20:47:44.424Z" },
+ { url = "https://files.pythonhosted.org/packages/06/4d/125c5c1537c7d8ee73ad3d530a442d6c619714b95027143f1b61c0b4dfe0/jiter-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37f27a32ce36364d2fa4f7fdc507279db604d27d239ea2e044c8f148410defe1", size = 318605, upload-time = "2025-11-09T20:47:45.973Z" },
+ { url = "https://files.pythonhosted.org/packages/99/bf/a840b89847885064c41a5f52de6e312e91fa84a520848ee56c97e4fa0205/jiter-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc0944aa3d4b4773e348cda635252824a78f4ba44328e042ef1ff3f6080d1cf", size = 349803, upload-time = "2025-11-09T20:47:47.535Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/88/e63441c28e0db50e305ae23e19c1d8fae012d78ed55365da392c1f34b09c/jiter-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da25c62d4ee1ffbacb97fac6dfe4dcd6759ebdc9015991e92a6eae5816287f44", size = 365120, upload-time = "2025-11-09T20:47:49.284Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/7c/49b02714af4343970eb8aca63396bc1c82fa01197dbb1e9b0d274b550d4e/jiter-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:048485c654b838140b007390b8182ba9774621103bd4d77c9c3f6f117474ba45", size = 479918, upload-time = "2025-11-09T20:47:50.807Z" },
+ { url = "https://files.pythonhosted.org/packages/69/ba/0a809817fdd5a1db80490b9150645f3aae16afad166960bcd562be194f3b/jiter-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:635e737fbb7315bef0037c19b88b799143d2d7d3507e61a76751025226b3ac87", size = 379008, upload-time = "2025-11-09T20:47:52.211Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/c3/c9fc0232e736c8877d9e6d83d6eeb0ba4e90c6c073835cc2e8f73fdeef51/jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e017c417b1ebda911bd13b1e40612704b1f5420e30695112efdbed8a4b389ed", size = 361785, upload-time = "2025-11-09T20:47:53.512Z" },
+ { url = "https://files.pythonhosted.org/packages/96/61/61f69b7e442e97ca6cd53086ddc1cf59fb830549bc72c0a293713a60c525/jiter-0.12.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89b0bfb8b2bf2351fba36bb211ef8bfceba73ef58e7f0c68fb67b5a2795ca2f9", size = 386108, upload-time = "2025-11-09T20:47:54.893Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/2e/76bb3332f28550c8f1eba3bf6e5efe211efda0ddbbaf24976bc7078d42a5/jiter-0.12.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f5aa5427a629a824a543672778c9ce0c5e556550d1569bb6ea28a85015287626", size = 519937, upload-time = "2025-11-09T20:47:56.253Z" },
+ { url = "https://files.pythonhosted.org/packages/84/d6/fa96efa87dc8bff2094fb947f51f66368fa56d8d4fc9e77b25d7fbb23375/jiter-0.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed53b3d6acbcb0fd0b90f20c7cb3b24c357fe82a3518934d4edfa8c6898e498c", size = 510853, upload-time = "2025-11-09T20:47:58.32Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/28/93f67fdb4d5904a708119a6ab58a8f1ec226ff10a94a282e0215402a8462/jiter-0.12.0-cp313-cp313-win32.whl", hash = "sha256:4747de73d6b8c78f2e253a2787930f4fffc68da7fa319739f57437f95963c4de", size = 204699, upload-time = "2025-11-09T20:47:59.686Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/1f/30b0eb087045a0abe2a5c9c0c0c8da110875a1d3be83afd4a9a4e548be3c/jiter-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:e25012eb0c456fcc13354255d0338cd5397cce26c77b2832b3c4e2e255ea5d9a", size = 204258, upload-time = "2025-11-09T20:48:01.01Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/f4/2b4daf99b96bce6fc47971890b14b2a36aef88d7beb9f057fafa032c6141/jiter-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:c97b92c54fe6110138c872add030a1f99aea2401ddcdaa21edf74705a646dd60", size = 185503, upload-time = "2025-11-09T20:48:02.35Z" },
+ { url = "https://files.pythonhosted.org/packages/39/ca/67bb15a7061d6fe20b9b2a2fd783e296a1e0f93468252c093481a2f00efa/jiter-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53839b35a38f56b8be26a7851a48b89bc47e5d88e900929df10ed93b95fea3d6", size = 317965, upload-time = "2025-11-09T20:48:03.783Z" },
+ { url = "https://files.pythonhosted.org/packages/18/af/1788031cd22e29c3b14bc6ca80b16a39a0b10e611367ffd480c06a259831/jiter-0.12.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f669548e55c91ab47fef8bddd9c954dab1938644e715ea49d7e117015110a4", size = 345831, upload-time = "2025-11-09T20:48:05.55Z" },
+ { url = "https://files.pythonhosted.org/packages/05/17/710bf8472d1dff0d3caf4ced6031060091c1320f84ee7d5dcbed1f352417/jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb", size = 361272, upload-time = "2025-11-09T20:48:06.951Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/f1/1dcc4618b59761fef92d10bcbb0b038b5160be653b003651566a185f1a5c/jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7", size = 204604, upload-time = "2025-11-09T20:48:08.328Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/32/63cb1d9f1c5c6632a783c0052cde9ef7ba82688f7065e2f0d5f10a7e3edb/jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3", size = 185628, upload-time = "2025-11-09T20:48:09.572Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/99/45c9f0dbe4a1416b2b9a8a6d1236459540f43d7fb8883cff769a8db0612d/jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525", size = 312478, upload-time = "2025-11-09T20:48:10.898Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/a7/54ae75613ba9e0f55fcb0bc5d1f807823b5167cc944e9333ff322e9f07dd/jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49", size = 318706, upload-time = "2025-11-09T20:48:12.266Z" },
+ { url = "https://files.pythonhosted.org/packages/59/31/2aa241ad2c10774baf6c37f8b8e1f39c07db358f1329f4eb40eba179c2a2/jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1", size = 351894, upload-time = "2025-11-09T20:48:13.673Z" },
+ { url = "https://files.pythonhosted.org/packages/54/4f/0f2759522719133a9042781b18cc94e335b6d290f5e2d3e6899d6af933e3/jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e", size = 365714, upload-time = "2025-11-09T20:48:15.083Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/6f/806b895f476582c62a2f52c453151edd8a0fde5411b0497baaa41018e878/jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e", size = 478989, upload-time = "2025-11-09T20:48:16.706Z" },
+ { url = "https://files.pythonhosted.org/packages/86/6c/012d894dc6e1033acd8db2b8346add33e413ec1c7c002598915278a37f79/jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff", size = 378615, upload-time = "2025-11-09T20:48:18.614Z" },
+ { url = "https://files.pythonhosted.org/packages/87/30/d718d599f6700163e28e2c71c0bbaf6dace692e7df2592fd793ac9276717/jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a", size = 364745, upload-time = "2025-11-09T20:48:20.117Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/85/315b45ce4b6ddc7d7fceca24068543b02bdc8782942f4ee49d652e2cc89f/jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a", size = 386502, upload-time = "2025-11-09T20:48:21.543Z" },
+ { url = "https://files.pythonhosted.org/packages/74/0b/ce0434fb40c5b24b368fe81b17074d2840748b4952256bab451b72290a49/jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67", size = 519845, upload-time = "2025-11-09T20:48:22.964Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/a3/7a7a4488ba052767846b9c916d208b3ed114e3eb670ee984e4c565b9cf0d/jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b", size = 510701, upload-time = "2025-11-09T20:48:24.483Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/16/052ffbf9d0467b70af24e30f91e0579e13ded0c17bb4a8eb2aed3cb60131/jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42", size = 205029, upload-time = "2025-11-09T20:48:25.749Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/18/3cf1f3f0ccc789f76b9a754bdb7a6977e5d1d671ee97a9e14f7eb728d80e/jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf", size = 204960, upload-time = "2025-11-09T20:48:27.415Z" },
+ { url = "https://files.pythonhosted.org/packages/02/68/736821e52ecfdeeb0f024b8ab01b5a229f6b9293bbdb444c27efade50b0f/jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451", size = 185529, upload-time = "2025-11-09T20:48:29.125Z" },
+ { url = "https://files.pythonhosted.org/packages/30/61/12ed8ee7a643cce29ac97c2281f9ce3956eb76b037e88d290f4ed0d41480/jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7", size = 318974, upload-time = "2025-11-09T20:48:30.87Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/c6/f3041ede6d0ed5e0e79ff0de4c8f14f401bbf196f2ef3971cdbe5fd08d1d/jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684", size = 345932, upload-time = "2025-11-09T20:48:32.658Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/5d/4d94835889edd01ad0e2dbfc05f7bdfaed46292e7b504a6ac7839aa00edb/jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c", size = 367243, upload-time = "2025-11-09T20:48:34.093Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/76/0051b0ac2816253a99d27baf3dda198663aff882fa6ea7deeb94046da24e/jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d", size = 479315, upload-time = "2025-11-09T20:48:35.507Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ae/83f793acd68e5cb24e483f44f482a1a15601848b9b6f199dacb970098f77/jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993", size = 380714, upload-time = "2025-11-09T20:48:40.014Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/4808a88338ad2c228b1126b93fcd8ba145e919e886fe910d578230dabe3b/jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f", size = 365168, upload-time = "2025-11-09T20:48:41.462Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/d4/04619a9e8095b42aef436b5aeb4c0282b4ff1b27d1db1508df9f5dc82750/jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783", size = 387893, upload-time = "2025-11-09T20:48:42.921Z" },
+ { url = "https://files.pythonhosted.org/packages/17/ea/d3c7e62e4546fdc39197fa4a4315a563a89b95b6d54c0d25373842a59cbe/jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b", size = 520828, upload-time = "2025-11-09T20:48:44.278Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/0b/c6d3562a03fd767e31cb119d9041ea7958c3c80cb3d753eafb19b3b18349/jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6", size = 511009, upload-time = "2025-11-09T20:48:45.726Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/51/2cb4468b3448a8385ebcd15059d325c9ce67df4e2758d133ab9442b19834/jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183", size = 205110, upload-time = "2025-11-09T20:48:47.033Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/c5/ae5ec83dec9c2d1af805fd5fe8f74ebded9c8670c5210ec7820ce0dbeb1e/jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873", size = 205223, upload-time = "2025-11-09T20:48:49.076Z" },
+ { url = "https://files.pythonhosted.org/packages/97/9a/3c5391907277f0e55195550cf3fa8e293ae9ee0c00fb402fec1e38c0c82f/jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473", size = 185564, upload-time = "2025-11-09T20:48:50.376Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/da/3e1fbd1f03f89ff0b4469d481be0b5cf2880c8e7b56fd80303b3ab5ae52d/jiter-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c9d28b218d5f9e5f69a0787a196322a5056540cb378cac8ff542b4fa7219966c", size = 319378, upload-time = "2025-11-09T20:48:51.761Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/4e/e07d69285e9e19a153050a6d281d2f0968600753a8fed8a3a141d6ffc140/jiter-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0ee12028daf8cfcf880dd492349a122a64f42c059b6c62a2b0c96a83a8da820", size = 312195, upload-time = "2025-11-09T20:48:53.547Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/82/1f1cb5231b36af9f3d6d5b6030e70110faf14fd143419fc5fe7d852e691a/jiter-0.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b135ebe757a82d67ed2821526e72d0acf87dd61f6013e20d3c45b8048af927b", size = 352777, upload-time = "2025-11-09T20:48:55.058Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/5e/728393bbbc99b31e8f7a4fdd8fa55e455a0a9648f79097d9088baf1f676f/jiter-0.12.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15d7fafb81af8a9e3039fc305529a61cd933eecee33b4251878a1c89859552a3", size = 370738, upload-time = "2025-11-09T20:48:56.632Z" },
+ { url = "https://files.pythonhosted.org/packages/30/08/ac92f0df7b14ac82f2fe0a382a8000e600ab90af95798d4a7db0c1bd0736/jiter-0.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92d1f41211d8a8fe412faad962d424d334764c01dac6691c44691c2e4d3eedaf", size = 483744, upload-time = "2025-11-09T20:48:58.006Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/f4/dbfa4e759a2b82e969a14c3d0a91b176f1ed94717183a2f495cf94a651b9/jiter-0.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a64a48d7c917b8f32f25c176df8749ecf08cec17c466114727efe7441e17f6d", size = 382888, upload-time = "2025-11-09T20:48:59.471Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/d9/b86fff7f748b0bb54222a8f132ffaf4d1be56b4591fa76d3cfdd701a33e5/jiter-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122046f3b3710b85de99d9aa2f3f0492a8233a2f54a64902b096efc27ea747b5", size = 366465, upload-time = "2025-11-09T20:49:01.408Z" },
+ { url = "https://files.pythonhosted.org/packages/93/3c/1152d8b433317a568927e13c1b125c680e6c058ff5d304833be8469bd4f2/jiter-0.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:27ec39225e03c32c6b863ba879deb427882f243ae46f0d82d68b695fa5b48b40", size = 392603, upload-time = "2025-11-09T20:49:02.784Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/92/ff19d8fb87f3f9438eb7464862c8d0126455bc046b345d59b21443640c62/jiter-0.12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26b9e155ddc132225a39b1995b3b9f0fe0f79a6d5cbbeacf103271e7d309b404", size = 523780, upload-time = "2025-11-09T20:49:04.42Z" },
+ { url = "https://files.pythonhosted.org/packages/87/3a/4260e2d84e4a293c36d2a8e8b8dcd69609c671f3bd310e4625359217c517/jiter-0.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab05b7c58e29bb9e60b70c2e0094c98df79a1e42e397b9bb6eaa989b7a66dd0", size = 514874, upload-time = "2025-11-09T20:49:05.844Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/f7/574d2cb79e86feb035ade18c2254da71d04417555907c9df51dd6b183426/jiter-0.12.0-cp39-cp39-win32.whl", hash = "sha256:59f9f9df87ed499136db1c2b6c9efb902f964bed42a582ab7af413b6a293e7b0", size = 208329, upload-time = "2025-11-09T20:49:07.444Z" },
+ { url = "https://files.pythonhosted.org/packages/05/ce/50725ec39782d8c973f19ae2d7dd3d192d01332c7cbde48c75e16a3e85a9/jiter-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:d3719596a1ebe7a48a498e8d5d0c4bf7553321d4c3eee1d620628d51351a3928", size = 206557, upload-time = "2025-11-09T20:49:08.888Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/54/5339ef1ecaa881c6948669956567a64d2670941925f245c434f494ffb0e5/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8", size = 311144, upload-time = "2025-11-09T20:49:10.503Z" },
+ { url = "https://files.pythonhosted.org/packages/27/74/3446c652bffbd5e81ab354e388b1b5fc1d20daac34ee0ed11ff096b1b01a/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3", size = 305877, upload-time = "2025-11-09T20:49:12.269Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/f4/ed76ef9043450f57aac2d4fbeb27175aa0eb9c38f833be6ef6379b3b9a86/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e", size = 340419, upload-time = "2025-11-09T20:49:13.803Z" },
+ { url = "https://files.pythonhosted.org/packages/21/01/857d4608f5edb0664aa791a3d45702e1a5bcfff9934da74035e7b9803846/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2097de91cf03eaa27b3cbdb969addf83f0179c6afc41bbc4513705e013c65d", size = 347212, upload-time = "2025-11-09T20:49:15.643Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/f5/12efb8ada5f5c9edc1d4555fe383c1fb2eac05ac5859258a72d61981d999/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb", size = 309974, upload-time = "2025-11-09T20:49:17.187Z" },
+ { url = "https://files.pythonhosted.org/packages/85/15/d6eb3b770f6a0d332675141ab3962fd4a7c270ede3515d9f3583e1d28276/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b", size = 304233, upload-time = "2025-11-09T20:49:18.734Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/3e/e7e06743294eea2cf02ced6aa0ff2ad237367394e37a0e2b4a1108c67a36/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f", size = 338537, upload-time = "2025-11-09T20:49:20.317Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/9c/6753e6522b8d0ef07d3a3d239426669e984fb0eba15a315cdbc1253904e4/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c", size = 346110, upload-time = "2025-11-09T20:49:21.817Z" },
+]
+
+[[package]]
+name = "jmespath"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" },
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.26.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs", marker = "python_full_version >= '3.10'" },
+ { name = "jsonschema-specifications", marker = "python_full_version >= '3.10'" },
+ { name = "referencing", marker = "python_full_version >= '3.10'" },
+ { name = "rpds-py", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "referencing", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
+]
+
+[[package]]
+name = "lia-web"
+version = "0.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cross-web", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/05/3d/7d574a7a5cf5fbc5fc09c07ea3696dd400353b7702bc009cf596b8c12035/lia_web-0.3.1.tar.gz", hash = "sha256:7f551269eddd729f1437e9341ad21622a849eb0c0975d9232ccbbaadbdc74c06", size = 2021, upload-time = "2025-12-25T20:41:51.195Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a1/8b/b628fc18658f94b3d094708a18b71083cf47628e85cbc6b9edba54d5b2d7/lia_web-0.3.1-py3-none-any.whl", hash = "sha256:e4e6e7a9381e228aca60a6f3d67dbae9a5f4638eced242d931f95797ddba3f8b", size = 5933, upload-time = "2025-12-25T20:41:52.289Z" },
+]
+
+[[package]]
+name = "logfire-api"
+version = "4.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8a/2a/76d8fbafa881cb03d5ad6e1d67d537e8c308ae7145812b8891f7b8751224/logfire_api-4.17.0.tar.gz", hash = "sha256:4647dad05146a68af441d59a7746a966df4c2581b316616f1210f8cf74931353", size = 58305, upload-time = "2026-01-07T10:52:17.768Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/bc/3844e103dca998dcc195d6ef09e0f29d9000bac870117db1dd59a29bfeef/logfire_api-4.17.0-py3-none-any.whl", hash = "sha256:80a4b79cd9918934cdf2043d944cfb04182708178d846273484d47f3619a5a39", size = 96146, upload-time = "2026-01-07T10:52:15.088Z" },
+]
+
+[[package]]
+name = "markdown"
+version = "3.9"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" },
+]
+
+[[package]]
+name = "markdown"
+version = "3.10"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" },
+]
+
+[[package]]
+name = "markdown-include-variants"
+version = "0.0.8"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c5/47/ec9eae4a6d2f336d95681df43720e2b25b045dc3ed44ae6d30a5ce2f5dff/markdown_include_variants-0.0.8.tar.gz", hash = "sha256:46d812340c64dcd3646b1eaa356bafb31626dd7b4955d15c44ff8c48c6357227", size = 46882, upload-time = "2025-12-12T16:11:04.254Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4e/0e/130958e7ec50d13f2ee7b6c142df5c352520a9251baf1652e41262703857/markdown_include_variants-0.0.8-py3-none-any.whl", hash = "sha256:425a300ae25fbcd598506cba67859a9dfa047333e869e0ff2e11a5e354b326dc", size = 8120, upload-time = "2025-12-12T16:11:02.881Z" },
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "mdurl", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "4.0.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "mdurl", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" },
+ { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" },
+ { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" },
+ { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" },
+ { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" },
+ { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" },
+ { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" },
+ { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" },
+ { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" },
+ { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" },
+ { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" },
+ { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
+ { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
+ { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
+ { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
+ { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
+ { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
+ { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
+ { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
+ { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
+ { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
+ { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
+ { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
+ { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
+ { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
+ { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
+ { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
+ { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
+ { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
+ { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
+ { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" },
+ { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" },
+ { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" },
+]
+
+[[package]]
+name = "mcp"
+version = "1.19.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio", marker = "python_full_version >= '3.10'" },
+ { name = "httpx", marker = "python_full_version >= '3.10'" },
+ { name = "httpx-sse", marker = "python_full_version >= '3.10'" },
+ { name = "jsonschema", marker = "python_full_version >= '3.10'" },
+ { name = "pydantic", marker = "python_full_version >= '3.10'" },
+ { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "python-multipart", version = "0.0.21", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pywin32", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
+ { name = "sse-starlette", marker = "python_full_version >= '3.10'" },
+ { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and sys_platform != 'emscripten'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/69/2b/916852a5668f45d8787378461eaa1244876d77575ffef024483c94c0649c/mcp-1.19.0.tar.gz", hash = "sha256:213de0d3cd63f71bc08ffe9cc8d4409cc87acffd383f6195d2ce0457c021b5c1", size = 444163, upload-time = "2025-10-24T01:11:15.839Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ce/a3/3e71a875a08b6a830b88c40bc413bff01f1650f1efe8a054b5e90a9d4f56/mcp-1.19.0-py3-none-any.whl", hash = "sha256:f5907fe1c0167255f916718f376d05f09a830a215327a3ccdd5ec8a519f2e572", size = 170105, upload-time = "2025-10-24T01:11:14.151Z" },
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
+]
+
+[[package]]
+name = "mdx-include"
+version = "1.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cyclic" },
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "rcslice" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bf/f0/f395a9cf164471d3c7bbe58cbd64d74289575a8b85a962b49a804ab7ed34/mdx_include-1.4.2.tar.gz", hash = "sha256:992f9fbc492b5cf43f7d8cb4b90b52a4e4c5fdd7fd04570290a83eea5c84f297", size = 15051, upload-time = "2022-07-26T05:46:14.129Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c0/40/6844997dee251103c5a4c4eb0d1d2f2162b7c29ffc4e86de3cd68d269be2/mdx_include-1.4.2-py3-none-any.whl", hash = "sha256:cfbeadd59985f27a9b70cb7ab0a3d209892fe1bb1aa342df055e0b135b3c9f34", size = 11591, upload-time = "2022-07-26T05:46:11.518Z" },
+]
+
+[[package]]
+name = "mergedeep"
+version = "1.3.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" },
+]
+
+[[package]]
+name = "mistralai"
+version = "1.9.11"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "eval-type-backport" },
+ { name = "httpx" },
+ { name = "invoke" },
+ { name = "pydantic" },
+ { name = "python-dateutil" },
+ { name = "pyyaml" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5a/8d/d8b7af67a966b6f227024e1cb7287fc19901a434f87a5a391dcfe635d338/mistralai-1.9.11.tar.gz", hash = "sha256:3df9e403c31a756ec79e78df25ee73cea3eb15f86693773e16b16adaf59c9b8a", size = 208051, upload-time = "2025-10-02T15:53:40.473Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fe/76/4ce12563aea5a76016f8643eff30ab731e6656c845e9e4d090ef10c7b925/mistralai-1.9.11-py3-none-any.whl", hash = "sha256:7a3dc2b8ef3fceaa3582220234261b5c4e3e03a972563b07afa150e44a25a6d3", size = 442796, upload-time = "2025-10-02T15:53:39.134Z" },
+]
+
+[[package]]
+name = "mkdocs"
+version = "1.6.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "ghp-import" },
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "jinja2" },
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "markupsafe" },
+ { name = "mergedeep" },
+ { name = "mkdocs-get-deps" },
+ { name = "packaging" },
+ { name = "pathspec" },
+ { name = "pyyaml" },
+ { name = "pyyaml-env-tag" },
+ { name = "watchdog" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" },
+]
+
+[[package]]
+name = "mkdocs-autorefs"
+version = "1.4.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "markupsafe" },
+ { name = "mkdocs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" },
+]
+
+[[package]]
+name = "mkdocs-get-deps"
+version = "0.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "mergedeep" },
+ { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" },
+]
+
+[[package]]
+name = "mkdocs-macros-plugin"
+version = "1.4.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "hjson" },
+ { name = "jinja2" },
+ { name = "mkdocs" },
+ { name = "packaging" },
+ { name = "pathspec" },
+ { name = "python-dateutil" },
+ { name = "pyyaml" },
+ { name = "requests" },
+ { name = "super-collections" },
+ { name = "termcolor", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "termcolor", version = "3.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1b/42/bb2ceed148c77f82b57c6d3b7f584f0f34ababf7a9a8ff85809380d1f400/mkdocs_macros_plugin-1.4.1.tar.gz", hash = "sha256:55a9c93871e3744cdeb0736316783d60830a6d5d97b1132364e6b491607f2332", size = 35094, upload-time = "2025-10-25T12:37:20.689Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/93/ec/e6e96a7ae8df414f03f43681821234b0d3b86666f7b91f70ab26775a8809/mkdocs_macros_plugin-1.4.1-py3-none-any.whl", hash = "sha256:5a9e483f6056fe7ad0923802affe699233ca468672e20a9640dba237165b3240", size = 40155, upload-time = "2025-10-25T12:37:19.417Z" },
+]
+
+[[package]]
+name = "mkdocs-material"
+version = "9.7.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "babel" },
+ { name = "backrefs" },
+ { name = "colorama" },
+ { name = "jinja2" },
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "mkdocs" },
+ { name = "mkdocs-material-extensions" },
+ { name = "paginate" },
+ { name = "pygments" },
+ { name = "pymdown-extensions" },
+ { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9c/3b/111b84cd6ff28d9e955b5f799ef217a17bc1684ac346af333e6100e413cb/mkdocs_material-9.7.0.tar.gz", hash = "sha256:602b359844e906ee402b7ed9640340cf8a474420d02d8891451733b6b02314ec", size = 4094546, upload-time = "2025-11-11T08:49:09.73Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/87/eefe8d5e764f4cf50ed91b943f8e8f96b5efd65489d8303b7a36e2e79834/mkdocs_material-9.7.0-py3-none-any.whl", hash = "sha256:da2866ea53601125ff5baa8aa06404c6e07af3c5ce3d5de95e3b52b80b442887", size = 9283770, upload-time = "2025-11-11T08:49:06.26Z" },
+]
+
+[[package]]
+name = "mkdocs-material-extensions"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" },
+]
+
+[[package]]
+name = "mkdocs-redirects"
+version = "1.2.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mkdocs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f1/a8/6d44a6cf07e969c7420cb36ab287b0669da636a2044de38a7d2208d5a758/mkdocs_redirects-1.2.2.tar.gz", hash = "sha256:3094981b42ffab29313c2c1b8ac3969861109f58b2dd58c45fc81cd44bfa0095", size = 7162, upload-time = "2024-11-07T14:57:21.109Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c4/ec/38443b1f2a3821bbcb24e46cd8ba979154417794d54baf949fefde1c2146/mkdocs_redirects-1.2.2-py3-none-any.whl", hash = "sha256:7dbfa5647b79a3589da4401403d69494bd1f4ad03b9c15136720367e1f340ed5", size = 6142, upload-time = "2024-11-07T14:57:19.143Z" },
+]
+
+[[package]]
+name = "mkdocstrings"
+version = "0.30.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "jinja2" },
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "markupsafe" },
+ { name = "mkdocs" },
+ { name = "mkdocs-autorefs" },
+ { name = "pymdown-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c5/33/2fa3243439f794e685d3e694590d28469a9b8ea733af4b48c250a3ffc9a0/mkdocstrings-0.30.1.tar.gz", hash = "sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f", size = 106350, upload-time = "2025-09-19T10:49:26.446Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/2c/f0dc4e1ee7f618f5bff7e05898d20bf8b6e7fa612038f768bfa295f136a4/mkdocstrings-0.30.1-py3-none-any.whl", hash = "sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82", size = 36704, upload-time = "2025-09-19T10:49:24.805Z" },
+]
+
+[package.optional-dependencies]
+python = [
+ { name = "mkdocstrings-python", version = "1.18.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "mkdocstrings-python", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+
+[[package]]
+name = "mkdocstrings-python"
+version = "1.18.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "mkdocs-autorefs", marker = "python_full_version < '3.10'" },
+ { name = "mkdocstrings", marker = "python_full_version < '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/95/ae/58ab2bfbee2792e92a98b97e872f7c003deb903071f75d8d83aa55db28fa/mkdocstrings_python-1.18.2.tar.gz", hash = "sha256:4ad536920a07b6336f50d4c6d5603316fafb1172c5c882370cbbc954770ad323", size = 207972, upload-time = "2025-08-28T16:11:19.847Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d5/8f/ce008599d9adebf33ed144e7736914385e8537f5fc686fdb7cceb8c22431/mkdocstrings_python-1.18.2-py3-none-any.whl", hash = "sha256:944fe6deb8f08f33fa936d538233c4036e9f53e840994f6146e8e94eb71b600d", size = 138215, upload-time = "2025-08-28T16:11:18.176Z" },
+]
+
+[[package]]
+name = "mkdocstrings-python"
+version = "2.0.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "mkdocs-autorefs", marker = "python_full_version >= '3.10'" },
+ { name = "mkdocstrings", marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/24/75/d30af27a2906f00eb90143470272376d728521997800f5dce5b340ba35bc/mkdocstrings_python-2.0.1.tar.gz", hash = "sha256:843a562221e6a471fefdd4b45cc6c22d2607ccbad632879234fa9692e9cf7732", size = 199345, upload-time = "2025-12-03T14:26:11.755Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/81/06/c5f8deba7d2cbdfa7967a716ae801aa9ca5f734b8f54fd473ef77a088dbe/mkdocstrings_python-2.0.1-py3-none-any.whl", hash = "sha256:66ecff45c5f8b71bf174e11d49afc845c2dfc7fc0ab17a86b6b337e0f24d8d90", size = 105055, upload-time = "2025-12-03T14:26:10.184Z" },
+]
+
+[[package]]
+name = "mypy"
+version = "1.14.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mypy-extensions" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051, upload-time = "2024-12-30T16:39:07.335Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb", size = 11211002, upload-time = "2024-12-30T16:37:22.435Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0", size = 10358400, upload-time = "2024-12-30T16:37:53.526Z" },
+ { url = "https://files.pythonhosted.org/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d", size = 12095172, upload-time = "2024-12-30T16:37:50.332Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b", size = 12828732, upload-time = "2024-12-30T16:37:29.96Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427", size = 13012197, upload-time = "2024-12-30T16:38:05.037Z" },
+ { url = "https://files.pythonhosted.org/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f", size = 9780836, upload-time = "2024-12-30T16:37:19.726Z" },
+ { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432, upload-time = "2024-12-30T16:37:11.533Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515, upload-time = "2024-12-30T16:37:40.724Z" },
+ { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791, upload-time = "2024-12-30T16:36:58.73Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203, upload-time = "2024-12-30T16:37:03.741Z" },
+ { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900, upload-time = "2024-12-30T16:37:57.948Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869, upload-time = "2024-12-30T16:37:33.428Z" },
+ { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668, upload-time = "2024-12-30T16:38:02.211Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060, upload-time = "2024-12-30T16:37:46.131Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167, upload-time = "2024-12-30T16:37:43.534Z" },
+ { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341, upload-time = "2024-12-30T16:37:36.249Z" },
+ { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991, upload-time = "2024-12-30T16:37:06.743Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016, upload-time = "2024-12-30T16:37:15.02Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097, upload-time = "2024-12-30T16:37:25.144Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728, upload-time = "2024-12-30T16:38:08.634Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965, upload-time = "2024-12-30T16:38:12.132Z" },
+ { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660, upload-time = "2024-12-30T16:38:17.342Z" },
+ { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198, upload-time = "2024-12-30T16:38:32.839Z" },
+ { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276, upload-time = "2024-12-30T16:38:20.828Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/1f/186d133ae2514633f8558e78cd658070ba686c0e9275c5a5c24a1e1f0d67/mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35", size = 11200493, upload-time = "2024-12-30T16:38:26.935Z" },
+ { url = "https://files.pythonhosted.org/packages/af/fc/4842485d034e38a4646cccd1369f6b1ccd7bc86989c52770d75d719a9941/mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc", size = 10357702, upload-time = "2024-12-30T16:38:50.623Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/e6/457b83f2d701e23869cfec013a48a12638f75b9d37612a9ddf99072c1051/mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9", size = 12091104, upload-time = "2024-12-30T16:38:53.735Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/bf/76a569158db678fee59f4fd30b8e7a0d75bcbaeef49edd882a0d63af6d66/mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb", size = 12830167, upload-time = "2024-12-30T16:38:56.437Z" },
+ { url = "https://files.pythonhosted.org/packages/43/bc/0bc6b694b3103de9fed61867f1c8bd33336b913d16831431e7cb48ef1c92/mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60", size = 13013834, upload-time = "2024-12-30T16:38:59.204Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/79/5f5ec47849b6df1e6943d5fd8e6632fbfc04b4fd4acfa5a5a9535d11b4e2/mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c", size = 9781231, upload-time = "2024-12-30T16:39:05.124Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905, upload-time = "2024-12-30T16:38:42.021Z" },
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
+]
+
+[[package]]
+name = "openai"
+version = "2.14.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "distro" },
+ { name = "httpx" },
+ { name = "jiter" },
+ { name = "pydantic" },
+ { name = "sniffio" },
+ { name = "tqdm" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d8/b1/12fe1c196bea326261718eb037307c1c1fe1dedc2d2d4de777df822e6238/openai-2.14.0.tar.gz", hash = "sha256:419357bedde9402d23bf8f2ee372fca1985a73348debba94bddff06f19459952", size = 626938, upload-time = "2025-12-19T03:28:45.742Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/27/4b/7c1a00c2c3fbd004253937f7520f692a9650767aa73894d7a34f0d65d3f4/openai-2.14.0-py3-none-any.whl", hash = "sha256:7ea40aca4ffc4c4a776e77679021b47eec1160e341f42ae086ba949c9dcc9183", size = 1067558, upload-time = "2025-12-19T03:28:43.727Z" },
+]
+
+[[package]]
+name = "opentelemetry-api"
+version = "1.39.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "importlib-metadata" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" },
+]
+
+[[package]]
+name = "orjson"
+version = "3.11.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/04/b8/333fdb27840f3bf04022d21b654a35f58e15407183aeb16f3b41aa053446/orjson-3.11.5.tar.gz", hash = "sha256:82393ab47b4fe44ffd0a7659fa9cfaacc717eb617c93cde83795f14af5c2e9d5", size = 5972347, upload-time = "2025-12-06T15:55:39.458Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/19/b22cf9dad4db20c8737041046054cbd4f38bb5a2d0e4bb60487832ce3d76/orjson-3.11.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:df9eadb2a6386d5ea2bfd81309c505e125cfc9ba2b1b99a97e60985b0b3665d1", size = 245719, upload-time = "2025-12-06T15:53:43.877Z" },
+ { url = "https://files.pythonhosted.org/packages/03/2e/b136dd6bf30ef5143fbe76a4c142828b55ccc618be490201e9073ad954a1/orjson-3.11.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc70da619744467d8f1f49a8cadae5ec7bbe054e5232d95f92ed8737f8c5870", size = 132467, upload-time = "2025-12-06T15:53:45.379Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/fc/ae99bfc1e1887d20a0268f0e2686eb5b13d0ea7bbe01de2b566febcd2130/orjson-3.11.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:073aab025294c2f6fc0807201c76fdaed86f8fc4be52c440fb78fbb759a1ac09", size = 130702, upload-time = "2025-12-06T15:53:46.659Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/43/ef7912144097765997170aca59249725c3ab8ef6079f93f9d708dd058df5/orjson-3.11.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:835f26fa24ba0bb8c53ae2a9328d1706135b74ec653ed933869b74b6909e63fd", size = 135907, upload-time = "2025-12-06T15:53:48.487Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/da/24d50e2d7f4092ddd4d784e37a3fa41f22ce8ed97abc9edd222901a96e74/orjson-3.11.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667c132f1f3651c14522a119e4dd631fad98761fa960c55e8e7430bb2a1ba4ac", size = 139935, upload-time = "2025-12-06T15:53:49.88Z" },
+ { url = "https://files.pythonhosted.org/packages/02/4a/b4cb6fcbfff5b95a3a019a8648255a0fac9b221fbf6b6e72be8df2361feb/orjson-3.11.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42e8961196af655bb5e63ce6c60d25e8798cd4dfbc04f4203457fa3869322c2e", size = 137541, upload-time = "2025-12-06T15:53:51.226Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/99/a11bd129f18c2377c27b2846a9d9be04acec981f770d711ba0aaea563984/orjson-3.11.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75412ca06e20904c19170f8a24486c4e6c7887dea591ba18a1ab572f1300ee9f", size = 139031, upload-time = "2025-12-06T15:53:52.309Z" },
+ { url = "https://files.pythonhosted.org/packages/64/29/d7b77d7911574733a036bb3e8ad7053ceb2b7d6ea42208b9dbc55b23b9ed/orjson-3.11.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6af8680328c69e15324b5af3ae38abbfcf9cbec37b5346ebfd52339c3d7e8a18", size = 141622, upload-time = "2025-12-06T15:53:53.606Z" },
+ { url = "https://files.pythonhosted.org/packages/93/41/332db96c1de76b2feda4f453e91c27202cd092835936ce2b70828212f726/orjson-3.11.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a86fe4ff4ea523eac8f4b57fdac319faf037d3c1be12405e6a7e86b3fbc4756a", size = 413800, upload-time = "2025-12-06T15:53:54.866Z" },
+ { url = "https://files.pythonhosted.org/packages/76/e1/5a0d148dd1f89ad2f9651df67835b209ab7fcb1118658cf353425d7563e9/orjson-3.11.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e607b49b1a106ee2086633167033afbd63f76f2999e9236f638b06b112b24ea7", size = 151198, upload-time = "2025-12-06T15:53:56.383Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/96/8db67430d317a01ae5cf7971914f6775affdcfe99f5bff9ef3da32492ecc/orjson-3.11.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7339f41c244d0eea251637727f016b3d20050636695bc78345cce9029b189401", size = 141984, upload-time = "2025-12-06T15:53:57.746Z" },
+ { url = "https://files.pythonhosted.org/packages/71/49/40d21e1aa1ac569e521069228bb29c9b5a350344ccf922a0227d93c2ed44/orjson-3.11.5-cp310-cp310-win32.whl", hash = "sha256:8be318da8413cdbbce77b8c5fac8d13f6eb0f0db41b30bb598631412619572e8", size = 135272, upload-time = "2025-12-06T15:53:59.769Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/7e/d0e31e78be0c100e08be64f48d2850b23bcb4d4c70d114f4e43b39f6895a/orjson-3.11.5-cp310-cp310-win_amd64.whl", hash = "sha256:b9f86d69ae822cabc2a0f6c099b43e8733dda788405cba2665595b7e8dd8d167", size = 133360, upload-time = "2025-12-06T15:54:01.25Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/68/6b3659daec3a81aed5ab47700adb1a577c76a5452d35b91c88efee89987f/orjson-3.11.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9c8494625ad60a923af6b2b0bd74107146efe9b55099e20d7740d995f338fcd8", size = 245318, upload-time = "2025-12-06T15:54:02.355Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/00/92db122261425f61803ccf0830699ea5567439d966cbc35856fe711bfe6b/orjson-3.11.5-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:7bb2ce0b82bc9fd1168a513ddae7a857994b780b2945a8c51db4ab1c4b751ebc", size = 129491, upload-time = "2025-12-06T15:54:03.877Z" },
+ { url = "https://files.pythonhosted.org/packages/94/4f/ffdcb18356518809d944e1e1f77589845c278a1ebbb5a8297dfefcc4b4cb/orjson-3.11.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67394d3becd50b954c4ecd24ac90b5051ee7c903d167459f93e77fc6f5b4c968", size = 132167, upload-time = "2025-12-06T15:54:04.944Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c6/0a8caff96f4503f4f7dd44e40e90f4d14acf80d3b7a97cb88747bb712d3e/orjson-3.11.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:298d2451f375e5f17b897794bcc3e7b821c0f32b4788b9bcae47ada24d7f3cf7", size = 130516, upload-time = "2025-12-06T15:54:06.274Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/63/43d4dc9bd9954bff7052f700fdb501067f6fb134a003ddcea2a0bb3854ed/orjson-3.11.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa5e4244063db8e1d87e0f54c3f7522f14b2dc937e65d5241ef0076a096409fd", size = 135695, upload-time = "2025-12-06T15:54:07.702Z" },
+ { url = "https://files.pythonhosted.org/packages/87/6f/27e2e76d110919cb7fcb72b26166ee676480a701bcf8fc53ac5d0edce32f/orjson-3.11.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1db2088b490761976c1b2e956d5d4e6409f3732e9d79cfa69f876c5248d1baf9", size = 139664, upload-time = "2025-12-06T15:54:08.828Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/f8/5966153a5f1be49b5fbb8ca619a529fde7bc71aa0a376f2bb83fed248bcd/orjson-3.11.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2ed66358f32c24e10ceea518e16eb3549e34f33a9d51f99ce23b0251776a1ef", size = 137289, upload-time = "2025-12-06T15:54:09.898Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/34/8acb12ff0299385c8bbcbb19fbe40030f23f15a6de57a9c587ebf71483fb/orjson-3.11.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2021afda46c1ed64d74b555065dbd4c2558d510d8cec5ea6a53001b3e5e82a9", size = 138784, upload-time = "2025-12-06T15:54:11.022Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/27/910421ea6e34a527f73d8f4ee7bdffa48357ff79c7b8d6eb6f7b82dd1176/orjson-3.11.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b42ffbed9128e547a1647a3e50bc88ab28ae9daa61713962e0d3dd35e820c125", size = 141322, upload-time = "2025-12-06T15:54:12.427Z" },
+ { url = "https://files.pythonhosted.org/packages/87/a3/4b703edd1a05555d4bb1753d6ce44e1a05b7a6d7c164d5b332c795c63d70/orjson-3.11.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8d5f16195bb671a5dd3d1dbea758918bada8f6cc27de72bd64adfbd748770814", size = 413612, upload-time = "2025-12-06T15:54:13.858Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/36/034177f11d7eeea16d3d2c42a1883b0373978e08bc9dad387f5074c786d8/orjson-3.11.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c0e5d9f7a0227df2927d343a6e3859bebf9208b427c79bd31949abcc2fa32fa5", size = 150993, upload-time = "2025-12-06T15:54:15.189Z" },
+ { url = "https://files.pythonhosted.org/packages/44/2f/ea8b24ee046a50a7d141c0227c4496b1180b215e728e3b640684f0ea448d/orjson-3.11.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23d04c4543e78f724c4dfe656b3791b5f98e4c9253e13b2636f1af5d90e4a880", size = 141774, upload-time = "2025-12-06T15:54:16.451Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/12/cc440554bf8200eb23348a5744a575a342497b65261cd65ef3b28332510a/orjson-3.11.5-cp311-cp311-win32.whl", hash = "sha256:c404603df4865f8e0afe981aa3c4b62b406e6d06049564d58934860b62b7f91d", size = 135109, upload-time = "2025-12-06T15:54:17.73Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/83/e0c5aa06ba73a6760134b169f11fb970caa1525fa4461f94d76e692299d9/orjson-3.11.5-cp311-cp311-win_amd64.whl", hash = "sha256:9645ef655735a74da4990c24ffbd6894828fbfa117bc97c1edd98c282ecb52e1", size = 133193, upload-time = "2025-12-06T15:54:19.426Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/35/5b77eaebc60d735e832c5b1a20b155667645d123f09d471db0a78280fb49/orjson-3.11.5-cp311-cp311-win_arm64.whl", hash = "sha256:1cbf2735722623fcdee8e712cbaaab9e372bbcb0c7924ad711b261c2eccf4a5c", size = 126830, upload-time = "2025-12-06T15:54:20.836Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/a4/8052a029029b096a78955eadd68ab594ce2197e24ec50e6b6d2ab3f4e33b/orjson-3.11.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:334e5b4bff9ad101237c2d799d9fd45737752929753bf4faf4b207335a416b7d", size = 245347, upload-time = "2025-12-06T15:54:22.061Z" },
+ { url = "https://files.pythonhosted.org/packages/64/67/574a7732bd9d9d79ac620c8790b4cfe0717a3d5a6eb2b539e6e8995e24a0/orjson-3.11.5-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:ff770589960a86eae279f5d8aa536196ebda8273a2a07db2a54e82b93bc86626", size = 129435, upload-time = "2025-12-06T15:54:23.615Z" },
+ { url = "https://files.pythonhosted.org/packages/52/8d/544e77d7a29d90cf4d9eecd0ae801c688e7f3d1adfa2ebae5e1e94d38ab9/orjson-3.11.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed24250e55efbcb0b35bed7caaec8cedf858ab2f9f2201f17b8938c618c8ca6f", size = 132074, upload-time = "2025-12-06T15:54:24.694Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/57/b9f5b5b6fbff9c26f77e785baf56ae8460ef74acdb3eae4931c25b8f5ba9/orjson-3.11.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a66d7769e98a08a12a139049aac2f0ca3adae989817f8c43337455fbc7669b85", size = 130520, upload-time = "2025-12-06T15:54:26.185Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/6d/d34970bf9eb33f9ec7c979a262cad86076814859e54eb9a059a52f6dc13d/orjson-3.11.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:86cfc555bfd5794d24c6a1903e558b50644e5e68e6471d66502ce5cb5fdef3f9", size = 136209, upload-time = "2025-12-06T15:54:27.264Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/39/bc373b63cc0e117a105ea12e57280f83ae52fdee426890d57412432d63b3/orjson-3.11.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a230065027bc2a025e944f9d4714976a81e7ecfa940923283bca7bbc1f10f626", size = 139837, upload-time = "2025-12-06T15:54:28.75Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/aa/7c4818c8d7d324da220f4f1af55c343956003aa4d1ce1857bdc1d396ba69/orjson-3.11.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b29d36b60e606df01959c4b982729c8845c69d1963f88686608be9ced96dbfaa", size = 137307, upload-time = "2025-12-06T15:54:29.856Z" },
+ { url = "https://files.pythonhosted.org/packages/46/bf/0993b5a056759ba65145effe3a79dd5a939d4a070eaa5da2ee3180fbb13f/orjson-3.11.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74099c6b230d4261fdc3169d50efc09abf38ace1a42ea2f9994b1d79153d477", size = 139020, upload-time = "2025-12-06T15:54:31.024Z" },
+ { url = "https://files.pythonhosted.org/packages/65/e8/83a6c95db3039e504eda60fc388f9faedbb4f6472f5aba7084e06552d9aa/orjson-3.11.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e697d06ad57dd0c7a737771d470eedc18e68dfdefcdd3b7de7f33dfda5b6212e", size = 141099, upload-time = "2025-12-06T15:54:32.196Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/b4/24fdc024abfce31c2f6812973b0a693688037ece5dc64b7a60c1ce69e2f2/orjson-3.11.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e08ca8a6c851e95aaecc32bc44a5aa75d0ad26af8cdac7c77e4ed93acf3d5b69", size = 413540, upload-time = "2025-12-06T15:54:33.361Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/37/01c0ec95d55ed0c11e4cae3e10427e479bba40c77312b63e1f9665e0737d/orjson-3.11.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e8b5f96c05fce7d0218df3fdfeb962d6b8cfff7e3e20264306b46dd8b217c0f3", size = 151530, upload-time = "2025-12-06T15:54:34.6Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/d4/f9ebc57182705bb4bbe63f5bbe14af43722a2533135e1d2fb7affa0c355d/orjson-3.11.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ddbfdb5099b3e6ba6d6ea818f61997bb66de14b411357d24c4612cf1ebad08ca", size = 141863, upload-time = "2025-12-06T15:54:35.801Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/04/02102b8d19fdcb009d72d622bb5781e8f3fae1646bf3e18c53d1bc8115b5/orjson-3.11.5-cp312-cp312-win32.whl", hash = "sha256:9172578c4eb09dbfcf1657d43198de59b6cef4054de385365060ed50c458ac98", size = 135255, upload-time = "2025-12-06T15:54:37.209Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/fb/f05646c43d5450492cb387de5549f6de90a71001682c17882d9f66476af5/orjson-3.11.5-cp312-cp312-win_amd64.whl", hash = "sha256:2b91126e7b470ff2e75746f6f6ee32b9ab67b7a93c8ba1d15d3a0caaf16ec875", size = 133252, upload-time = "2025-12-06T15:54:38.401Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/a6/7b8c0b26ba18c793533ac1cd145e131e46fcf43952aa94c109b5b913c1f0/orjson-3.11.5-cp312-cp312-win_arm64.whl", hash = "sha256:acbc5fac7e06777555b0722b8ad5f574739e99ffe99467ed63da98f97f9ca0fe", size = 126777, upload-time = "2025-12-06T15:54:39.515Z" },
+ { url = "https://files.pythonhosted.org/packages/10/43/61a77040ce59f1569edf38f0b9faadc90c8cf7e9bec2e0df51d0132c6bb7/orjson-3.11.5-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3b01799262081a4c47c035dd77c1301d40f568f77cc7ec1bb7db5d63b0a01629", size = 245271, upload-time = "2025-12-06T15:54:40.878Z" },
+ { url = "https://files.pythonhosted.org/packages/55/f9/0f79be617388227866d50edd2fd320cb8fb94dc1501184bb1620981a0aba/orjson-3.11.5-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:61de247948108484779f57a9f406e4c84d636fa5a59e411e6352484985e8a7c3", size = 129422, upload-time = "2025-12-06T15:54:42.403Z" },
+ { url = "https://files.pythonhosted.org/packages/77/42/f1bf1549b432d4a78bfa95735b79b5dac75b65b5bb815bba86ad406ead0a/orjson-3.11.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:894aea2e63d4f24a7f04a1908307c738d0dce992e9249e744b8f4e8dd9197f39", size = 132060, upload-time = "2025-12-06T15:54:43.531Z" },
+ { url = "https://files.pythonhosted.org/packages/25/49/825aa6b929f1a6ed244c78acd7b22c1481fd7e5fda047dc8bf4c1a807eb6/orjson-3.11.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ddc21521598dbe369d83d4d40338e23d4101dad21dae0e79fa20465dbace019f", size = 130391, upload-time = "2025-12-06T15:54:45.059Z" },
+ { url = "https://files.pythonhosted.org/packages/42/ec/de55391858b49e16e1aa8f0bbbb7e5997b7345d8e984a2dec3746d13065b/orjson-3.11.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cce16ae2f5fb2c53c3eafdd1706cb7b6530a67cc1c17abe8ec747f5cd7c0c51", size = 135964, upload-time = "2025-12-06T15:54:46.576Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/40/820bc63121d2d28818556a2d0a09384a9f0262407cf9fa305e091a8048df/orjson-3.11.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e46c762d9f0e1cfb4ccc8515de7f349abbc95b59cb5a2bd68df5973fdef913f8", size = 139817, upload-time = "2025-12-06T15:54:48.084Z" },
+ { url = "https://files.pythonhosted.org/packages/09/c7/3a445ca9a84a0d59d26365fd8898ff52bdfcdcb825bcc6519830371d2364/orjson-3.11.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7345c759276b798ccd6d77a87136029e71e66a8bbf2d2755cbdde1d82e78706", size = 137336, upload-time = "2025-12-06T15:54:49.426Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/b3/dc0d3771f2e5d1f13368f56b339c6782f955c6a20b50465a91acb79fe961/orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75bc2e59e6a2ac1dd28901d07115abdebc4563b5b07dd612bf64260a201b1c7f", size = 138993, upload-time = "2025-12-06T15:54:50.939Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/a2/65267e959de6abe23444659b6e19c888f242bf7725ff927e2292776f6b89/orjson-3.11.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:54aae9b654554c3b4edd61896b978568c6daa16af96fa4681c9b5babd469f863", size = 141070, upload-time = "2025-12-06T15:54:52.414Z" },
+ { url = "https://files.pythonhosted.org/packages/63/c9/da44a321b288727a322c6ab17e1754195708786a04f4f9d2220a5076a649/orjson-3.11.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4bdd8d164a871c4ec773f9de0f6fe8769c2d6727879c37a9666ba4183b7f8228", size = 413505, upload-time = "2025-12-06T15:54:53.67Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/17/68dc14fa7000eefb3d4d6d7326a190c99bb65e319f02747ef3ebf2452f12/orjson-3.11.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a261fef929bcf98a60713bf5e95ad067cea16ae345d9a35034e73c3990e927d2", size = 151342, upload-time = "2025-12-06T15:54:55.113Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/c5/ccee774b67225bed630a57478529fc026eda33d94fe4c0eac8fe58d4aa52/orjson-3.11.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c028a394c766693c5c9909dec76b24f37e6a1b91999e8d0c0d5feecbe93c3e05", size = 141823, upload-time = "2025-12-06T15:54:56.331Z" },
+ { url = "https://files.pythonhosted.org/packages/67/80/5d00e4155d0cd7390ae2087130637671da713959bb558db9bac5e6f6b042/orjson-3.11.5-cp313-cp313-win32.whl", hash = "sha256:2cc79aaad1dfabe1bd2d50ee09814a1253164b3da4c00a78c458d82d04b3bdef", size = 135236, upload-time = "2025-12-06T15:54:57.507Z" },
+ { url = "https://files.pythonhosted.org/packages/95/fe/792cc06a84808dbdc20ac6eab6811c53091b42f8e51ecebf14b540e9cfe4/orjson-3.11.5-cp313-cp313-win_amd64.whl", hash = "sha256:ff7877d376add4e16b274e35a3f58b7f37b362abf4aa31863dadacdd20e3a583", size = 133167, upload-time = "2025-12-06T15:54:58.71Z" },
+ { url = "https://files.pythonhosted.org/packages/46/2c/d158bd8b50e3b1cfdcf406a7e463f6ffe3f0d167b99634717acdaf5e299f/orjson-3.11.5-cp313-cp313-win_arm64.whl", hash = "sha256:59ac72ea775c88b163ba8d21b0177628bd015c5dd060647bbab6e22da3aad287", size = 126712, upload-time = "2025-12-06T15:54:59.892Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/60/77d7b839e317ead7bb225d55bb50f7ea75f47afc489c81199befc5435b50/orjson-3.11.5-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e446a8ea0a4c366ceafc7d97067bfd55292969143b57e3c846d87fc701e797a0", size = 245252, upload-time = "2025-12-06T15:55:01.127Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/aa/d4639163b400f8044cef0fb9aa51b0337be0da3a27187a20d1166e742370/orjson-3.11.5-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:53deb5addae9c22bbe3739298f5f2196afa881ea75944e7720681c7080909a81", size = 129419, upload-time = "2025-12-06T15:55:02.723Z" },
+ { url = "https://files.pythonhosted.org/packages/30/94/9eabf94f2e11c671111139edf5ec410d2f21e6feee717804f7e8872d883f/orjson-3.11.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cd00d49d6063d2b8791da5d4f9d20539c5951f965e45ccf4e96d33505ce68f", size = 132050, upload-time = "2025-12-06T15:55:03.918Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/c8/ca10f5c5322f341ea9a9f1097e140be17a88f88d1cfdd29df522970d9744/orjson-3.11.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fd15f9fc8c203aeceff4fda211157fad114dde66e92e24097b3647a08f4ee9e", size = 130370, upload-time = "2025-12-06T15:55:05.173Z" },
+ { url = "https://files.pythonhosted.org/packages/25/d4/e96824476d361ee2edd5c6290ceb8d7edf88d81148a6ce172fc00278ca7f/orjson-3.11.5-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df95000fbe6777bf9820ae82ab7578e8662051bb5f83d71a28992f539d2cda7", size = 136012, upload-time = "2025-12-06T15:55:06.402Z" },
+ { url = "https://files.pythonhosted.org/packages/85/8e/9bc3423308c425c588903f2d103cfcfe2539e07a25d6522900645a6f257f/orjson-3.11.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a8d676748fca47ade5bc3da7430ed7767afe51b2f8100e3cd65e151c0eaceb", size = 139809, upload-time = "2025-12-06T15:55:07.656Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/3c/b404e94e0b02a232b957c54643ce68d0268dacb67ac33ffdee24008c8b27/orjson-3.11.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa0f513be38b40234c77975e68805506cad5d57b3dfd8fe3baa7f4f4051e15b4", size = 137332, upload-time = "2025-12-06T15:55:08.961Z" },
+ { url = "https://files.pythonhosted.org/packages/51/30/cc2d69d5ce0ad9b84811cdf4a0cd5362ac27205a921da524ff42f26d65e0/orjson-3.11.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1863e75b92891f553b7922ce4ee10ed06db061e104f2b7815de80cdcb135ad", size = 138983, upload-time = "2025-12-06T15:55:10.595Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/87/de3223944a3e297d4707d2fe3b1ffb71437550e165eaf0ca8bbe43ccbcb1/orjson-3.11.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4be86b58e9ea262617b8ca6251a2f0d63cc132a6da4b5fcc8e0a4128782c829", size = 141069, upload-time = "2025-12-06T15:55:11.832Z" },
+ { url = "https://files.pythonhosted.org/packages/65/30/81d5087ae74be33bcae3ff2d80f5ccaa4a8fedc6d39bf65a427a95b8977f/orjson-3.11.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b923c1c13fa02084eb38c9c065afd860a5cff58026813319a06949c3af5732ac", size = 413491, upload-time = "2025-12-06T15:55:13.314Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/6f/f6058c21e2fc1efaf918986dbc2da5cd38044f1a2d4b7b91ad17c4acf786/orjson-3.11.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1b6bd351202b2cd987f35a13b5e16471cf4d952b42a73c391cc537974c43ef6d", size = 151375, upload-time = "2025-12-06T15:55:14.715Z" },
+ { url = "https://files.pythonhosted.org/packages/54/92/c6921f17d45e110892899a7a563a925b2273d929959ce2ad89e2525b885b/orjson-3.11.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bb150d529637d541e6af06bbe3d02f5498d628b7f98267ff87647584293ab439", size = 141850, upload-time = "2025-12-06T15:55:15.94Z" },
+ { url = "https://files.pythonhosted.org/packages/88/86/cdecb0140a05e1a477b81f24739da93b25070ee01ce7f7242f44a6437594/orjson-3.11.5-cp314-cp314-win32.whl", hash = "sha256:9cc1e55c884921434a84a0c3dd2699eb9f92e7b441d7f53f3941079ec6ce7499", size = 135278, upload-time = "2025-12-06T15:55:17.202Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/97/b638d69b1e947d24f6109216997e38922d54dcdcdb1b11c18d7efd2d3c59/orjson-3.11.5-cp314-cp314-win_amd64.whl", hash = "sha256:a4f3cb2d874e03bc7767c8f88adaa1a9a05cecea3712649c3b58589ec7317310", size = 133170, upload-time = "2025-12-06T15:55:18.468Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/dd/f4fff4a6fe601b4f8f3ba3aa6da8ac33d17d124491a3b804c662a70e1636/orjson-3.11.5-cp314-cp314-win_arm64.whl", hash = "sha256:38b22f476c351f9a1c43e5b07d8b5a02eb24a6ab8e75f700f7d479d4568346a5", size = 126713, upload-time = "2025-12-06T15:55:19.738Z" },
+ { url = "https://files.pythonhosted.org/packages/50/c7/7b682849dd4c9fb701a981669b964ea700516ecbd8e88f62aae07c6852bd/orjson-3.11.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1b280e2d2d284a6713b0cfec7b08918ebe57df23e3f76b27586197afca3cb1e9", size = 245298, upload-time = "2025-12-06T15:55:20.984Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/3f/194355a9335707a15fdc79ddc670148987b43d04712dd26898a694539ce6/orjson-3.11.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d8a112b274fae8c5f0f01954cb0480137072c271f3f4958127b010dfefaec", size = 132150, upload-time = "2025-12-06T15:55:22.364Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/08/d74b3a986d37e6c2e04b8821c62927620c9a1924bb49ea51519a87751b86/orjson-3.11.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0a2ae6f09ac7bd47d2d5a5305c1d9ed08ac057cda55bb0a49fa506f0d2da00", size = 130490, upload-time = "2025-12-06T15:55:23.619Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/16/ebd04c38c1db01e493a68eee442efdffc505a43112eccd481e0146c6acc2/orjson-3.11.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0d87bd1896faac0d10b4f849016db81a63e4ec5df38757ffae84d45ab38aa71", size = 135726, upload-time = "2025-12-06T15:55:24.912Z" },
+ { url = "https://files.pythonhosted.org/packages/06/64/2ce4b2c09a099403081c37639c224bdcdfe401138bd66fed5c96d4f8dbd3/orjson-3.11.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:801a821e8e6099b8c459ac7540b3c32dba6013437c57fdcaec205b169754f38c", size = 139640, upload-time = "2025-12-06T15:55:26.535Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/e2/425796df8ee1d7cea3a7edf868920121dd09162859dbb76fffc9a5c37fd3/orjson-3.11.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a0f6ac618c98c74b7fbc8c0172ba86f9e01dbf9f62aa0b1776c2231a7bffe5", size = 137289, upload-time = "2025-12-06T15:55:27.78Z" },
+ { url = "https://files.pythonhosted.org/packages/32/a2/88e482eb8e899a037dcc9eff85ef117a568e6ca1ffa1a2b2be3fcb51b7bb/orjson-3.11.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea7339bdd22e6f1060c55ac31b6a755d86a5b2ad3657f2669ec243f8e3b2bdb", size = 138761, upload-time = "2025-12-06T15:55:29.388Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/fd/131dd6d32eeb74c513bfa487f434a2150811d0fbd9cb06689284f2f21b34/orjson-3.11.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4dad582bc93cef8f26513e12771e76385a7e6187fd713157e971c784112aad56", size = 141357, upload-time = "2025-12-06T15:55:31.064Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/90/e4a0abbcca7b53e9098ac854f27f5ed9949c796f3c760bc04af997da0eb2/orjson-3.11.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:0522003e9f7fba91982e83a97fec0708f5a714c96c4209db7104e6b9d132f111", size = 413638, upload-time = "2025-12-06T15:55:32.344Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/c2/df91e385514924120001ade9cd52d6295251023d3bfa2c0a01f38cfc485a/orjson-3.11.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7403851e430a478440ecc1258bcbacbfbd8175f9ac1e39031a7121dd0de05ff8", size = 150972, upload-time = "2025-12-06T15:55:33.725Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/ff/c76cc5a30a4451191ff1b868a331ad1354433335277fc40931f5fc3cab9d/orjson-3.11.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5f691263425d3177977c8d1dd896cde7b98d93cbf390b2544a090675e83a6a0a", size = 141729, upload-time = "2025-12-06T15:55:35.317Z" },
+ { url = "https://files.pythonhosted.org/packages/27/c3/7830bf74389ea1eaab2b017d8b15d1cab2bb0737d9412dfa7fb8644f7d78/orjson-3.11.5-cp39-cp39-win32.whl", hash = "sha256:61026196a1c4b968e1b1e540563e277843082e9e97d78afa03eb89315af531f1", size = 135100, upload-time = "2025-12-06T15:55:36.57Z" },
+ { url = "https://files.pythonhosted.org/packages/69/e6/babf31154e047e465bc194eb72d1326d7c52ad4d7f50bf92b02b3cacda5c/orjson-3.11.5-cp39-cp39-win_amd64.whl", hash = "sha256:09b94b947ac08586af635ef922d69dc9bc63321527a3a04647f4986a73f4bd30", size = 133189, upload-time = "2025-12-06T15:55:38.143Z" },
+]
+
+[[package]]
+name = "outcome"
+version = "1.3.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", size = 21060, upload-time = "2023-10-26T04:26:04.361Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692, upload-time = "2023-10-26T04:26:02.532Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
+]
+
+[[package]]
+name = "paginate"
+version = "0.5.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" },
+]
+
+[[package]]
+name = "pathspec"
+version = "1.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/41/b9/6eb731b52f132181a9144bbe77ff82117f6b2d2fbfba49aaab2c014c4760/pathspec-1.0.2.tar.gz", hash = "sha256:fa32b1eb775ed9ba8d599b22c5f906dc098113989da2c00bf8b210078ca7fb92", size = 130502, upload-time = "2026-01-08T04:33:27.613Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/78/6b/14fc9049d78435fd29e82846c777bd7ed9c470013dc8d0260fff3ff1c11e/pathspec-1.0.2-py3-none-any.whl", hash = "sha256:62f8558917908d237d399b9b338ef455a814801a4688bc41074b25feefd93472", size = 54844, upload-time = "2026-01-08T04:33:26.4Z" },
+]
+
+[[package]]
+name = "pillow"
+version = "11.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" },
+ { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" },
+ { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" },
+ { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" },
+ { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" },
+ { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" },
+ { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" },
+ { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" },
+ { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" },
+ { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" },
+ { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" },
+ { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" },
+ { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" },
+ { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" },
+ { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" },
+ { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" },
+ { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" },
+ { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" },
+ { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" },
+ { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" },
+ { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" },
+ { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" },
+ { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" },
+ { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" },
+ { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" },
+ { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/8e/9c089f01677d1264ab8648352dcb7773f37da6ad002542760c80107da816/pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f", size = 5316478, upload-time = "2025-07-01T09:15:52.209Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/a9/5749930caf674695867eb56a581e78eb5f524b7583ff10b01b6e5048acb3/pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081", size = 4686522, upload-time = "2025-07-01T09:15:54.162Z" },
+ { url = "https://files.pythonhosted.org/packages/43/46/0b85b763eb292b691030795f9f6bb6fcaf8948c39413c81696a01c3577f7/pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4", size = 5853376, upload-time = "2025-07-03T13:11:01.066Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/c6/1a230ec0067243cbd60bc2dad5dc3ab46a8a41e21c15f5c9b52b26873069/pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc", size = 7626020, upload-time = "2025-07-03T13:11:06.479Z" },
+ { url = "https://files.pythonhosted.org/packages/63/dd/f296c27ffba447bfad76c6a0c44c1ea97a90cb9472b9304c94a732e8dbfb/pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06", size = 5956732, upload-time = "2025-07-01T09:15:56.111Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/a0/98a3630f0b57f77bae67716562513d3032ae70414fcaf02750279c389a9e/pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a", size = 6624404, upload-time = "2025-07-01T09:15:58.245Z" },
+ { url = "https://files.pythonhosted.org/packages/de/e6/83dfba5646a290edd9a21964da07674409e410579c341fc5b8f7abd81620/pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978", size = 6067760, upload-time = "2025-07-01T09:16:00.003Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/41/15ab268fe6ee9a2bc7391e2bbb20a98d3974304ab1a406a992dcb297a370/pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d", size = 6700534, upload-time = "2025-07-01T09:16:02.29Z" },
+ { url = "https://files.pythonhosted.org/packages/64/79/6d4f638b288300bed727ff29f2a3cb63db054b33518a95f27724915e3fbc/pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71", size = 6277091, upload-time = "2025-07-01T09:16:04.4Z" },
+ { url = "https://files.pythonhosted.org/packages/46/05/4106422f45a05716fd34ed21763f8ec182e8ea00af6e9cb05b93a247361a/pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada", size = 6986091, upload-time = "2025-07-01T09:16:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/63/c6/287fd55c2c12761d0591549d48885187579b7c257bef0c6660755b0b59ae/pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb", size = 2422632, upload-time = "2025-07-01T09:16:08.142Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" },
+ { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" },
+ { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" },
+ { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" },
+ { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" },
+ { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" },
+ { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.4.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.5.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
+]
+
+[[package]]
+name = "playwright"
+version = "1.57.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "greenlet", version = "3.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "greenlet", version = "3.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pyee" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ed/b6/e17543cea8290ae4dced10be21d5a43c360096aa2cce0aa7039e60c50df3/playwright-1.57.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:9351c1ac3dfd9b3820fe7fc4340d96c0d3736bb68097b9b7a69bd45d25e9370c", size = 41985039, upload-time = "2025-12-09T08:06:18.408Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/04/ef95b67e1ff59c080b2effd1a9a96984d6953f667c91dfe9d77c838fc956/playwright-1.57.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4a9d65027bce48eeba842408bcc1421502dfd7e41e28d207e94260fa93ca67e", size = 40775575, upload-time = "2025-12-09T08:06:22.105Z" },
+ { url = "https://files.pythonhosted.org/packages/60/bd/5563850322a663956c927eefcf1457d12917e8f118c214410e815f2147d1/playwright-1.57.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:99104771abc4eafee48f47dac2369e0015516dc1ce8c409807d2dd440828b9a4", size = 41985042, upload-time = "2025-12-09T08:06:25.357Z" },
+ { url = "https://files.pythonhosted.org/packages/56/61/3a803cb5ae0321715bfd5247ea871d25b32c8f372aeb70550a90c5f586df/playwright-1.57.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:284ed5a706b7c389a06caa431b2f0ba9ac4130113c3a779767dda758c2497bb1", size = 45975252, upload-time = "2025-12-09T08:06:29.186Z" },
+ { url = "https://files.pythonhosted.org/packages/83/d7/b72eb59dfbea0013a7f9731878df8c670f5f35318cedb010c8a30292c118/playwright-1.57.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a1bae6c0a07839cdeaddbc0756b3b2b85e476c07945f64ece08f1f956a86f1", size = 45706917, upload-time = "2025-12-09T08:06:32.549Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/09/3fc9ebd7c95ee54ba6a68d5c0bc23e449f7235f4603fc60534a364934c16/playwright-1.57.0-py3-none-win32.whl", hash = "sha256:1dd93b265688da46e91ecb0606d36f777f8eadcf7fbef12f6426b20bf0c9137c", size = 36553860, upload-time = "2025-12-09T08:06:35.864Z" },
+ { url = "https://files.pythonhosted.org/packages/58/d4/dcdfd2a33096aeda6ca0d15584800443dd2be64becca8f315634044b135b/playwright-1.57.0-py3-none-win_amd64.whl", hash = "sha256:6caefb08ed2c6f29d33b8088d05d09376946e49a73be19271c8cd5384b82b14c", size = 36553864, upload-time = "2025-12-09T08:06:38.915Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/60/fe31d7e6b8907789dcb0584f88be741ba388413e4fbce35f1eba4e3073de/playwright-1.57.0-py3-none-win_arm64.whl", hash = "sha256:5f065f5a133dbc15e6e7c71e7bc04f258195755b1c32a432b792e28338c8335e", size = 32837940, upload-time = "2025-12-09T08:06:42.268Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "prek"
+version = "0.2.22"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/83/a7/1e07536315f77d7b233cbf3dd916dc3424239c435ee0a0110c9b2cbcf6b0/prek-0.2.22.tar.gz", hash = "sha256:5abbda8bae0a63a18d3fe573162e8504a7b100e3603169cc2d06053891a02d7c", size = 267212, upload-time = "2025-12-13T12:57:51.797Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ac/fe/ba9a940adc55d78b96b58376a8752e95261402c1e5812acce6ea1a000fb8/prek-0.2.22-py3-none-linux_armv6l.whl", hash = "sha256:d026b2d75529a743466000e8dd058d3d5e7c597c34905b333f2ede3d24cb23f1", size = 4798026, upload-time = "2025-12-13T12:57:45.286Z" },
+ { url = "https://files.pythonhosted.org/packages/12/40/459cf510491271b08d19b4ef34f8293440eb472e633f4ffaf34179f39a12/prek-0.2.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:60b5bec94fa9f59fe5a9e90554c7346ceef81ea33d01bb18172d2576b07ac449", size = 4894023, upload-time = "2025-12-13T12:57:40.102Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/0b/59e0438b1e7d1b6fa3f14174a916d369e27c421f8876f7ec7c7a52fbfae7/prek-0.2.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a0c7c6ceee536122916d32d26b6fa4fac9e95ba28631901164ffc0b0fed28a9e", size = 4615858, upload-time = "2025-12-13T12:57:57.471Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/27/ea40cf715717298fdf802da2b15a2c4445b8c114aae28cab6bf794d65670/prek-0.2.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c90273bef7b638dfc36dede62c494f958456330375ffce891c68321b2a7b46ba", size = 4810206, upload-time = "2025-12-13T12:57:46.534Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/12/d1c3db35839492236afb8642a2818d5b413e5fce4ea909bc7ddfb3d4591a/prek-0.2.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d8ecf202073433b87df2671a98bc44d3b68bb5711f7119b50b7bd65c2a67f13", size = 4722439, upload-time = "2025-12-13T12:57:48.106Z" },
+ { url = "https://files.pythonhosted.org/packages/64/a0/0f24a9cacd5d78119f47063d860e03fa42b4d7dcf6803a49b0bef51b771b/prek-0.2.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4f26d76247ce7671cf5d9786e7fc86fdb43c065fd5507e8d64b3de7fd5e4447", size = 5037705, upload-time = "2025-12-13T12:57:50.596Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/6e/7616f84141755f1d9fe232f0bd06589421ae0dabd99180fdae2840d22ae8/prek-0.2.22-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac1f0ea2c82e35eb0ffc98dfbcd9ee34cfd7350b64f97198da4c311a271cdb8f", size = 5453199, upload-time = "2025-12-13T12:57:38.458Z" },
+ { url = "https://files.pythonhosted.org/packages/51/80/542a583db9b27bfd34954243666e451b266513bc742e0491cd61ff1b390e/prek-0.2.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f5d5131b9e57548f64d74665fd4414a8deb603a67d52ee18b3e6540cdb77733", size = 5399635, upload-time = "2025-12-13T12:57:43.359Z" },
+ { url = "https://files.pythonhosted.org/packages/49/06/ca4e6fee73e14e1aced90f5c83b9cdf9a8e1c3b1aa1e4f45a2a65de05a28/prek-0.2.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a768484e1c94a33228765f63701261316b64e11c482abe2a35c54045d3f81feb", size = 5498340, upload-time = "2025-12-13T12:57:41.827Z" },
+ { url = "https://files.pythonhosted.org/packages/85/a8/9636fc782db9c22d1740a8e5dc4e1ffc3a28099d074f812da46332e7c7a7/prek-0.2.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c941c8503ea537a84ea97074dc97b0f0dfd9861864883eb8b90586ed321847e", size = 5078431, upload-time = "2025-12-13T12:57:31.664Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/29/e78d2f444cf1f097aaaefee8910d7b9fe34195f06b086e0d2153b6c66e07/prek-0.2.22-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:946c6cfe18b17a7b53c49a389bf65f1e8e45a1b96bfdaeeacde21f5b5ca2d149", size = 4820871, upload-time = "2025-12-13T12:57:33.074Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/ec/779db6c35663e949b3f9989c584297aa115d3cc44822c149dbe40d51cd14/prek-0.2.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2cffd5809cf678b4300378d612b5da12cd2183ddc7aee78178db0b1ea48f0069", size = 4834431, upload-time = "2025-12-13T12:57:34.65Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/18/12bb4fece680457f4d4f13d21c5784675ce8b1db5c968261348c52087232/prek-0.2.22-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:f91df793cbc28647863eb54d578f37782736726671838ca92c9d0601329cb928", size = 4709742, upload-time = "2025-12-13T12:57:52.706Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/27/de1d9d037f59393568713121f4bfcea11cd546dcf96f214827983b8beccf/prek-0.2.22-py3-none-musllinux_1_1_i686.whl", hash = "sha256:3c40ba36b3e89817b20efe6163fd15387b81caf1f489060265d84103ae6e5184", size = 4925048, upload-time = "2025-12-13T12:57:49.348Z" },
+ { url = "https://files.pythonhosted.org/packages/49/bf/d40eef2e5ccbc520da94c2463450d0ecab598c092684002b463fd5491ff6/prek-0.2.22-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:a4154a419581723d12eccaa5b1d27686283c5c78b753c1984270d7e144a15fa7", size = 5192083, upload-time = "2025-12-13T12:57:35.957Z" },
+ { url = "https://files.pythonhosted.org/packages/41/ba/11ea837a876dcc7f5df85962bc560c8627a962261f046a1615b0a6016b01/prek-0.2.22-py3-none-win32.whl", hash = "sha256:9fd3d629a256ce3171bebc3183f9c608022fff0db19a511307ab0f4c7682d5e3", size = 4586129, upload-time = "2025-12-13T12:57:54.438Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/8c/05ab6d11ac670664c99944e4819a77a63360aab253d8daf4ae411c705bcd/prek-0.2.22-py3-none-win_amd64.whl", hash = "sha256:ad7997ae4bef4fccc0a6761c00479bdd44f2a5bb7eb97aebda3b42fe785e10a1", size = 5273787, upload-time = "2025-12-13T12:57:37.205Z" },
+ { url = "https://files.pythonhosted.org/packages/38/7a/53e8a550df705b5bf78a589c4e11d21485ac38c1a65e9c98fc3169a5eb25/prek-0.2.22-py3-none-win_arm64.whl", hash = "sha256:2442c0f12bd57675124542a92f5c799e7ffe52dc7cd98301c43c361849a3aef6", size = 4941186, upload-time = "2025-12-13T12:57:56.214Z" },
+]
+
+[[package]]
+name = "prompt-toolkit"
+version = "3.0.52"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "wcwidth" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
+]
+
+[[package]]
+name = "pwdlib"
+version = "0.2.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/82/a0/9daed437a6226f632a25d98d65d60ba02bdafa920c90dcb6454c611ead6c/pwdlib-0.2.1.tar.gz", hash = "sha256:9a1d8a8fa09a2f7ebf208265e55d7d008103cbdc82b9e4902ffdd1ade91add5e", size = 11699, upload-time = "2024-08-19T06:48:59.58Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/01/f3/0dae5078a486f0fdf4d4a1121e103bc42694a9da9bea7b0f2c63f29cfbd3/pwdlib-0.2.1-py3-none-any.whl", hash = "sha256:1823dc6f22eae472b540e889ecf57fd424051d6a4023ec0bcf7f0de2d9d7ef8c", size = 8082, upload-time = "2024-08-19T06:49:00.997Z" },
+]
+
+[package.optional-dependencies]
+argon2 = [
+ { name = "argon2-cffi", version = "23.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+]
+
+[[package]]
+name = "pwdlib"
+version = "0.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5f/41/a7c0d8a003c36ce3828ae3ed0391fe6a15aad65f082dbd6bec817ea95c0b/pwdlib-0.3.0.tar.gz", hash = "sha256:6ca30f9642a1467d4f5d0a4d18619de1c77f17dfccb42dd200b144127d3c83fc", size = 215810, upload-time = "2025-10-25T12:44:24.395Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/0c/9086a357d02a050fbb3270bf5043ac284dbfb845670e16c9389a41defc9e/pwdlib-0.3.0-py3-none-any.whl", hash = "sha256:f86c15c138858c09f3bba0a10984d4f9178158c55deaa72eac0210849b1a140d", size = 8633, upload-time = "2025-10-25T12:44:23.406Z" },
+]
+
+[package.optional-dependencies]
+argon2 = [
+ { name = "argon2-cffi", version = "25.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+
+[[package]]
+name = "pyasn1"
+version = "0.6.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" },
+]
+
+[[package]]
+name = "pyasn1-modules"
+version = "0.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyasn1" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" },
+]
+
+[[package]]
+name = "pycparser"
+version = "2.23"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
+]
+
+[[package]]
+name = "pydantic"
+version = "2.12.5"
+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/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
+]
+
+[package.optional-dependencies]
+email = [
+ { name = "email-validator" },
+]
+
+[[package]]
+name = "pydantic-ai"
+version = "0.4.10"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "huggingface", "mcp", "mistral", "openai", "retries", "vertexai"] },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/dd/67/eef2d1d64579c20ff732244c8f46f5b10401a47122b2404fd1a786860e0e/pydantic_ai-0.4.10.tar.gz", hash = "sha256:21cda3b62706dfdd5b9d662bb9a2ead50d51951f6ee0bbee45320b8752a95055", size = 43555371, upload-time = "2025-07-30T23:03:50.744Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/9c/3ea9ce322448e963fd84abb61985fa2d2ea0cb3f09bf0634cf43b2f49de6/pydantic_ai-0.4.10-py3-none-any.whl", hash = "sha256:4822325c92a57c9b45bdfd993106558e2397362b98ec211670d106c4b073ddd8", size = 10194, upload-time = "2025-07-30T23:03:42.435Z" },
+]
+
+[[package]]
+name = "pydantic-ai-slim"
+version = "0.4.10"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "eval-type-backport" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "httpx" },
+ { name = "opentelemetry-api" },
+ { name = "pydantic" },
+ { name = "pydantic-graph" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ee/fc/4123faf9372807e487c83acc858482f6d5a2f39ee6c1e25a895f23f700b6/pydantic_ai_slim-0.4.10.tar.gz", hash = "sha256:c9f6904aaa91c0309f91cc3a5617c570e153afbbb9888ee52190f58f029640f0", size = 189595, upload-time = "2025-07-30T23:03:55.44Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/20/d7/b1f6cf2eb9d24086de8a7f9e08956421fde514bcb7a4a3594b108e12636e/pydantic_ai_slim-0.4.10-py3-none-any.whl", hash = "sha256:49a386d6a900b4c1a81bca3469522a4c0eaab5a46a3953d1ffda8f0f2865ed97", size = 254994, upload-time = "2025-07-30T23:03:45.03Z" },
+]
+
+[package.optional-dependencies]
+ag-ui = [
+ { name = "ag-ui-protocol" },
+ { name = "starlette", version = "0.49.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+anthropic = [
+ { name = "anthropic" },
+]
+bedrock = [
+ { name = "boto3" },
+]
+cli = [
+ { name = "argcomplete" },
+ { name = "prompt-toolkit" },
+ { name = "rich" },
+]
+cohere = [
+ { name = "cohere", marker = "sys_platform != 'emscripten'" },
+]
+evals = [
+ { name = "pydantic-evals" },
+]
+google = [
+ { name = "google-genai", version = "1.47.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "google-genai", version = "1.57.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+groq = [
+ { name = "groq" },
+]
+huggingface = [
+ { name = "huggingface-hub" },
+]
+mcp = [
+ { name = "mcp", marker = "python_full_version >= '3.10'" },
+]
+mistral = [
+ { name = "mistralai" },
+]
+openai = [
+ { name = "openai" },
+]
+retries = [
+ { name = "tenacity" },
+]
+vertexai = [
+ { name = "google-auth" },
+ { name = "requests" },
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.41.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" },
+ { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" },
+ { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" },
+ { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" },
+ { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" },
+ { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" },
+ { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" },
+ { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" },
+ { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" },
+ { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" },
+ { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
+ { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
+ { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
+ { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
+ { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
+ { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
+ { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
+ { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
+ { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
+ { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
+ { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
+ { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
+ { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
+ { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
+ { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
+ { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
+ { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
+ { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
+ { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
+ { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
+ { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
+ { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
+ { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
+ { url = "https://files.pythonhosted.org/packages/54/db/160dffb57ed9a3705c4cbcbff0ac03bdae45f1ca7d58ab74645550df3fbd/pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf", size = 2107999, upload-time = "2025-11-04T13:42:03.885Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/7d/88e7de946f60d9263cc84819f32513520b85c0f8322f9b8f6e4afc938383/pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5", size = 1929745, upload-time = "2025-11-04T13:42:06.075Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/c2/aef51e5b283780e85e99ff19db0f05842d2d4a8a8cd15e63b0280029b08f/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d", size = 1920220, upload-time = "2025-11-04T13:42:08.457Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/97/492ab10f9ac8695cd76b2fdb24e9e61f394051df71594e9bcc891c9f586e/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60", size = 2067296, upload-time = "2025-11-04T13:42:10.817Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/23/984149650e5269c59a2a4c41d234a9570adc68ab29981825cfaf4cfad8f4/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82", size = 2231548, upload-time = "2025-11-04T13:42:13.843Z" },
+ { url = "https://files.pythonhosted.org/packages/71/0c/85bcbb885b9732c28bec67a222dbed5ed2d77baee1f8bba2002e8cd00c5c/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5", size = 2362571, upload-time = "2025-11-04T13:42:16.208Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/4a/412d2048be12c334003e9b823a3fa3d038e46cc2d64dd8aab50b31b65499/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3", size = 2068175, upload-time = "2025-11-04T13:42:18.911Z" },
+ { url = "https://files.pythonhosted.org/packages/73/f4/c58b6a776b502d0a5540ad02e232514285513572060f0d78f7832ca3c98b/pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425", size = 2177203, upload-time = "2025-11-04T13:42:22.578Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/ae/f06ea4c7e7a9eead3d165e7623cd2ea0cb788e277e4f935af63fc98fa4e6/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504", size = 2148191, upload-time = "2025-11-04T13:42:24.89Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/57/25a11dcdc656bf5f8b05902c3c2934ac3ea296257cc4a3f79a6319e61856/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5", size = 2343907, upload-time = "2025-11-04T13:42:27.683Z" },
+ { url = "https://files.pythonhosted.org/packages/96/82/e33d5f4933d7a03327c0c43c65d575e5919d4974ffc026bc917a5f7b9f61/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3", size = 2322174, upload-time = "2025-11-04T13:42:30.776Z" },
+ { url = "https://files.pythonhosted.org/packages/81/45/4091be67ce9f469e81656f880f3506f6a5624121ec5eb3eab37d7581897d/pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460", size = 1990353, upload-time = "2025-11-04T13:42:33.111Z" },
+ { url = "https://files.pythonhosted.org/packages/44/8a/a98aede18db6e9cd5d66bcacd8a409fcf8134204cdede2e7de35c5a2c5ef/pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b", size = 2015698, upload-time = "2025-11-04T13:42:35.484Z" },
+ { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" },
+ { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" },
+ { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" },
+ { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" },
+ { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" },
+ { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" },
+ { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" },
+ { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
+ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
+]
+
+[[package]]
+name = "pydantic-evals"
+version = "0.4.10"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "eval-type-backport", marker = "python_full_version < '3.11'" },
+ { name = "logfire-api" },
+ { name = "pydantic" },
+ { name = "pydantic-ai-slim" },
+ { name = "pyyaml" },
+ { name = "rich" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/98/b7/3f925d6ec9a2627e3310e87c2131514c3b44427c8975fafe83453825cfca/pydantic_evals-0.4.10.tar.gz", hash = "sha256:310acc7559d601743a101c606e50c0c5a9592bfc53cb733c7e54143b39e0fc97", size = 43729, upload-time = "2025-07-30T23:03:56.943Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9a/9f/7972c450d74c2b0f6d9f9de644ac33c4c63f0f2156f8acd37a274b1f43a3/pydantic_evals-0.4.10-py3-none-any.whl", hash = "sha256:5fda10c5ced2c99f03b407bd56645574598e6daab0e5be2ed7056e815e6037f6", size = 52511, upload-time = "2025-07-30T23:03:46.717Z" },
+]
+
+[[package]]
+name = "pydantic-extra-types"
+version = "2.11.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pydantic" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fd/35/2fee58b1316a73e025728583d3b1447218a97e621933fc776fb8c0f2ebdd/pydantic_extra_types-2.11.0.tar.gz", hash = "sha256:4e9991959d045b75feb775683437a97991d02c138e00b59176571db9ce634f0e", size = 157226, upload-time = "2025-12-31T16:18:27.944Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fe/17/fabd56da47096d240dd45ba627bead0333b0cf0ee8ada9bec579287dadf3/pydantic_extra_types-2.11.0-py3-none-any.whl", hash = "sha256:84b864d250a0fc62535b7ec591e36f2c5b4d1325fa0017eb8cda9aeb63b374a6", size = 74296, upload-time = "2025-12-31T16:18:26.38Z" },
+]
+
+[[package]]
+name = "pydantic-graph"
+version = "0.4.10"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "httpx" },
+ { name = "logfire-api" },
+ { name = "pydantic" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/04/46/62b83bd471a8743c41aba6693f55b746b8ee1294f64b7e9f42db7c3fd4b5/pydantic_graph-0.4.10.tar.gz", hash = "sha256:034063ac0ce2143a877a4fac563520492e70dde42d262b22c928f081d7759c5b", size = 21978, upload-time = "2025-07-30T23:03:57.986Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/55/55/0026897b1cd6ab6b179b25c06b1dff496a3ebb83e30c6acb1a5a40e80cd1/pydantic_graph-0.4.10-py3-none-any.whl", hash = "sha256:e5128d5e370a6391aa6441eaefff6b3a139e583e0ac6755d83b88c9df9c6c6a7", size = 27578, upload-time = "2025-07-30T23:03:47.925Z" },
+]
+
+[[package]]
+name = "pydantic-settings"
+version = "2.11.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "pydantic", marker = "python_full_version < '3.10'" },
+ { name = "python-dotenv", marker = "python_full_version < '3.10'" },
+ { name = "typing-inspection", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180", size = 188394, upload-time = "2025-09-24T14:19:11.764Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" },
+]
+
+[[package]]
+name = "pydantic-settings"
+version = "2.12.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "pydantic", marker = "python_full_version >= '3.10'" },
+ { name = "python-dotenv", marker = "python_full_version >= '3.10'" },
+ { name = "typing-inspection", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" },
+]
+
+[[package]]
+name = "pyee"
+version = "13.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" },
+]
+
+[[package]]
+name = "pygithub"
+version = "2.8.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyjwt", extra = ["crypto"] },
+ { name = "pynacl" },
+ { name = "requests" },
+ { name = "typing-extensions" },
+ { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c1/74/e560bdeffea72ecb26cff27f0fad548bbff5ecc51d6a155311ea7f9e4c4c/pygithub-2.8.1.tar.gz", hash = "sha256:341b7c78521cb07324ff670afd1baa2bf5c286f8d9fd302c1798ba594a5400c9", size = 2246994, upload-time = "2025-09-02T17:41:54.674Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/07/ba/7049ce39f653f6140aac4beb53a5aaf08b4407b6a3019aae394c1c5244ff/pygithub-2.8.1-py3-none-any.whl", hash = "sha256:23a0a5bca93baef082e03411bf0ce27204c32be8bfa7abc92fe4a3e132936df0", size = 432709, upload-time = "2025-09-02T17:41:52.947Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "pyjwt"
+version = "2.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825, upload-time = "2024-08-01T15:01:08.445Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344, upload-time = "2024-08-01T15:01:06.481Z" },
+]
+
+[package.optional-dependencies]
+crypto = [
+ { name = "cryptography" },
+]
+
+[[package]]
+name = "pymdown-extensions"
+version = "10.20"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3e/35/e3814a5b7df295df69d035cfb8aab78b2967cdf11fcfae7faed726b66664/pymdown_extensions-10.20.tar.gz", hash = "sha256:5c73566ab0cf38c6ba084cb7c5ea64a119ae0500cce754ccb682761dfea13a52", size = 852774, upload-time = "2025-12-31T19:59:42.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl", hash = "sha256:ea9e62add865da80a271d00bfa1c0fa085b20d133fb3fc97afdc88e682f60b2f", size = 268733, upload-time = "2025-12-31T19:59:40.652Z" },
+]
+
+[[package]]
+name = "pynacl"
+version = "1.6.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064, upload-time = "2026-01-01T17:31:57.264Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370, upload-time = "2026-01-01T17:31:59.198Z" },
+ { url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304, upload-time = "2026-01-01T17:32:01.162Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871, upload-time = "2026-01-01T17:32:02.824Z" },
+ { url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356, upload-time = "2026-01-01T17:32:04.452Z" },
+ { url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814, upload-time = "2026-01-01T17:32:06.078Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742, upload-time = "2026-01-01T17:32:07.651Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714, upload-time = "2026-01-01T17:32:09.558Z" },
+ { url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257, upload-time = "2026-01-01T17:32:11.026Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319, upload-time = "2026-01-01T17:32:12.46Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044, upload-time = "2026-01-01T17:32:13.781Z" },
+ { url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740, upload-time = "2026-01-01T17:32:15.083Z" },
+ { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" },
+ { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" },
+ { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" },
+ { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" },
+ { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" },
+ { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "8.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
+]
+
+[[package]]
+name = "pytest-codspeed"
+version = "4.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi" },
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "pytest" },
+ { name = "rich" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e2/e8/27fcbe6516a1c956614a4b61a7fccbf3791ea0b992e07416e8948184327d/pytest_codspeed-4.2.0.tar.gz", hash = "sha256:04b5d0bc5a1851ba1504d46bf9d7dbb355222a69f2cd440d54295db721b331f7", size = 113263, upload-time = "2025-10-24T09:02:55.704Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6c/b8/d599a466c50af3f04001877ae8b17c12b803f3b358235736b91a0769de0d/pytest_codspeed-4.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:609828b03972966b75b9b7416fa2570c4a0f6124f67e02d35cd3658e64312a7b", size = 261943, upload-time = "2025-10-24T09:02:37.962Z" },
+ { url = "https://files.pythonhosted.org/packages/74/19/ccc1a2fcd28357a8db08ba6b60f381832088a3850abc262c8e0b3406491a/pytest_codspeed-4.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23a0c0fbf8bb4de93a3454fd9e5efcdca164c778aaef0a9da4f233d85cb7f5b8", size = 250782, upload-time = "2025-10-24T09:02:39.617Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/2d/f0083a2f14ecf008d961d40439a71da0ae0d568e5f8dc2fccd3e8a2ab3e4/pytest_codspeed-4.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2de87bde9fbc6fd53f0fd21dcf2599c89e0b8948d49f9bad224edce51c47e26b", size = 261960, upload-time = "2025-10-24T09:02:40.665Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/0c/1f514c553db4ea5a69dfbe2706734129acd0eca8d5101ec16f1dd00dbc0f/pytest_codspeed-4.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95aeb2479ca383f6b18e2cc9ebcd3b03ab184980a59a232aea6f370bbf59a1e3", size = 250808, upload-time = "2025-10-24T09:02:42.07Z" },
+ { url = "https://files.pythonhosted.org/packages/81/04/479905bd6653bc981c0554fcce6df52d7ae1594e1eefd53e6cf31810ec7f/pytest_codspeed-4.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d4fefbd4ae401e2c60f6be920a0be50eef0c3e4a1f0a1c83962efd45be38b39", size = 262084, upload-time = "2025-10-24T09:02:43.155Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/46/d6f345d7907bac6cbb6224bd697ecbc11cf7427acc9e843c3618f19e3476/pytest_codspeed-4.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:309b4227f57fcbb9df21e889ea1ae191d0d1cd8b903b698fdb9ea0461dbf1dfe", size = 251100, upload-time = "2025-10-24T09:02:44.168Z" },
+ { url = "https://files.pythonhosted.org/packages/de/dc/e864f45e994a50390ff49792256f1bdcbf42f170e3bc0470ee1a7d2403f3/pytest_codspeed-4.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72aab8278452a6d020798b9e4f82780966adb00f80d27a25d1274272c54630d5", size = 262057, upload-time = "2025-10-24T09:02:45.791Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/1c/f1d2599784486879cf6579d8d94a3e22108f0e1f130033dab8feefd29249/pytest_codspeed-4.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:684fcd9491d810ded653a8d38de4835daa2d001645f4a23942862950664273f8", size = 251013, upload-time = "2025-10-24T09:02:46.937Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/fd/eafd24db5652a94b4d00fe9b309b607de81add0f55f073afb68a378a24b6/pytest_codspeed-4.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50794dabea6ec90d4288904452051e2febace93e7edf4ca9f2bce8019dd8cd37", size = 262065, upload-time = "2025-10-24T09:02:48.018Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/14/8d9340d7dc0ae647991b28a396e16b3403e10def883cde90d6b663d3f7ec/pytest_codspeed-4.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0ebd87f2a99467a1cfd8e83492c4712976e43d353ee0b5f71cbb057f1393aca", size = 251057, upload-time = "2025-10-24T09:02:49.102Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/39/48cf6afbca55bc7c8c93c3d4ae926a1068bcce3f0241709db19b078d5418/pytest_codspeed-4.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbbb2d61b85bef8fc7e2193f723f9ac2db388a48259d981bbce96319043e9830", size = 267983, upload-time = "2025-10-24T09:02:50.558Z" },
+ { url = "https://files.pythonhosted.org/packages/33/86/4407341efb5dceb3e389635749ce1d670542d6ca148bd34f9d5334295faf/pytest_codspeed-4.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:748411c832147bfc85f805af78a1ab1684f52d08e14aabe22932bbe46c079a5f", size = 256732, upload-time = "2025-10-24T09:02:51.603Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/60/c395c19c14a1345d41ac3f7f0a9b372b666e88f9ba1f71988215174882bb/pytest_codspeed-4.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:238e17abe8f08d8747fa6c7acff34fefd3c40f17a56a7847ca13dc8d6e8c6009", size = 261935, upload-time = "2025-10-24T09:02:52.702Z" },
+ { url = "https://files.pythonhosted.org/packages/15/ed/442fb6a1832c2c9002653f24770873839b24c091bd2ed658090c7862c563/pytest_codspeed-4.2.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0881a736285f33b9a8894da8fe8e1775aa1a4310226abe5d1f0329228efb680c", size = 250770, upload-time = "2025-10-24T09:02:53.73Z" },
+ { url = "https://files.pythonhosted.org/packages/25/0e/8cb71fd3ed4ed08c07aec1245aea7bc1b661ba55fd9c392db76f1978d453/pytest_codspeed-4.2.0-py3-none-any.whl", hash = "sha256:e81bbb45c130874ef99aca97929d72682733527a49f84239ba575b5cb843bab0", size = 113726, upload-time = "2025-10-24T09:02:54.785Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+]
+
+[[package]]
+name = "python-dotenv"
+version = "1.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
+]
+
+[[package]]
+name = "python-multipart"
+version = "0.0.20"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
+]
+
+[[package]]
+name = "python-multipart"
+version = "0.0.21"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/78/96/804520d0850c7db98e5ccb70282e29208723f0964e88ffd9d0da2f52ea09/python_multipart-0.0.21.tar.gz", hash = "sha256:7137ebd4d3bbf70ea1622998f902b97a29434a9e8dc40eb203bbcf7c2a2cba92", size = 37196, upload-time = "2025-12-17T09:24:22.446Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/aa/76/03af049af4dcee5d27442f71b6924f01f3efb5d2bd34f23fcd563f2cc5f5/python_multipart-0.0.21-py3-none-any.whl", hash = "sha256:cf7a6713e01c87aa35387f4774e812c4361150938d20d232800f75ffcf266090", size = 24541, upload-time = "2025-12-17T09:24:21.153Z" },
+]
+
+[[package]]
+name = "python-slugify"
+version = "8.0.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "text-unidecode" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" },
+]
+
+[[package]]
+name = "pywin32"
+version = "311"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" },
+ { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" },
+ { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" },
+ { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" },
+ { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" },
+ { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
+ { url = "https://files.pythonhosted.org/packages/59/42/b86689aac0cdaee7ae1c58d464b0ff04ca909c19bb6502d4973cdd9f9544/pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b", size = 8760837, upload-time = "2025-07-14T20:12:59.59Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/8a/1403d0353f8c5a2f0829d2b1c4becbf9da2f0a4d040886404fc4a5431e4d/pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91", size = 9590187, upload-time = "2025-07-14T20:13:01.419Z" },
+ { url = "https://files.pythonhosted.org/packages/60/22/e0e8d802f124772cec9c75430b01a212f86f9de7546bda715e54140d5aeb/pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d", size = 8778162, upload-time = "2025-07-14T20:13:03.544Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
+ { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
+ { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
+ { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
+ { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
+ { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
+ { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
+ { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
+ { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
+ { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
+ { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
+ { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
+ { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" },
+ { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" },
+ { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" },
+]
+
+[[package]]
+name = "pyyaml-env-tag"
+version = "1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" },
+]
+
+[[package]]
+name = "rcslice"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/53/3e/abe47d91d5340b77b003baf96fdf8966c946eb4c5a704a844b5d03e6e578/rcslice-1.1.0.tar.gz", hash = "sha256:a2ce70a60690eb63e52b722e046b334c3aaec5e900b28578f529878782ee5c6e", size = 4414, upload-time = "2018-09-27T12:44:06.601Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/aa/96/7935186fba032312eb8a75e6503440b0e6de76c901421f791408e4debd93/rcslice-1.1.0-py3-none-any.whl", hash = "sha256:1b12fc0c0ca452e8a9fd2b56ac008162f19e250906a4290a7e7a98be3200c2a6", size = 5180, upload-time = "2018-09-27T12:44:05.197Z" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.37.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs", marker = "python_full_version >= '3.10'" },
+ { name = "rpds-py", marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
+]
+
+[[package]]
+name = "requests"
+version = "2.32.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
+]
+
+[[package]]
+name = "rich"
+version = "14.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
+]
+
+[[package]]
+name = "rich-toolkit"
+version = "0.17.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "rich" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/97/09/3f9b8d9daaf235195c626f21e03604c05b987404ee3bcacee0c1f67f2a8e/rich_toolkit-0.17.1.tar.gz", hash = "sha256:5af54df8d1dd9c8530e462e1bdcaed625c9b49f5a55b035aa0ba1c17bdb87c9a", size = 187925, upload-time = "2025-12-17T10:49:22.583Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7f/7b/15e55fa8a76d0d41bf34d965af78acdaf80a315907adb30de8b63c272694/rich_toolkit-0.17.1-py3-none-any.whl", hash = "sha256:96d24bb921ecd225ffce7c526a9149e74006410c05e6d405bd74ffd54d5631ed", size = 31412, upload-time = "2025-12-17T10:49:21.793Z" },
+]
+
+[[package]]
+name = "rignore"
+version = "0.7.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e5/f5/8bed2310abe4ae04b67a38374a4d311dd85220f5d8da56f47ae9361be0b0/rignore-0.7.6.tar.gz", hash = "sha256:00d3546cd793c30cb17921ce674d2c8f3a4b00501cb0e3dd0e82217dbeba2671", size = 57140, upload-time = "2025-11-05T21:41:21.968Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/86/7a/b970cd0138b0ece72eb28f086e933f9ed75b795716ad3de5ab22994b3b54/rignore-0.7.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f3c74a7e5ee77aea669c95fdb3933f2a6c7549893700082e759128a29cf67e45", size = 884999, upload-time = "2025-11-05T20:42:38.373Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/05/23faca29616d8966ada63fb0e13c214107811fa9a0aba2275e4c7ca63bd5/rignore-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7202404958f5fe3474bac91f65350f0b1dde1a5e05089f2946549b7e91e79ec", size = 824824, upload-time = "2025-11-05T20:42:22.1Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/2e/05a1e61f04cf2548524224f0b5f21ca19ea58f7273a863bac10846b8ff69/rignore-0.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bde7c5835fa3905bfb7e329a4f1d7eccb676de63da7a3f934ddd5c06df20597", size = 899121, upload-time = "2025-11-05T20:40:48.94Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/35/71518847e10bdbf359badad8800e4681757a01f4777b3c5e03dbde8a42d8/rignore-0.7.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:626c3d4ba03af266694d25101bc1d8d16eda49c5feb86cedfec31c614fceca7d", size = 873813, upload-time = "2025-11-05T20:41:04.71Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/c8/32ae405d3e7fd4d9f9b7838f2fcca0a5005bb87fa514b83f83fd81c0df22/rignore-0.7.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a43841e651e7a05a4274b9026cc408d1912e64016ede8cd4c145dae5d0635be", size = 1168019, upload-time = "2025-11-05T20:41:20.723Z" },
+ { url = "https://files.pythonhosted.org/packages/25/98/013c955982bc5b4719bf9a5bea58be317eea28aa12bfd004025e3cd7c000/rignore-0.7.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7978c498dbf7f74d30cdb8859fe612167d8247f0acd377ae85180e34490725da", size = 942822, upload-time = "2025-11-05T20:41:36.99Z" },
+ { url = "https://files.pythonhosted.org/packages/90/fb/9a3f3156c6ed30bcd597e63690353edac1fcffe9d382ad517722b56ac195/rignore-0.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d22f72ab695c07d2d96d2a645208daff17084441b5d58c07378c9dd6f9c4c87", size = 959820, upload-time = "2025-11-05T20:42:06.364Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/b2/93bf609633021e9658acaff24cfb055d8cdaf7f5855d10ebb35307900dda/rignore-0.7.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5bd8e1a91ed1a789b2cbe39eeea9204a6719d4f2cf443a9544b521a285a295f", size = 985050, upload-time = "2025-11-05T20:41:51.124Z" },
+ { url = "https://files.pythonhosted.org/packages/69/bc/ec2d040469bdfd7b743df10f2201c5d285009a4263d506edbf7a06a090bb/rignore-0.7.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fc03efad5789365018e94ac4079f851a999bc154d1551c45179f7fcf45322", size = 1079164, upload-time = "2025-11-05T21:40:10.368Z" },
+ { url = "https://files.pythonhosted.org/packages/df/26/4b635f4ea5baf4baa8ba8eee06163f6af6e76dfbe72deb57da34bb24b19d/rignore-0.7.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ce2617fe28c51367fd8abfd4eeea9e61664af63c17d4ea00353d8ef56dfb95fa", size = 1139028, upload-time = "2025-11-05T21:40:27.977Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/54/a3147ebd1e477b06eb24e2c2c56d951ae5faa9045b7b36d7892fec5080d9/rignore-0.7.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7c4ad2cee85068408e7819a38243043214e2c3047e9bd4c506f8de01c302709e", size = 1119024, upload-time = "2025-11-05T21:40:45.148Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/f4/27475db769a57cff18fe7e7267b36e6cdb5b1281caa185ba544171106cba/rignore-0.7.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:02cd240bfd59ecc3907766f4839cbba20530a2e470abca09eaa82225e4d946fb", size = 1128531, upload-time = "2025-11-05T21:41:02.734Z" },
+ { url = "https://files.pythonhosted.org/packages/97/32/6e782d3b352e4349fa0e90bf75b13cb7f11d8908b36d9e2b262224b65d9a/rignore-0.7.6-cp310-cp310-win32.whl", hash = "sha256:fe2bd8fa1ff555259df54c376abc73855cb02628a474a40d51b358c3a1ddc55b", size = 646817, upload-time = "2025-11-05T21:41:47.51Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/8a/53185c69abb3bb362e8a46b8089999f820bf15655629ff8395107633c8ab/rignore-0.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:d80afd6071c78baf3765ec698841071b19e41c326f994cfa69b5a1df676f5d39", size = 727001, upload-time = "2025-11-05T21:41:32.778Z" },
+ { url = "https://files.pythonhosted.org/packages/25/41/b6e2be3069ef3b7f24e35d2911bd6deb83d20ed5642ad81d5a6d1c015473/rignore-0.7.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:40be8226e12d6653abbebaffaea2885f80374c1c8f76fe5ca9e0cadd120a272c", size = 885285, upload-time = "2025-11-05T20:42:39.763Z" },
+ { url = "https://files.pythonhosted.org/packages/52/66/ba7f561b6062402022887706a7f2b2c2e2e2a28f1e3839202b0a2f77e36d/rignore-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182f4e5e4064d947c756819446a7d4cdede8e756b8c81cf9e509683fe38778d7", size = 823882, upload-time = "2025-11-05T20:42:23.488Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/81/4087453df35a90b07370647b19017029324950c1b9137d54bf1f33843f17/rignore-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16b63047648a916a87be1e51bb5c009063f1b8b6f5afe4f04f875525507e63dc", size = 899362, upload-time = "2025-11-05T20:40:51.111Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/c9/390a8fdfabb76d71416be773bd9f162977bd483084f68daf19da1dec88a6/rignore-0.7.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba5524f5178deca4d7695e936604ebc742acb8958f9395776e1fcb8133f8257a", size = 873633, upload-time = "2025-11-05T20:41:06.193Z" },
+ { url = "https://files.pythonhosted.org/packages/df/c9/79404fcb0faa76edfbc9df0901f8ef18568d1104919ebbbad6d608c888d1/rignore-0.7.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62020dbb89a1dd4b84ab3d60547b3b2eb2723641d5fb198463643f71eaaed57d", size = 1167633, upload-time = "2025-11-05T20:41:22.491Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/8d/b3466d32d445d158a0aceb80919085baaae495b1f540fb942f91d93b5e5b/rignore-0.7.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b34acd532769d5a6f153a52a98dcb81615c949ab11697ce26b2eb776af2e174d", size = 941434, upload-time = "2025-11-05T20:41:38.151Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/40/9cd949761a7af5bc27022a939c91ff622d29c7a0b66d0c13a863097dde2d/rignore-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5e53b752f9de44dff7b3be3c98455ce3bf88e69d6dc0cf4f213346c5e3416c", size = 959461, upload-time = "2025-11-05T20:42:08.476Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/87/1e1a145731f73bdb7835e11f80da06f79a00d68b370d9a847de979575e6d/rignore-0.7.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25b3536d13a5d6409ce85f23936f044576eeebf7b6db1d078051b288410fc049", size = 985323, upload-time = "2025-11-05T20:41:52.735Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/31/1ecff992fc3f59c4fcdcb6c07d5f6c1e6dfb55ccda19c083aca9d86fa1c6/rignore-0.7.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e01cad2b0b92f6b1993f29fc01f23f2d78caf4bf93b11096d28e9d578eb08ce", size = 1079173, upload-time = "2025-11-05T21:40:12.007Z" },
+ { url = "https://files.pythonhosted.org/packages/17/18/162eedadb4c2282fa4c521700dbf93c9b14b8842e8354f7d72b445b8d593/rignore-0.7.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5991e46ab9b4868334c9e372ab0892b0150f3f586ff2b1e314272caeb38aaedb", size = 1139012, upload-time = "2025-11-05T21:40:29.399Z" },
+ { url = "https://files.pythonhosted.org/packages/78/96/a9ca398a8af74bb143ad66c2a31303c894111977e28b0d0eab03867f1b43/rignore-0.7.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6c8ae562e5d1246cba5eaeb92a47b2a279e7637102828dde41dcbe291f529a3e", size = 1118827, upload-time = "2025-11-05T21:40:46.6Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/22/1c1a65047df864def9a047dbb40bc0b580b8289a4280e62779cd61ae21f2/rignore-0.7.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aaf938530dcc0b47c4cfa52807aa2e5bfd5ca6d57a621125fe293098692f6345", size = 1128182, upload-time = "2025-11-05T21:41:04.239Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/f4/1526eb01fdc2235aca1fd9d0189bee4021d009a8dcb0161540238c24166e/rignore-0.7.6-cp311-cp311-win32.whl", hash = "sha256:166ebce373105dd485ec213a6a2695986346e60c94ff3d84eb532a237b24a4d5", size = 646547, upload-time = "2025-11-05T21:41:49.439Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/c8/dda0983e1845706beb5826459781549a840fe5a7eb934abc523e8cd17814/rignore-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:44f35ee844b1a8cea50d056e6a595190ce9d42d3cccf9f19d280ae5f3058973a", size = 727139, upload-time = "2025-11-05T21:41:34.367Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/47/eb1206b7bf65970d41190b879e1723fc6bbdb2d45e53565f28991a8d9d96/rignore-0.7.6-cp311-cp311-win_arm64.whl", hash = "sha256:14b58f3da4fa3d5c3fa865cab49821675371f5e979281c683e131ae29159a581", size = 657598, upload-time = "2025-11-05T21:41:23.758Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/0e/012556ef3047a2628842b44e753bb15f4dc46806780ff090f1e8fe4bf1eb/rignore-0.7.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:03e82348cb7234f8d9b2834f854400ddbbd04c0f8f35495119e66adbd37827a8", size = 883488, upload-time = "2025-11-05T20:42:41.359Z" },
+ { url = "https://files.pythonhosted.org/packages/93/b0/d4f1f3fe9eb3f8e382d45ce5b0547ea01c4b7e0b4b4eb87bcd66a1d2b888/rignore-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9e624f6be6116ea682e76c5feb71ea91255c67c86cb75befe774365b2931961", size = 820411, upload-time = "2025-11-05T20:42:24.782Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/c8/dea564b36dedac8de21c18e1851789545bc52a0c22ece9843444d5608a6a/rignore-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bda49950d405aa8d0ebe26af807c4e662dd281d926530f03f29690a2e07d649a", size = 897821, upload-time = "2025-11-05T20:40:52.613Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/2b/ee96db17ac1835e024c5d0742eefb7e46de60020385ac883dd3d1cde2c1f/rignore-0.7.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5fd5ab3840b8c16851d327ed06e9b8be6459702a53e5ab1fc4073b684b3789e", size = 873963, upload-time = "2025-11-05T20:41:07.49Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/8c/ad5a57bbb9d14d5c7e5960f712a8a0b902472ea3f4a2138cbf70d1777b75/rignore-0.7.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ced2a248352636a5c77504cb755dc02c2eef9a820a44d3f33061ce1bb8a7f2d2", size = 1169216, upload-time = "2025-11-05T20:41:23.73Z" },
+ { url = "https://files.pythonhosted.org/packages/80/e6/5b00bc2a6bc1701e6878fca798cf5d9125eb3113193e33078b6fc0d99123/rignore-0.7.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04a3b73b75ddc12c9c9b21efcdaab33ca3832941d6f1d67bffd860941cd448a", size = 942942, upload-time = "2025-11-05T20:41:39.393Z" },
+ { url = "https://files.pythonhosted.org/packages/85/e5/7f99bd0cc9818a91d0e8b9acc65b792e35750e3bdccd15a7ee75e64efca4/rignore-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24321efac92140b7ec910ac7c53ab0f0c86a41133d2bb4b0e6a7c94967f44dd", size = 959787, upload-time = "2025-11-05T20:42:09.765Z" },
+ { url = "https://files.pythonhosted.org/packages/55/54/2ffea79a7c1eabcede1926347ebc2a81bc6b81f447d05b52af9af14948b9/rignore-0.7.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c7aa109d41e593785c55fdaa89ad80b10330affa9f9d3e3a51fa695f739b20", size = 984245, upload-time = "2025-11-05T20:41:54.062Z" },
+ { url = "https://files.pythonhosted.org/packages/41/f7/e80f55dfe0f35787fa482aa18689b9c8251e045076c35477deb0007b3277/rignore-0.7.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1734dc49d1e9501b07852ef44421f84d9f378da9fbeda729e77db71f49cac28b", size = 1078647, upload-time = "2025-11-05T21:40:13.463Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/cf/2c64f0b6725149f7c6e7e5a909d14354889b4beaadddaa5fff023ec71084/rignore-0.7.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5719ea14ea2b652c0c0894be5dfde954e1853a80dea27dd2fbaa749618d837f5", size = 1139186, upload-time = "2025-11-05T21:40:31.27Z" },
+ { url = "https://files.pythonhosted.org/packages/75/95/a86c84909ccc24af0d094b50d54697951e576c252a4d9f21b47b52af9598/rignore-0.7.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8e23424fc7ce35726854f639cb7968151a792c0c3d9d082f7f67e0c362cfecca", size = 1117604, upload-time = "2025-11-05T21:40:48.07Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/5e/13b249613fd5d18d58662490ab910a9f0be758981d1797789913adb4e918/rignore-0.7.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3efdcf1dd84d45f3e2bd2f93303d9be103888f56dfa7c3349b5bf4f0657ec696", size = 1127725, upload-time = "2025-11-05T21:41:05.804Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/28/fa5dcd1e2e16982c359128664e3785f202d3eca9b22dd0b2f91c4b3d242f/rignore-0.7.6-cp312-cp312-win32.whl", hash = "sha256:ccca9d1a8b5234c76b71546fc3c134533b013f40495f394a65614a81f7387046", size = 646145, upload-time = "2025-11-05T21:41:51.096Z" },
+ { url = "https://files.pythonhosted.org/packages/26/87/69387fb5dd81a0f771936381431780b8cf66fcd2cfe9495e1aaf41548931/rignore-0.7.6-cp312-cp312-win_amd64.whl", hash = "sha256:c96a285e4a8bfec0652e0bfcf42b1aabcdda1e7625f5006d188e3b1c87fdb543", size = 726090, upload-time = "2025-11-05T21:41:36.485Z" },
+ { url = "https://files.pythonhosted.org/packages/24/5f/e8418108dcda8087fb198a6f81caadbcda9fd115d61154bf0df4d6d3619b/rignore-0.7.6-cp312-cp312-win_arm64.whl", hash = "sha256:a64a750e7a8277a323f01ca50b7784a764845f6cce2fe38831cb93f0508d0051", size = 656317, upload-time = "2025-11-05T21:41:25.305Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/8a/a4078f6e14932ac7edb171149c481de29969d96ddee3ece5dc4c26f9e0c3/rignore-0.7.6-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2bdab1d31ec9b4fb1331980ee49ea051c0d7f7bb6baa28b3125ef03cdc48fdaf", size = 883057, upload-time = "2025-11-05T20:42:42.741Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/8f/f8daacd177db4bf7c2223bab41e630c52711f8af9ed279be2058d2fe4982/rignore-0.7.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:90f0a00ce0c866c275bf888271f1dc0d2140f29b82fcf33cdbda1e1a6af01010", size = 820150, upload-time = "2025-11-05T20:42:26.545Z" },
+ { url = "https://files.pythonhosted.org/packages/36/31/b65b837e39c3f7064c426754714ac633b66b8c2290978af9d7f513e14aa9/rignore-0.7.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ad295537041dc2ed4b540fb1a3906bd9ede6ccdad3fe79770cd89e04e3c73c", size = 897406, upload-time = "2025-11-05T20:40:53.854Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/58/1970ce006c427e202ac7c081435719a076c478f07b3a23f469227788dc23/rignore-0.7.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f782dbd3a65a5ac85adfff69e5c6b101285ef3f845c3a3cae56a54bebf9fe116", size = 874050, upload-time = "2025-11-05T20:41:08.922Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/00/eb45db9f90137329072a732273be0d383cb7d7f50ddc8e0bceea34c1dfdf/rignore-0.7.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65cece3b36e5b0826d946494734c0e6aaf5a0337e18ff55b071438efe13d559e", size = 1167835, upload-time = "2025-11-05T20:41:24.997Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/f1/6f1d72ddca41a64eed569680587a1236633587cc9f78136477ae69e2c88a/rignore-0.7.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e4bb66c13cd7602dc8931822c02dfbbd5252015c750ac5d6152b186f0a8be0", size = 941945, upload-time = "2025-11-05T20:41:40.628Z" },
+ { url = "https://files.pythonhosted.org/packages/48/6f/2f178af1c1a276a065f563ec1e11e7a9e23d4996fd0465516afce4b5c636/rignore-0.7.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297e500c15766e196f68aaaa70e8b6db85fa23fdc075b880d8231fdfba738cd7", size = 959067, upload-time = "2025-11-05T20:42:11.09Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/db/423a81c4c1e173877c7f9b5767dcaf1ab50484a94f60a0b2ed78be3fa765/rignore-0.7.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a07084211a8d35e1a5b1d32b9661a5ed20669970b369df0cf77da3adea3405de", size = 984438, upload-time = "2025-11-05T20:41:55.443Z" },
+ { url = "https://files.pythonhosted.org/packages/31/eb/c4f92cc3f2825d501d3c46a244a671eb737fc1bcf7b05a3ecd34abb3e0d7/rignore-0.7.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:181eb2a975a22256a1441a9d2f15eb1292839ea3f05606620bd9e1938302cf79", size = 1078365, upload-time = "2025-11-05T21:40:15.148Z" },
+ { url = "https://files.pythonhosted.org/packages/26/09/99442f02794bd7441bfc8ed1c7319e890449b816a7493b2db0e30af39095/rignore-0.7.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7bbcdc52b5bf9f054b34ce4af5269df5d863d9c2456243338bc193c28022bd7b", size = 1139066, upload-time = "2025-11-05T21:40:32.771Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/88/bcfc21e520bba975410e9419450f4b90a2ac8236b9a80fd8130e87d098af/rignore-0.7.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f2e027a6da21a7c8c0d87553c24ca5cc4364def18d146057862c23a96546238e", size = 1118036, upload-time = "2025-11-05T21:40:49.646Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/25/d37215e4562cda5c13312636393aea0bafe38d54d4e0517520a4cc0753ec/rignore-0.7.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee4a18b82cbbc648e4aac1510066682fe62beb5dc88e2c67c53a83954e541360", size = 1127550, upload-time = "2025-11-05T21:41:07.648Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/76/a264ab38bfa1620ec12a8ff1c07778da89e16d8c0f3450b0333020d3d6dc/rignore-0.7.6-cp313-cp313-win32.whl", hash = "sha256:a7d7148b6e5e95035d4390396895adc384d37ff4e06781a36fe573bba7c283e5", size = 646097, upload-time = "2025-11-05T21:41:53.201Z" },
+ { url = "https://files.pythonhosted.org/packages/62/44/3c31b8983c29ea8832b6082ddb1d07b90379c2d993bd20fce4487b71b4f4/rignore-0.7.6-cp313-cp313-win_amd64.whl", hash = "sha256:b037c4b15a64dced08fc12310ee844ec2284c4c5c1ca77bc37d0a04f7bff386e", size = 726170, upload-time = "2025-11-05T21:41:38.131Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/41/e26a075cab83debe41a42661262f606166157df84e0e02e2d904d134c0d8/rignore-0.7.6-cp313-cp313-win_arm64.whl", hash = "sha256:e47443de9b12fe569889bdbe020abe0e0b667516ee2ab435443f6d0869bd2804", size = 656184, upload-time = "2025-11-05T21:41:27.396Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/b9/1f5bd82b87e5550cd843ceb3768b4a8ef274eb63f29333cf2f29644b3d75/rignore-0.7.6-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:8e41be9fa8f2f47239ded8920cc283699a052ac4c371f77f5ac017ebeed75732", size = 882632, upload-time = "2025-11-05T20:42:44.063Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/6b/07714a3efe4a8048864e8a5b7db311ba51b921e15268b17defaebf56d3db/rignore-0.7.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6dc1e171e52cefa6c20e60c05394a71165663b48bca6c7666dee4f778f2a7d90", size = 820760, upload-time = "2025-11-05T20:42:27.885Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/0f/348c829ea2d8d596e856371b14b9092f8a5dfbb62674ec9b3f67e4939a9d/rignore-0.7.6-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ce2268837c3600f82ab8db58f5834009dc638ee17103582960da668963bebc5", size = 899044, upload-time = "2025-11-05T20:40:55.336Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/30/2e1841a19b4dd23878d73edd5d82e998a83d5ed9570a89675f140ca8b2ad/rignore-0.7.6-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:690a3e1b54bfe77e89c4bacb13f046e642f8baadafc61d68f5a726f324a76ab6", size = 874144, upload-time = "2025-11-05T20:41:10.195Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/bf/0ce9beb2e5f64c30e3580bef09f5829236889f01511a125f98b83169b993/rignore-0.7.6-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09d12ac7a0b6210c07bcd145007117ebd8abe99c8eeb383e9e4673910c2754b2", size = 1168062, upload-time = "2025-11-05T20:41:26.511Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/8b/571c178414eb4014969865317da8a02ce4cf5241a41676ef91a59aab24de/rignore-0.7.6-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a2b2b74a8c60203b08452479b90e5ce3dbe96a916214bc9eb2e5af0b6a9beb0", size = 942542, upload-time = "2025-11-05T20:41:41.838Z" },
+ { url = "https://files.pythonhosted.org/packages/19/62/7a3cf601d5a45137a7e2b89d10c05b5b86499190c4b7ca5c3c47d79ee519/rignore-0.7.6-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc5a531ef02131e44359419a366bfac57f773ea58f5278c2cdd915f7d10ea94", size = 958739, upload-time = "2025-11-05T20:42:12.463Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/1f/4261f6a0d7caf2058a5cde2f5045f565ab91aa7badc972b57d19ce58b14e/rignore-0.7.6-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7a1f77d9c4cd7e76229e252614d963442686bfe12c787a49f4fe481df49e7a9", size = 984138, upload-time = "2025-11-05T20:41:56.775Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/bf/628dfe19c75e8ce1f45f7c248f5148b17dfa89a817f8e3552ab74c3ae812/rignore-0.7.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ead81f728682ba72b5b1c3d5846b011d3e0174da978de87c61645f2ed36659a7", size = 1079299, upload-time = "2025-11-05T21:40:16.639Z" },
+ { url = "https://files.pythonhosted.org/packages/af/a5/be29c50f5c0c25c637ed32db8758fdf5b901a99e08b608971cda8afb293b/rignore-0.7.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:12ffd50f520c22ffdabed8cd8bfb567d9ac165b2b854d3e679f4bcaef11a9441", size = 1139618, upload-time = "2025-11-05T21:40:34.507Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/40/3c46cd7ce4fa05c20b525fd60f599165e820af66e66f2c371cd50644558f/rignore-0.7.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e5a16890fbe3c894f8ca34b0fcacc2c200398d4d46ae654e03bc9b3dbf2a0a72", size = 1117626, upload-time = "2025-11-05T21:40:51.494Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/b9/aea926f263b8a29a23c75c2e0d8447965eb1879d3feb53cfcf84db67ed58/rignore-0.7.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3abab3bf99e8a77488ef6c7c9a799fac22224c28fe9f25cc21aa7cc2b72bfc0b", size = 1128144, upload-time = "2025-11-05T21:41:09.169Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/f6/0d6242f8d0df7f2ecbe91679fefc1f75e7cd2072cb4f497abaab3f0f8523/rignore-0.7.6-cp314-cp314-win32.whl", hash = "sha256:eeef421c1782953c4375aa32f06ecae470c1285c6381eee2a30d2e02a5633001", size = 646385, upload-time = "2025-11-05T21:41:55.105Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/38/c0dcd7b10064f084343d6af26fe9414e46e9619c5f3224b5272e8e5d9956/rignore-0.7.6-cp314-cp314-win_amd64.whl", hash = "sha256:6aeed503b3b3d5af939b21d72a82521701a4bd3b89cd761da1e7dc78621af304", size = 725738, upload-time = "2025-11-05T21:41:39.736Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/7a/290f868296c1ece914d565757ab363b04730a728b544beb567ceb3b2d96f/rignore-0.7.6-cp314-cp314-win_arm64.whl", hash = "sha256:104f215b60b3c984c386c3e747d6ab4376d5656478694e22c7bd2f788ddd8304", size = 656008, upload-time = "2025-11-05T21:41:29.028Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/d2/3c74e3cd81fe8ea08a8dcd2d755c09ac2e8ad8fe409508904557b58383d3/rignore-0.7.6-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bb24a5b947656dd94cb9e41c4bc8b23cec0c435b58be0d74a874f63c259549e8", size = 882835, upload-time = "2025-11-05T20:42:45.443Z" },
+ { url = "https://files.pythonhosted.org/packages/77/61/a772a34b6b63154877433ac2d048364815b24c2dd308f76b212c408101a2/rignore-0.7.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b1e33c9501cefe24b70a1eafd9821acfd0ebf0b35c3a379430a14df089993e3", size = 820301, upload-time = "2025-11-05T20:42:29.226Z" },
+ { url = "https://files.pythonhosted.org/packages/71/30/054880b09c0b1b61d17eeb15279d8bf729c0ba52b36c3ada52fb827cbb3c/rignore-0.7.6-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bec3994665a44454df86deb762061e05cd4b61e3772f5b07d1882a8a0d2748d5", size = 897611, upload-time = "2025-11-05T20:40:56.475Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/40/b2d1c169f833d69931bf232600eaa3c7998ba4f9a402e43a822dad2ea9f2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26cba2edfe3cff1dfa72bddf65d316ddebf182f011f2f61538705d6dbaf54986", size = 873875, upload-time = "2025-11-05T20:41:11.561Z" },
+ { url = "https://files.pythonhosted.org/packages/55/59/ca5ae93d83a1a60e44b21d87deb48b177a8db1b85e82fc8a9abb24a8986d/rignore-0.7.6-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffa86694fec604c613696cb91e43892aa22e1fec5f9870e48f111c603e5ec4e9", size = 1167245, upload-time = "2025-11-05T20:41:28.29Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/52/cf3dce392ba2af806cba265aad6bcd9c48bb2a6cb5eee448d3319f6e505b/rignore-0.7.6-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48efe2ed95aa8104145004afb15cdfa02bea5cdde8b0344afeb0434f0d989aa2", size = 941750, upload-time = "2025-11-05T20:41:43.111Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/be/3f344c6218d779395e785091d05396dfd8b625f6aafbe502746fcd880af2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dcae43eb44b7f2457fef7cc87f103f9a0013017a6f4e62182c565e924948f21", size = 958896, upload-time = "2025-11-05T20:42:13.784Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/34/d3fa71938aed7d00dcad87f0f9bcb02ad66c85d6ffc83ba31078ce53646a/rignore-0.7.6-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2cd649a7091c0dad2f11ef65630d30c698d505cbe8660dd395268e7c099cc99f", size = 983992, upload-time = "2025-11-05T20:41:58.022Z" },
+ { url = "https://files.pythonhosted.org/packages/24/a4/52a697158e9920705bdbd0748d59fa63e0f3233fb92e9df9a71afbead6ca/rignore-0.7.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42de84b0289d478d30ceb7ae59023f7b0527786a9a5b490830e080f0e4ea5aeb", size = 1078181, upload-time = "2025-11-05T21:40:18.151Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/65/aa76dbcdabf3787a6f0fd61b5cc8ed1e88580590556d6c0207960d2384bb/rignore-0.7.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:875a617e57b53b4acbc5a91de418233849711c02e29cc1f4f9febb2f928af013", size = 1139232, upload-time = "2025-11-05T21:40:35.966Z" },
+ { url = "https://files.pythonhosted.org/packages/08/44/31b31a49b3233c6842acc1c0731aa1e7fb322a7170612acf30327f700b44/rignore-0.7.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8703998902771e96e49968105207719f22926e4431b108450f3f430b4e268b7c", size = 1117349, upload-time = "2025-11-05T21:40:53.013Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/ae/1b199a2302c19c658cf74e5ee1427605234e8c91787cfba0015f2ace145b/rignore-0.7.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:602ef33f3e1b04c1e9a10a3c03f8bc3cef2d2383dcc250d309be42b49923cabc", size = 1127702, upload-time = "2025-11-05T21:41:10.881Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/d3/18210222b37e87e36357f7b300b7d98c6dd62b133771e71ae27acba83a4f/rignore-0.7.6-cp314-cp314t-win32.whl", hash = "sha256:c1d8f117f7da0a4a96a8daef3da75bc090e3792d30b8b12cfadc240c631353f9", size = 647033, upload-time = "2025-11-05T21:42:00.095Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/87/033eebfbee3ec7d92b3bb1717d8f68c88e6fc7de54537040f3b3a405726f/rignore-0.7.6-cp314-cp314t-win_amd64.whl", hash = "sha256:ca36e59408bec81de75d307c568c2d0d410fb880b1769be43611472c61e85c96", size = 725647, upload-time = "2025-11-05T21:41:44.449Z" },
+ { url = "https://files.pythonhosted.org/packages/79/62/b88e5879512c55b8ee979c666ee6902adc4ed05007226de266410ae27965/rignore-0.7.6-cp314-cp314t-win_arm64.whl", hash = "sha256:b83adabeb3e8cf662cabe1931b83e165b88c526fa6af6b3aa90429686e474896", size = 656035, upload-time = "2025-11-05T21:41:31.13Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/b4/e7577504d926ced2d6a3fa5ec5f27756639a1ed58a6a3fbefcf3a5659721/rignore-0.7.6-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b3746bda73f2fe6a9c3ab2f20b792e7d810b30acbdba044313fbd2d0174802e7", size = 886535, upload-time = "2025-11-05T20:42:49.317Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/74/098bc71a33e2997bc3291d500760123d23e3a6d354380d26c8a7ddc036de/rignore-0.7.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:67a99cf19a5137cc12f14b78dc1bb3f48500f1d5580702c623297d5297bf2752", size = 826621, upload-time = "2025-11-05T20:42:32.421Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/73/5f8c276d71009a7e73fb3af6ec3bb930269efeae5830de5c796fa1fb020f/rignore-0.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9e851cfa87033c0c3fd9d35dd8b102aff2981db8bc6e0cab27b460bfe38bf3f", size = 900335, upload-time = "2025-11-05T20:40:59.178Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/5f/dde3758084a087e6a5cd981c5277c6171d12127deed64fc4fbf12fb8ceaa/rignore-0.7.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e9b0def154665036516114437a5d603274e5451c0dc9694f622cc3b7e94603e7", size = 874274, upload-time = "2025-11-05T20:41:14.512Z" },
+ { url = "https://files.pythonhosted.org/packages/58/b9/da85646824ab728036378ce62c330316108a52f30f36e6c69cac6ceda376/rignore-0.7.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b81274a47e8121224f7f637392b5dfcd9558e32a53e67ba7d04007d8b5281da9", size = 1171639, upload-time = "2025-11-05T20:41:31.206Z" },
+ { url = "https://files.pythonhosted.org/packages/35/d1/8c12b779b7f0302c03c1d41511f2ab47012afecdfcd684fbec80af06b331/rignore-0.7.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d75d0b0696fb476664bea1169c8e67b13197750b91eceb4f10b3c7f379c7a204", size = 943985, upload-time = "2025-11-05T20:41:45.598Z" },
+ { url = "https://files.pythonhosted.org/packages/79/bf/c233a85d31e4f94b911e92ee7e2dd2b962a5c2528f5ebd79a702596f0626/rignore-0.7.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ad3aa4dca77cef9168d0c142f72376f5bd27d1d4b8a81561bd01276d3ad9fe1", size = 961707, upload-time = "2025-11-05T20:42:16.461Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/eb/cadee9316a5f2a52b4aa7051967ecb94ec17938d6b425bd842d9317559eb/rignore-0.7.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00f8a59e19d219f44a93af7173de197e0d0e61c386364da20ebe98a303cbe38c", size = 986638, upload-time = "2025-11-05T20:42:00.65Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/f0/2c3042c8c9639056593def5e99c3bfe850fbb9a38d061ba67b6314315bad/rignore-0.7.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd6c682f3cdd741e7a30af2581f6a382ac910080977cd1f97c651467b6268352", size = 1080136, upload-time = "2025-11-05T21:40:21.551Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/28/7237b9eb1257b593ee51cd7ef8eed7cc32f50ccff18cb4d7cfe1e6dc54d7/rignore-0.7.6-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ae4e93193f75ebf6b820241594a78f347785cfd5a5fbbac94634052589418352", size = 1139413, upload-time = "2025-11-05T21:40:39.025Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/df/c3f382a31ad7ed68510b411c28fec42354d2c43fecb7c053d998ee9410ed/rignore-0.7.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1163d8b5d3a320d4d7cc8635213328850dc41f60e438c7869d540061adf66c98", size = 1120204, upload-time = "2025-11-05T21:40:56.062Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/3d/e8585c4e9c0077255ba599684aee78326176ab13ff13805ea62aa7e3235f/rignore-0.7.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3e685f47b4c58b2df7dee81ebc1ec9dbb7f798b9455c3f22be6d75ac6bddee30", size = 1129757, upload-time = "2025-11-05T21:41:14.148Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/56/852226c13f89ddbbf12d639900941dc55dcbcf79f5d15294796fd3279d73/rignore-0.7.6-cp39-cp39-win32.whl", hash = "sha256:2af6a0a76575220863cd838693c808a94e750640e0c8a3e9f707e93c2f131fdf", size = 648265, upload-time = "2025-11-05T21:41:58.589Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/c6/14e7585dc453a870fe99b1270ee95e2adff02ea0d297cd6e2c4aa46cd43a/rignore-0.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:a326eab6db9ab85b4afb5e6eb28736a9f2b885a9246d9e8c1989bc693dd059a0", size = 728715, upload-time = "2025-11-05T21:41:42.823Z" },
+ { url = "https://files.pythonhosted.org/packages/85/12/62d690b4644c330d7ac0f739b7f078190ab4308faa909a60842d0e4af5b2/rignore-0.7.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3d3a523af1cd4ed2c0cba8d277a32d329b0c96ef9901fb7ca45c8cfaccf31a5", size = 887462, upload-time = "2025-11-05T20:42:50.804Z" },
+ { url = "https://files.pythonhosted.org/packages/05/bc/6528a0e97ed2bd7a7c329183367d1ffbc5b9762ae8348d88dae72cc9d1f5/rignore-0.7.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:990853566e65184a506e1e2af2d15045afad3ebaebb8859cb85b882081915110", size = 826918, upload-time = "2025-11-05T20:42:33.689Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/2c/7d7bad116e09a04e9e1688c6f891fa2d4fd33f11b69ac0bd92419ddebeae/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cab9ff2e436ce7240d7ee301c8ef806ed77c1fd6b8a8239ff65f9bbbcb5b8a3", size = 900922, upload-time = "2025-11-05T20:41:00.361Z" },
+ { url = "https://files.pythonhosted.org/packages/09/ba/e5ea89fbde8e37a90ce456e31c5e9d85512cef5ae38e0f4d2426eb776a19/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1a6671b2082c13bfd9a5cf4ce64670f832a6d41470556112c4ab0b6519b2fc4", size = 876987, upload-time = "2025-11-05T20:41:16.219Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/fb/93d14193f0ec0c3d35b763f0a000e9780f63b2031f3d3756442c2152622d/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2468729b4c5295c199d084ab88a40afcb7c8b974276805105239c07855bbacee", size = 1171110, upload-time = "2025-11-05T20:41:32.631Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/46/08436312ff96ffa29cfa4e1a987efc37e094531db46ba5e9fda9bb792afd/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:775710777fd71e5fdf54df69cdc249996a1d6f447a2b5bfb86dbf033fddd9cf9", size = 943339, upload-time = "2025-11-05T20:41:47.128Z" },
+ { url = "https://files.pythonhosted.org/packages/34/28/3b3c51328f505cfaf7e53f408f78a1e955d561135d02f9cb0341ea99f69a/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4565407f4a77f72cf9d91469e75d15d375f755f0a01236bb8aaa176278cc7085", size = 961680, upload-time = "2025-11-05T20:42:18.061Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/9e/cbff75c8676d4f4a90bd58a1581249d255c7305141b0868f0abc0324836b/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc44c33f8fb2d5c9da748de7a6e6653a78aa740655e7409895e94a247ffa97c8", size = 987045, upload-time = "2025-11-05T20:42:02.315Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/25/d802d1d369502a7ddb8816059e7c79d2d913e17df975b863418e0aca4d8a/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8f32478f05540513c11923e8838afab9efef0131d66dca7f67f0e1bbd118af6a", size = 1080310, upload-time = "2025-11-05T21:40:23.184Z" },
+ { url = "https://files.pythonhosted.org/packages/43/f0/250b785c2e473b1ab763eaf2be820934c2a5409a722e94b279dddac21c7d/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:1b63a3dd76225ea35b01dd6596aa90b275b5d0f71d6dc28fce6dd295d98614aa", size = 1140998, upload-time = "2025-11-05T21:40:40.603Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/d6/bb42fd2a8bba6aea327962656e20621fd495523259db40cfb4c5f760f05c/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:fe6c41175c36554a4ef0994cd1b4dbd6d73156fca779066456b781707402048e", size = 1121178, upload-time = "2025-11-05T21:40:57.585Z" },
+ { url = "https://files.pythonhosted.org/packages/97/f4/aeb548374129dce3dc191a4bb598c944d9ed663f467b9af830315d86059c/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a0c6792406ae36f4e7664dc772da909451d46432ff8485774526232d4885063", size = 1130190, upload-time = "2025-11-05T21:41:16.403Z" },
+ { url = "https://files.pythonhosted.org/packages/82/78/a6250ff0c49a3cdb943910ada4116e708118e9b901c878cfae616c80a904/rignore-0.7.6-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a20b6fb61bcced9a83dfcca6599ad45182b06ba720cff7c8d891e5b78db5b65f", size = 886470, upload-time = "2025-11-05T20:42:52.314Z" },
+ { url = "https://files.pythonhosted.org/packages/35/af/c69c0c51b8f9f7914d95c4ea91c29a2ac067572048cae95dd6d2efdbe05d/rignore-0.7.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:392dcabfecbe176c9ebbcb40d85a5e86a5989559c4f988c2741da7daf1b5be25", size = 825976, upload-time = "2025-11-05T20:42:35.118Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/d2/1b264f56132264ea609d3213ab603d6a27016b19559a1a1ede1a66a03dcd/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22baa462abdc36fdd5a5e2dae423107723351b85ff093762f9261148b9d0a04a", size = 899739, upload-time = "2025-11-05T20:41:01.518Z" },
+ { url = "https://files.pythonhosted.org/packages/55/e4/b3c5dfdd8d8a10741dfe7199ef45d19a0e42d0c13aa377c83bd6caf65d90/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53fb28882d2538cb2d231972146c4927a9d9455e62b209f85d634408c4103538", size = 874843, upload-time = "2025-11-05T20:41:17.687Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/10/d6f3750233881a2a154cefc9a6a0a9b19da526b19f7f08221b552c6f827d/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87409f7eeb1103d6b77f3472a3a0d9a5953e3ae804a55080bdcb0120ee43995b", size = 1170348, upload-time = "2025-11-05T20:41:34.21Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/10/ad98ca05c9771c15af734cee18114a3c280914b6e34fde9ffea2e61e88aa/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684014e42e4341ab3ea23a203551857fcc03a7f8ae96ca3aefb824663f55db32", size = 942315, upload-time = "2025-11-05T20:41:48.508Z" },
+ { url = "https://files.pythonhosted.org/packages/de/00/ab5c0f872acb60d534e687e629c17e0896c62da9b389c66d3aa16b817aa8/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77356ebb01ba13f8a425c3d30fcad40e57719c0e37670d022d560884a30e4767", size = 961047, upload-time = "2025-11-05T20:42:19.403Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/86/3030fdc363a8f0d1cd155b4c453d6db9bab47a24fcc64d03f61d9d78fe6a/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6cbd8a48abbd3747a6c830393cd578782fab5d43f4deea48c5f5e344b8fed2b0", size = 986090, upload-time = "2025-11-05T20:42:03.581Z" },
+ { url = "https://files.pythonhosted.org/packages/33/b8/133aa4002cee0ebbb39362f94e4898eec7fbd09cec9fcbce1cd65b355b7f/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2673225dcec7f90497e79438c35e34638d0d0391ccea3cbb79bfb9adc0dc5bd7", size = 1079656, upload-time = "2025-11-05T21:40:24.89Z" },
+ { url = "https://files.pythonhosted.org/packages/67/56/36d5d34210e5e7dfcd134eed8335b19e80ae940ee758f493e4f2b344dd70/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c081f17290d8a2b96052b79207622aa635686ea39d502b976836384ede3d303c", size = 1139789, upload-time = "2025-11-05T21:40:42.119Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/5b/bb4f9420802bf73678033a4a55ab1bede36ce2e9b41fec5f966d83d932b3/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:57e8327aacc27f921968cb2a174f9e47b084ce9a7dd0122c8132d22358f6bd79", size = 1120308, upload-time = "2025-11-05T21:40:59.402Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/8b/a1299085b28a2f6135e30370b126e3c5055b61908622f2488ade67641479/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d8955b57e42f2a5434670d5aa7b75eaf6e74602ccd8955dddf7045379cd762fb", size = 1129444, upload-time = "2025-11-05T21:41:17.906Z" },
+ { url = "https://files.pythonhosted.org/packages/47/98/80ef6fda78161e88ef9336fcbe851afccf78c48e69e8266a23fb7922b5aa/rignore-0.7.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e6ba1511c0ab8cd1ed8d6055bb0a6e629f48bfe04854293e0cd2dd88bd7153f8", size = 887180, upload-time = "2025-11-05T21:40:07.665Z" },
+ { url = "https://files.pythonhosted.org/packages/21/d7/8666e7081f8476b003d8d2c8f39ecc17c93b7efd261740d15b6830acde82/rignore-0.7.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:50586d90be15f9aa8a2e2ee5a042ee6c51e28848812a35f0c95d4bfc0533d469", size = 827029, upload-time = "2025-11-05T20:42:36.628Z" },
+ { url = "https://files.pythonhosted.org/packages/01/aa/3aba657d17b1737f4180b143866fedd269de15f361a8cb26ba363c0c3c13/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b129873dd0ade248e67f25a09b5b72288cbef76ba1a9aae6bac193ee1d8be72", size = 901338, upload-time = "2025-11-05T20:41:03.059Z" },
+ { url = "https://files.pythonhosted.org/packages/90/cc/d8c2c9770f5f61b28999c582804f282f2227c155ba13dfb0e9ea03daeaaf/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9d6dd947556ddebfd62753005104986ee14a4e0663818aed19cdf2c33a6b5d5", size = 877563, upload-time = "2025-11-05T20:41:19.209Z" },
+ { url = "https://files.pythonhosted.org/packages/55/63/42dd625bf96989be4a928b5444ddec9101ee63a98a15646e611b3ce58b82/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91b95faa532efba888b196331e9af69e693635d469185ac52c796e435e2484e5", size = 1171087, upload-time = "2025-11-05T20:41:35.558Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/1e/4130fb622c2081c5322caf7a8888d1d265b99cd5d62cb714b512b8911233/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1016f430fb56f7e400838bbc56fdf43adddb6fcb7bf2a14731dfd725c2fae6c", size = 944335, upload-time = "2025-11-05T20:41:49.859Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/b9/3d3ef7773da85e002fab53b1fdd9e9bb111cc86792b761cb38bd00c1532e/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f00c519861926dc703ecbb7bbeb884be67099f96f98b175671fa0a54718f55d1", size = 961500, upload-time = "2025-11-05T20:42:20.798Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/bc/346c874a31a721064935c60666a19016b6b01cd716cf73d52dc64e467b30/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e34d172bf50e881b7c02e530ae8b1ea96093f0b16634c344f637227b39707b41", size = 987741, upload-time = "2025-11-05T20:42:05.071Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/b8/d12dc548da8fdb63292a38727b035153495220cd93730019ee8ed3bdcffb/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:101d3143619898db1e7bede2e3e647daf19bb867c4fb25978016d67978d14868", size = 1081057, upload-time = "2025-11-05T21:40:26.53Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/51/7eea5d949212709740ad07e01c524336e44608ef0614a2a1cb31c9a0ea30/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c9f3b420f54199a2b2b3b532d8c7e0860be3fa51f67501113cca6c7bfc392840", size = 1141653, upload-time = "2025-11-05T21:40:43.676Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/2b/76ec843cc392fcb4e37d6a8340e823a0bf644872e191d2f5652a4c2c18ee/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:1c6795e3694d750ae5ef172eab7d68a52aefbd9168d2e06647df691db2b03a50", size = 1121465, upload-time = "2025-11-05T21:41:00.904Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/9d/e69ad5cf03211a1076f9fe04ca2698c9cb8208b63419c928c26646bdf1d9/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:750a83a254b020e1193bfa7219dc7edca26bd8888a94cdc59720cbe386ab0c72", size = 1130110, upload-time = "2025-11-05T21:41:20.263Z" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "0.30.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" },
+ { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" },
+ { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" },
+ { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" },
+ { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" },
+ { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" },
+ { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" },
+ { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" },
+ { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" },
+ { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" },
+ { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" },
+ { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" },
+ { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" },
+ { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" },
+ { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" },
+ { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" },
+ { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" },
+ { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" },
+ { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" },
+ { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" },
+ { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" },
+ { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" },
+ { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" },
+ { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" },
+ { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" },
+ { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" },
+ { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" },
+ { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" },
+ { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" },
+ { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" },
+ { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" },
+ { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" },
+ { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" },
+ { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" },
+ { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" },
+ { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" },
+ { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" },
+ { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" },
+ { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" },
+ { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" },
+ { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" },
+ { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" },
+ { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" },
+ { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" },
+ { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" },
+ { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" },
+ { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" },
+ { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" },
+ { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" },
+ { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" },
+]
+
+[[package]]
+name = "rsa"
+version = "4.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyasn1" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.14.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/75/62/50b7727004dfe361104dfbf898c45a9a2fdfad8c72c04ae62900224d6ecf/ruff-0.14.3.tar.gz", hash = "sha256:4ff876d2ab2b161b6de0aa1f5bd714e8e9b4033dc122ee006925fbacc4f62153", size = 5558687, upload-time = "2025-10-31T00:26:26.878Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ce/8e/0c10ff1ea5d4360ab8bfca4cb2c9d979101a391f3e79d2616c9bf348cd26/ruff-0.14.3-py3-none-linux_armv6l.whl", hash = "sha256:876b21e6c824f519446715c1342b8e60f97f93264012de9d8d10314f8a79c371", size = 12535613, upload-time = "2025-10-31T00:25:44.302Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/c8/6724f4634c1daf52409fbf13fefda64aa9c8f81e44727a378b7b73dc590b/ruff-0.14.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6fd8c79b457bedd2abf2702b9b472147cd860ed7855c73a5247fa55c9117654", size = 12855812, upload-time = "2025-10-31T00:25:47.793Z" },
+ { url = "https://files.pythonhosted.org/packages/de/03/db1bce591d55fd5f8a08bb02517fa0b5097b2ccabd4ea1ee29aa72b67d96/ruff-0.14.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:71ff6edca490c308f083156938c0c1a66907151263c4abdcb588602c6e696a14", size = 11944026, upload-time = "2025-10-31T00:25:49.657Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/75/4f8dbd48e03272715d12c87dc4fcaaf21b913f0affa5f12a4e9c6f8a0582/ruff-0.14.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:786ee3ce6139772ff9272aaf43296d975c0217ee1b97538a98171bf0d21f87ed", size = 12356818, upload-time = "2025-10-31T00:25:51.949Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/9b/506ec5b140c11d44a9a4f284ea7c14ebf6f8b01e6e8917734a3325bff787/ruff-0.14.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cd6291d0061811c52b8e392f946889916757610d45d004e41140d81fb6cd5ddc", size = 12336745, upload-time = "2025-10-31T00:25:54.248Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/e1/c560d254048c147f35e7f8131d30bc1f63a008ac61595cf3078a3e93533d/ruff-0.14.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a497ec0c3d2c88561b6d90f9c29f5ae68221ac00d471f306fa21fa4264ce5fcd", size = 13101684, upload-time = "2025-10-31T00:25:56.253Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/32/e310133f8af5cd11f8cc30f52522a3ebccc5ea5bff4b492f94faceaca7a8/ruff-0.14.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e231e1be58fc568950a04fbe6887c8e4b85310e7889727e2b81db205c45059eb", size = 14535000, upload-time = "2025-10-31T00:25:58.397Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/a1/7b0470a22158c6d8501eabc5e9b6043c99bede40fa1994cadf6b5c2a61c7/ruff-0.14.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:469e35872a09c0e45fecf48dd960bfbce056b5db2d5e6b50eca329b4f853ae20", size = 14156450, upload-time = "2025-10-31T00:26:00.889Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/96/24bfd9d1a7f532b560dcee1a87096332e461354d3882124219bcaff65c09/ruff-0.14.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d6bc90307c469cb9d28b7cfad90aaa600b10d67c6e22026869f585e1e8a2db0", size = 13568414, upload-time = "2025-10-31T00:26:03.291Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/e7/138b883f0dfe4ad5b76b58bf4ae675f4d2176ac2b24bdd81b4d966b28c61/ruff-0.14.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2f8a0bbcffcfd895df39c9a4ecd59bb80dca03dc43f7fb63e647ed176b741e", size = 13315293, upload-time = "2025-10-31T00:26:05.708Z" },
+ { url = "https://files.pythonhosted.org/packages/33/f4/c09bb898be97b2eb18476b7c950df8815ef14cf956074177e9fbd40b7719/ruff-0.14.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:678fdd7c7d2d94851597c23ee6336d25f9930b460b55f8598e011b57c74fd8c5", size = 13539444, upload-time = "2025-10-31T00:26:08.09Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/aa/b30a1db25fc6128b1dd6ff0741fa4abf969ded161599d07ca7edd0739cc0/ruff-0.14.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1ec1ac071e7e37e0221d2f2dbaf90897a988c531a8592a6a5959f0603a1ecf5e", size = 12252581, upload-time = "2025-10-31T00:26:10.297Z" },
+ { url = "https://files.pythonhosted.org/packages/da/13/21096308f384d796ffe3f2960b17054110a9c3828d223ca540c2b7cc670b/ruff-0.14.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afcdc4b5335ef440d19e7df9e8ae2ad9f749352190e96d481dc501b753f0733e", size = 12307503, upload-time = "2025-10-31T00:26:12.646Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/cc/a350bac23f03b7dbcde3c81b154706e80c6f16b06ff1ce28ed07dc7b07b0/ruff-0.14.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7bfc42f81862749a7136267a343990f865e71fe2f99cf8d2958f684d23ce3dfa", size = 12675457, upload-time = "2025-10-31T00:26:15.044Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/76/46346029fa2f2078826bc88ef7167e8c198e58fe3126636e52f77488cbba/ruff-0.14.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a65e448cfd7e9c59fae8cf37f9221585d3354febaad9a07f29158af1528e165f", size = 13403980, upload-time = "2025-10-31T00:26:17.81Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/a4/35f1ef68c4e7b236d4a5204e3669efdeefaef21f0ff6a456792b3d8be438/ruff-0.14.3-py3-none-win32.whl", hash = "sha256:f3d91857d023ba93e14ed2d462ab62c3428f9bbf2b4fbac50a03ca66d31991f7", size = 12500045, upload-time = "2025-10-31T00:26:20.503Z" },
+ { url = "https://files.pythonhosted.org/packages/03/15/51960ae340823c9859fb60c63301d977308735403e2134e17d1d2858c7fb/ruff-0.14.3-py3-none-win_amd64.whl", hash = "sha256:d7b7006ac0756306db212fd37116cce2bd307e1e109375e1c6c106002df0ae5f", size = 13594005, upload-time = "2025-10-31T00:26:22.533Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/73/4de6579bac8e979fca0a77e54dec1f1e011a0d268165eb8a9bc0982a6564/ruff-0.14.3-py3-none-win_arm64.whl", hash = "sha256:26eb477ede6d399d898791d01961e16b86f02bc2486d0d1a7a9bb2379d055dc1", size = 12590017, upload-time = "2025-10-31T00:26:24.52Z" },
+]
+
+[[package]]
+name = "s3transfer"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "botocore" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" },
+]
+
+[[package]]
+name = "sentry-sdk"
+version = "2.49.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/02/94/23ac26616a883f492428d9ee9ad6eee391612125326b784dbfc30e1e7bab/sentry_sdk-2.49.0.tar.gz", hash = "sha256:c1878599cde410d481c04ef50ee3aedd4f600e4d0d253f4763041e468b332c30", size = 387228, upload-time = "2026-01-08T09:56:25.642Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/43/1c586f9f413765201234541857cb82fda076f4b0f7bad4a0ec248da39cf3/sentry_sdk-2.49.0-py2.py3-none-any.whl", hash = "sha256:6ea78499133874445a20fe9c826c9e960070abeb7ae0cdf930314ab16bb97aa0", size = 415693, upload-time = "2026-01-08T09:56:21.872Z" },
+]
+
+[[package]]
+name = "shellingham"
+version = "1.5.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "smmap"
+version = "5.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" },
+]
+
+[[package]]
+name = "smokeshow"
+version = "0.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "httpx" },
+ { name = "typer" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/75/94/c99b76517c268ef8d5c2ff88faba5a019664bd69e4754944afa294b4f24c/smokeshow-0.5.0.tar.gz", hash = "sha256:91dcabc29ac3116bff59b4d8a7bda4ae3ccc4c70742a38cec7127b8162e4a0f6", size = 101349, upload-time = "2025-01-07T19:41:51.732Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/77/10/0d23e4953eb7c1e1ad848084b3115f19234f34f907658ed11bed0d826aee/smokeshow-0.5.0-py3-none-any.whl", hash = "sha256:da12a960fc7cb525efc4035a0c3c9363b6217ea7e66bc39b9ed3cd8bed6eeedc", size = 8389, upload-time = "2025-01-07T19:41:49.194Z" },
+]
+
+[[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 = "sortedcontainers"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
+]
+
+[[package]]
+name = "sqlalchemy"
+version = "2.0.45"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "greenlet", version = "3.2.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.10' and platform_machine == 'AMD64') or (python_full_version < '3.10' and platform_machine == 'WIN32') or (python_full_version < '3.10' and platform_machine == 'aarch64') or (python_full_version < '3.10' and platform_machine == 'amd64') or (python_full_version < '3.10' and platform_machine == 'ppc64le') or (python_full_version < '3.10' and platform_machine == 'win32') or (python_full_version < '3.10' and platform_machine == 'x86_64')" },
+ { name = "greenlet", version = "3.3.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and platform_machine == 'AMD64') or (python_full_version >= '3.10' and platform_machine == 'WIN32') or (python_full_version >= '3.10' and platform_machine == 'aarch64') or (python_full_version >= '3.10' and platform_machine == 'amd64') or (python_full_version >= '3.10' and platform_machine == 'ppc64le') or (python_full_version >= '3.10' and platform_machine == 'win32') or (python_full_version >= '3.10' and platform_machine == 'x86_64')" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/be/f9/5e4491e5ccf42f5d9cfc663741d261b3e6e1683ae7812114e7636409fcc6/sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88", size = 9869912, upload-time = "2025-12-09T21:05:16.737Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fe/70/75b1387d72e2847220441166c5eb4e9846dd753895208c13e6d66523b2d9/sqlalchemy-2.0.45-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c64772786d9eee72d4d3784c28f0a636af5b0a29f3fe26ff11f55efe90c0bd85", size = 2154148, upload-time = "2025-12-10T20:03:21.023Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/a4/7805e02323c49cb9d1ae5cd4913b28c97103079765f520043f914fca4cb3/sqlalchemy-2.0.45-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ae64ebf7657395824a19bca98ab10eb9a3ecb026bf09524014f1bb81cb598d4", size = 3233051, upload-time = "2025-12-09T22:06:04.768Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ec/32ae09139f61bef3de3142e85c47abdee8db9a55af2bb438da54a4549263/sqlalchemy-2.0.45-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f02325709d1b1a1489f23a39b318e175a171497374149eae74d612634b234c0", size = 3232781, upload-time = "2025-12-09T22:09:54.435Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/bd/bf7b869b6f5585eac34222e1cf4405f4ba8c3b85dd6b1af5d4ce8bca695f/sqlalchemy-2.0.45-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2c3684fca8a05f0ac1d9a21c1f4a266983a7ea9180efb80ffeb03861ecd01a0", size = 3182096, upload-time = "2025-12-09T22:06:06.169Z" },
+ { url = "https://files.pythonhosted.org/packages/21/6a/c219720a241bb8f35c88815ccc27761f5af7fdef04b987b0e8a2c1a6dcaa/sqlalchemy-2.0.45-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040f6f0545b3b7da6b9317fc3e922c9a98fc7243b2a1b39f78390fc0942f7826", size = 3205109, upload-time = "2025-12-09T22:09:55.969Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/c4/6ccf31b2bc925d5d95fab403ffd50d20d7c82b858cf1a4855664ca054dce/sqlalchemy-2.0.45-cp310-cp310-win32.whl", hash = "sha256:830d434d609fe7bfa47c425c445a8b37929f140a7a44cdaf77f6d34df3a7296a", size = 2114240, upload-time = "2025-12-09T21:29:54.007Z" },
+ { url = "https://files.pythonhosted.org/packages/de/29/a27a31fca07316def418db6f7c70ab14010506616a2decef1906050a0587/sqlalchemy-2.0.45-cp310-cp310-win_amd64.whl", hash = "sha256:0209d9753671b0da74da2cfbb9ecf9c02f72a759e4b018b3ab35f244c91842c7", size = 2137615, upload-time = "2025-12-09T21:29:55.85Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/1c/769552a9d840065137272ebe86ffbb0bc92b0f1e0a68ee5266a225f8cd7b/sqlalchemy-2.0.45-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e90a344c644a4fa871eb01809c32096487928bd2038bf10f3e4515cb688cc56", size = 2153860, upload-time = "2025-12-10T20:03:23.843Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/f8/9be54ff620e5b796ca7b44670ef58bc678095d51b0e89d6e3102ea468216/sqlalchemy-2.0.45-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8c8b41b97fba5f62349aa285654230296829672fc9939cd7f35aab246d1c08b", size = 3309379, upload-time = "2025-12-09T22:06:07.461Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/2b/60ce3ee7a5ae172bfcd419ce23259bb874d2cddd44f67c5df3760a1e22f9/sqlalchemy-2.0.45-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12c694ed6468333a090d2f60950e4250b928f457e4962389553d6ba5fe9951ac", size = 3309948, upload-time = "2025-12-09T22:09:57.643Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/42/bac8d393f5db550e4e466d03d16daaafd2bad1f74e48c12673fb499a7fc1/sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f7d27a1d977a1cfef38a0e2e1ca86f09c4212666ce34e6ae542f3ed0a33bc606", size = 3261239, upload-time = "2025-12-09T22:06:08.879Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/12/43dc70a0528c59842b04ea1c1ed176f072a9b383190eb015384dd102fb19/sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d62e47f5d8a50099b17e2bfc1b0c7d7ecd8ba6b46b1507b58cc4f05eefc3bb1c", size = 3284065, upload-time = "2025-12-09T22:09:59.454Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/9c/563049cf761d9a2ec7bc489f7879e9d94e7b590496bea5bbee9ed7b4cc32/sqlalchemy-2.0.45-cp311-cp311-win32.whl", hash = "sha256:3c5f76216e7b85770d5bb5130ddd11ee89f4d52b11783674a662c7dd57018177", size = 2113480, upload-time = "2025-12-09T21:29:57.03Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/fa/09d0a11fe9f15c7fa5c7f0dd26be3d235b0c0cbf2f9544f43bc42efc8a24/sqlalchemy-2.0.45-cp311-cp311-win_amd64.whl", hash = "sha256:a15b98adb7f277316f2c276c090259129ee4afca783495e212048daf846654b2", size = 2138407, upload-time = "2025-12-09T21:29:58.556Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/c7/1900b56ce19bff1c26f39a4ce427faec7716c81ac792bfac8b6a9f3dca93/sqlalchemy-2.0.45-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3ee2aac15169fb0d45822983631466d60b762085bc4535cd39e66bea362df5f", size = 3333760, upload-time = "2025-12-09T22:11:02.66Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/93/3be94d96bb442d0d9a60e55a6bb6e0958dd3457751c6f8502e56ef95fed0/sqlalchemy-2.0.45-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba547ac0b361ab4f1608afbc8432db669bd0819b3e12e29fb5fa9529a8bba81d", size = 3348268, upload-time = "2025-12-09T22:13:49.054Z" },
+ { url = "https://files.pythonhosted.org/packages/48/4b/f88ded696e61513595e4a9778f9d3f2bf7332cce4eb0c7cedaabddd6687b/sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215f0528b914e5c75ef2559f69dca86878a3beeb0c1be7279d77f18e8d180ed4", size = 3278144, upload-time = "2025-12-09T22:11:04.14Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/6a/310ecb5657221f3e1bd5288ed83aa554923fb5da48d760a9f7622afeb065/sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:107029bf4f43d076d4011f1afb74f7c3e2ea029ec82eb23d8527d5e909e97aa6", size = 3313907, upload-time = "2025-12-09T22:13:50.598Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/39/69c0b4051079addd57c84a5bfb34920d87456dd4c90cf7ee0df6efafc8ff/sqlalchemy-2.0.45-cp312-cp312-win32.whl", hash = "sha256:0c9f6ada57b58420a2c0277ff853abe40b9e9449f8d7d231763c6bc30f5c4953", size = 2112182, upload-time = "2025-12-09T21:39:30.824Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/4e/510db49dd89fc3a6e994bee51848c94c48c4a00dc905e8d0133c251f41a7/sqlalchemy-2.0.45-cp312-cp312-win_amd64.whl", hash = "sha256:8defe5737c6d2179c7997242d6473587c3beb52e557f5ef0187277009f73e5e1", size = 2139200, upload-time = "2025-12-09T21:39:32.321Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/c8/7cc5221b47a54edc72a0140a1efa56e0a2730eefa4058d7ed0b4c4357ff8/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf", size = 3277082, upload-time = "2025-12-09T22:11:06.167Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e", size = 3293131, upload-time = "2025-12-09T22:13:52.626Z" },
+ { url = "https://files.pythonhosted.org/packages/da/4c/13dab31266fc9904f7609a5dc308a2432a066141d65b857760c3bef97e69/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b", size = 3225389, upload-time = "2025-12-09T22:11:08.093Z" },
+ { url = "https://files.pythonhosted.org/packages/74/04/891b5c2e9f83589de202e7abaf24cd4e4fa59e1837d64d528829ad6cc107/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8", size = 3266054, upload-time = "2025-12-09T22:13:54.262Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/24/fc59e7f71b0948cdd4cff7a286210e86b0443ef1d18a23b0d83b87e4b1f7/sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a", size = 2110299, upload-time = "2025-12-09T21:39:33.486Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee", size = 2136264, upload-time = "2025-12-09T21:39:36.801Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/8d/bb40a5d10e7a5f2195f235c0b2f2c79b0bf6e8f00c0c223130a4fbd2db09/sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6", size = 3521998, upload-time = "2025-12-09T22:13:28.622Z" },
+ { url = "https://files.pythonhosted.org/packages/75/a5/346128b0464886f036c039ea287b7332a410aa2d3fb0bb5d404cb8861635/sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a", size = 3473434, upload-time = "2025-12-09T22:13:30.188Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/64/4e1913772646b060b025d3fc52ce91a58967fe58957df32b455de5a12b4f/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f46ec744e7f51275582e6a24326e10c49fbdd3fc99103e01376841213028774", size = 3272404, upload-time = "2025-12-09T22:11:09.662Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/27/caf606ee924282fe4747ee4fd454b335a72a6e018f97eab5ff7f28199e16/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883c600c345123c033c2f6caca18def08f1f7f4c3ebeb591a63b6fceffc95cce", size = 3277057, upload-time = "2025-12-09T22:13:56.213Z" },
+ { url = "https://files.pythonhosted.org/packages/85/d0/3d64218c9724e91f3d1574d12eb7ff8f19f937643815d8daf792046d88ab/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c0b74aa79e2deade948fe8593654c8ef4228c44ba862bb7c9585c8e0db90f33", size = 3222279, upload-time = "2025-12-09T22:11:11.1Z" },
+ { url = "https://files.pythonhosted.org/packages/24/10/dd7688a81c5bc7690c2a3764d55a238c524cd1a5a19487928844cb247695/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a420169cef179d4c9064365f42d779f1e5895ad26ca0c8b4c0233920973db74", size = 3244508, upload-time = "2025-12-09T22:13:57.932Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/41/db75756ca49f777e029968d9c9fee338c7907c563267740c6d310a8e3f60/sqlalchemy-2.0.45-cp314-cp314-win32.whl", hash = "sha256:e50dcb81a5dfe4b7b4a4aa8f338116d127cb209559124f3694c70d6cd072b68f", size = 2113204, upload-time = "2025-12-09T21:39:38.365Z" },
+ { url = "https://files.pythonhosted.org/packages/89/a2/0e1590e9adb292b1d576dbcf67ff7df8cf55e56e78d2c927686d01080f4b/sqlalchemy-2.0.45-cp314-cp314-win_amd64.whl", hash = "sha256:4748601c8ea959e37e03d13dcda4a44837afcd1b21338e637f7c935b8da06177", size = 2138785, upload-time = "2025-12-09T21:39:39.503Z" },
+ { url = "https://files.pythonhosted.org/packages/42/39/f05f0ed54d451156bbed0e23eb0516bcad7cbb9f18b3bf219c786371b3f0/sqlalchemy-2.0.45-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd337d3526ec5298f67d6a30bbbe4ed7e5e68862f0bf6dd21d289f8d37b7d60b", size = 3522029, upload-time = "2025-12-09T22:13:32.09Z" },
+ { url = "https://files.pythonhosted.org/packages/54/0f/d15398b98b65c2bce288d5ee3f7d0a81f77ab89d9456994d5c7cc8b2a9db/sqlalchemy-2.0.45-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9a62b446b7d86a3909abbcd1cd3cc550a832f99c2bc37c5b22e1925438b9367b", size = 3475142, upload-time = "2025-12-09T22:13:33.739Z" },
+ { url = "https://files.pythonhosted.org/packages/53/01/a01b9829d146ba59972e6dfc88138142f5ffa4110e492c83326e7d765a17/sqlalchemy-2.0.45-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d29b2b99d527dbc66dd87c3c3248a5dd789d974a507f4653c969999fc7c1191b", size = 2157179, upload-time = "2025-12-10T20:05:13.998Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/78/ed43ed8ac27844f129adfc45a8735bab5dcad3e5211f4dc1bd7e676bc3ed/sqlalchemy-2.0.45-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:59a8b8bd9c6bedf81ad07c8bd5543eedca55fe9b8780b2b628d495ba55f8db1e", size = 3233038, upload-time = "2025-12-09T22:06:55.42Z" },
+ { url = "https://files.pythonhosted.org/packages/24/1c/721ec797f21431c905ad98cbce66430d72a340935e3b7e3232cf05e015cc/sqlalchemy-2.0.45-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd93c6f5d65f254ceabe97548c709e073d6da9883343adaa51bf1a913ce93f8e", size = 3233117, upload-time = "2025-12-09T22:10:03.143Z" },
+ { url = "https://files.pythonhosted.org/packages/52/33/dcfb8dffb2ccd7c6803d63454dc1917ef5ec5b5e281fecbbc0ed1de1f125/sqlalchemy-2.0.45-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d0beadc2535157070c9c17ecf25ecec31e13c229a8f69196d7590bde8082bf1", size = 3182306, upload-time = "2025-12-09T22:06:56.894Z" },
+ { url = "https://files.pythonhosted.org/packages/53/76/7cf8ce9e6dcac1d37125425aadec406d8a839dffc1b8763f6e7a56b0bf33/sqlalchemy-2.0.45-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e057f928ffe9c9b246a55b469c133b98a426297e1772ad24ce9f0c47d123bd5b", size = 3205587, upload-time = "2025-12-09T22:10:04.812Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/ac/5cd0d14f7830981c06f468507237b0a8205691d626492b5551a67535eb30/sqlalchemy-2.0.45-cp39-cp39-win32.whl", hash = "sha256:c1c2091b1489435ff85728fafeb990f073e64f6f5e81d5cd53059773e8521eb6", size = 2115932, upload-time = "2025-12-09T22:09:17.012Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/eb/76f6db8828c6e0cfac89820a07a40a2bab25e82e69827177b942a9bff42a/sqlalchemy-2.0.45-cp39-cp39-win_amd64.whl", hash = "sha256:56ead1f8dfb91a54a28cd1d072c74b3d635bcffbd25e50786533b822d4f2cde2", size = 2139570, upload-time = "2025-12-09T22:09:18.545Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload-time = "2025-12-09T21:54:52.608Z" },
+]
+
+[[package]]
+name = "sqlmodel"
+version = "0.0.27"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pydantic" },
+ { name = "sqlalchemy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/90/5a/693d90866233e837d182da76082a6d4c2303f54d3aaaa5c78e1238c5d863/sqlmodel-0.0.27.tar.gz", hash = "sha256:ad1227f2014a03905aef32e21428640848ac09ff793047744a73dfdd077ff620", size = 118053, upload-time = "2025-10-08T16:39:11.938Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8c/92/c35e036151fe53822893979f8a13e6f235ae8191f4164a79ae60a95d66aa/sqlmodel-0.0.27-py3-none-any.whl", hash = "sha256:667fe10aa8ff5438134668228dc7d7a08306f4c5c4c7e6ad3ad68defa0e7aa49", size = 29131, upload-time = "2025-10-08T16:39:10.917Z" },
+]
+
+[[package]]
+name = "sse-starlette"
+version = "3.1.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio", marker = "python_full_version >= '3.10'" },
+ { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/da/34/f5df66cb383efdbf4f2db23cabb27f51b1dcb737efaf8a558f6f1d195134/sse_starlette-3.1.2.tar.gz", hash = "sha256:55eff034207a83a0eb86de9a68099bd0157838f0b8b999a1b742005c71e33618", size = 26303, upload-time = "2025-12-31T08:02:20.023Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/95/8c4b76eec9ae574474e5d2997557cebf764bcd3586458956c30631ae08f4/sse_starlette-3.1.2-py3-none-any.whl", hash = "sha256:cd800dd349f4521b317b9391d3796fa97b71748a4da9b9e00aafab32dda375c8", size = 12484, upload-time = "2025-12-31T08:02:18.894Z" },
+]
+
+[[package]]
+name = "starlette"
+version = "0.49.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "anyio", marker = "python_full_version < '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/de/1a/608df0b10b53b0beb96a37854ee05864d182ddd4b1156a22f1ad3860425a/starlette-0.49.3.tar.gz", hash = "sha256:1c14546f299b5901a1ea0e34410575bc33bbd741377a10484a54445588d00284", size = 2655031, upload-time = "2025-11-01T15:12:26.13Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a3/e0/021c772d6a662f43b63044ab481dc6ac7592447605b5b35a957785363122/starlette-0.49.3-py3-none-any.whl", hash = "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f", size = 74340, upload-time = "2025-11-01T15:12:24.387Z" },
+]
+
+[[package]]
+name = "starlette"
+version = "0.50.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "anyio", marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" },
+]
+
+[[package]]
+name = "strawberry-graphql"
+version = "0.283.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "graphql-core", marker = "python_full_version < '3.10'" },
+ { name = "lia-web", marker = "python_full_version < '3.10'" },
+ { name = "packaging", marker = "python_full_version < '3.10'" },
+ { name = "python-dateutil", marker = "python_full_version < '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/30/74/729c227b1e7fce28678290a5013ddceb543f350b6c14ae83400ab2c727d1/strawberry_graphql-0.283.3.tar.gz", hash = "sha256:375e545856b7587debd4e0f1e2a6fca19d09cc126238a07b9e5164e5eb09342a", size = 212141, upload-time = "2025-10-10T20:03:46.985Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/23/1b/aa358ef730d727d2e42810bf943542a8cc4c15aa2401f8629d356643a06f/strawberry_graphql-0.283.3-py3-none-any.whl", hash = "sha256:3751d86a219d81a16a13f335bb7d2fa3f57a85fab83d7d284b8ea88e2261d68b", size = 309885, upload-time = "2025-10-10T20:03:44.051Z" },
+]
+
+[[package]]
+name = "strawberry-graphql"
+version = "0.288.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "cross-web", marker = "python_full_version >= '3.10'" },
+ { name = "graphql-core", marker = "python_full_version >= '3.10'" },
+ { name = "packaging", marker = "python_full_version >= '3.10'" },
+ { name = "python-dateutil", marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d9/13/306dd2edb5f0c4aa51e2d071994a91ed1a0304e9feafa79ea0f04da51298/strawberry_graphql-0.288.2.tar.gz", hash = "sha256:853dbab407e3f5099f3a27dbf37786535894a0fbf150df5dde145fc290db607e", size = 215182, upload-time = "2026-01-01T20:01:19.277Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bf/14/15abfa6d289048eeb1b1cca4a582db08a3c1f42784b485c21ef54617e2c7/strawberry_graphql-0.288.2-py3-none-any.whl", hash = "sha256:ad72d7904582db333158568751bb6186a872380a8cc6671159d011d279382542", size = 313137, upload-time = "2026-01-01T20:01:17.32Z" },
+]
+
+[[package]]
+name = "super-collections"
+version = "0.6.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "hjson" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e0/de/a0c3d1244912c260638f0f925e190e493ccea37ecaea9bbad7c14413b803/super_collections-0.6.2.tar.gz", hash = "sha256:0c8d8abacd9fad2c7c1c715f036c29f5db213f8cac65f24d45ecba12b4da187a", size = 31315, upload-time = "2025-09-30T00:37:08.067Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/17/43/47c7cf84b3bd74a8631b02d47db356656bb8dff6f2e61a4c749963814d0d/super_collections-0.6.2-py3-none-any.whl", hash = "sha256:291b74d26299e9051d69ad9d89e61b07b6646f86a57a2f5ab3063d206eee9c56", size = 16173, upload-time = "2025-09-30T00:37:07.104Z" },
+]
+
+[[package]]
+name = "tenacity"
+version = "9.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
+]
+
+[[package]]
+name = "termcolor"
+version = "3.1.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" },
+]
+
+[[package]]
+name = "termcolor"
+version = "3.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" },
+]
+
+[[package]]
+name = "text-unidecode"
+version = "1.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" },
+]
+
+[[package]]
+name = "tinycss2"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "webencodings", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" },
+]
+
+[[package]]
+name = "tinycss2"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "webencodings", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/ae/2ca4913e5c0f09781d75482874c3a95db9105462a92ddd303c7d285d3df2/tinycss2-1.5.1.tar.gz", hash = "sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957", size = 88195, upload-time = "2025-11-23T10:29:10.082Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl", hash = "sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661", size = 28404, upload-time = "2025-11-23T10:29:08.676Z" },
+]
+
+[[package]]
+name = "tokenizers"
+version = "0.22.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "huggingface-hub" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" },
+ { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" },
+ { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" },
+ { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" },
+ { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" },
+ { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" },
+ { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" },
+ { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" },
+ { url = "https://files.pythonhosted.org/packages/84/04/655b79dbcc9b3ac5f1479f18e931a344af67e5b7d3b251d2dcdcd7558592/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4", size = 3282301, upload-time = "2026-01-05T10:40:34.858Z" },
+ { url = "https://files.pythonhosted.org/packages/46/cd/e4851401f3d8f6f45d8480262ab6a5c8cb9c4302a790a35aa14eeed6d2fd/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c", size = 3161308, upload-time = "2026-01-05T10:40:40.737Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/6e/55553992a89982cd12d4a66dddb5e02126c58677ea3931efcbe601d419db/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195", size = 3718964, upload-time = "2026-01-05T10:40:46.56Z" },
+ { url = "https://files.pythonhosted.org/packages/59/8c/b1c87148aa15e099243ec9f0cf9d0e970cc2234c3257d558c25a2c5304e6/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5", size = 3373542, upload-time = "2026-01-05T10:40:52.803Z" },
+ { url = "https://files.pythonhosted.org/packages/27/46/8d7db1dff181be50b207ab0a7483a22d5c3a4f903a9afc7cf7e465ad8109/tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319f659ee992222f04e58f84cbf407cfa66a65fe3a8de44e8ad2bc53e7d99012", size = 3287784, upload-time = "2026-01-05T10:40:37.108Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/6e/3bc33cae8bf114afa5a98e35eb065c72b7c37d01d370906a893f33881767/tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e50f8554d504f617d9e9d6e4c2c2884a12b388a97c5c77f0bc6cf4cd032feee", size = 3164301, upload-time = "2026-01-05T10:40:42.367Z" },
+ { url = "https://files.pythonhosted.org/packages/91/fc/6aa749d7d443aab4daa6f8bc00338389149fd2534e25b772285c3301993e/tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a62ba2c5faa2dd175aaeed7b15abf18d20266189fb3406c5d0550dd34dd5f37", size = 3717771, upload-time = "2026-01-05T10:40:49.076Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/60/5b440d251863bd33f9b0a416c695b0309487b83abf6f2dafe9163a3aeac2/tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143b999bdc46d10febb15cbffb4207ddd1f410e2c755857b5a0797961bbdc113", size = 3377740, upload-time = "2026-01-05T10:40:54.859Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
+ { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
+ { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
+ { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
+ { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
+ { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
+ { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
+ { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
+ { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
+ { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" },
+ { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" },
+ { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" },
+ { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" },
+ { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" },
+ { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" },
+ { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" },
+ { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" },
+ { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" },
+ { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" },
+ { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" },
+ { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" },
+ { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" },
+ { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" },
+ { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" },
+ { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" },
+ { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" },
+ { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
+]
+
+[[package]]
+name = "tqdm"
+version = "4.67.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
+]
+
+[[package]]
+name = "trio"
+version = "0.31.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "attrs", marker = "python_full_version < '3.10'" },
+ { name = "cffi", marker = "python_full_version < '3.10' and implementation_name != 'pypy' and os_name == 'nt'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.10'" },
+ { name = "idna", marker = "python_full_version < '3.10'" },
+ { name = "outcome", marker = "python_full_version < '3.10'" },
+ { name = "sniffio", marker = "python_full_version < '3.10'" },
+ { name = "sortedcontainers", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/76/8f/c6e36dd11201e2a565977d8b13f0b027ba4593c1a80bed5185489178e257/trio-0.31.0.tar.gz", hash = "sha256:f71d551ccaa79d0cb73017a33ef3264fde8335728eb4c6391451fe5d253a9d5b", size = 605825, upload-time = "2025-09-09T15:17:15.242Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/31/5b/94237a3485620dbff9741df02ff6d8acaa5fdec67d81ab3f62e4d8511bf7/trio-0.31.0-py3-none-any.whl", hash = "sha256:b5d14cd6293d79298b49c3485ffd9c07e3ce03a6da8c7dfbe0cb3dd7dc9a4774", size = 512679, upload-time = "2025-09-09T15:17:13.821Z" },
+]
+
+[[package]]
+name = "trio"
+version = "0.32.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "attrs", marker = "python_full_version >= '3.10'" },
+ { name = "cffi", marker = "python_full_version >= '3.10' and implementation_name != 'pypy' and os_name == 'nt'" },
+ { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" },
+ { name = "idna", marker = "python_full_version >= '3.10'" },
+ { name = "outcome", marker = "python_full_version >= '3.10'" },
+ { name = "sniffio", marker = "python_full_version >= '3.10'" },
+ { name = "sortedcontainers", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d8/ce/0041ddd9160aac0031bcf5ab786c7640d795c797e67c438e15cfedf815c8/trio-0.32.0.tar.gz", hash = "sha256:150f29ec923bcd51231e1d4c71c7006e65247d68759dd1c19af4ea815a25806b", size = 605323, upload-time = "2025-10-31T07:18:17.466Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/bf/945d527ff706233636c73880b22c7c953f3faeb9d6c7e2e85bfbfd0134a0/trio-0.32.0-py3-none-any.whl", hash = "sha256:4ab65984ef8370b79a76659ec87aa3a30c5c7c83ff250b4de88c29a8ab6123c5", size = 512030, upload-time = "2025-10-31T07:18:15.885Z" },
+]
+
+[[package]]
+name = "typer"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "rich" },
+ { name = "shellingham" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625, upload-time = "2025-05-26T14:30:31.824Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" },
+]
+
+[[package]]
+name = "typer-slim"
+version = "0.21.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/17/d4/064570dec6358aa9049d4708e4a10407d74c99258f8b2136bb8702303f1a/typer_slim-0.21.1.tar.gz", hash = "sha256:73495dd08c2d0940d611c5a8c04e91c2a0a98600cbd4ee19192255a233b6dbfd", size = 110478, upload-time = "2026-01-06T11:21:11.176Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c8/0a/4aca634faf693e33004796b6cee0ae2e1dba375a800c16ab8d3eff4bb800/typer_slim-0.21.1-py3-none-any.whl", hash = "sha256:6e6c31047f171ac93cc5a973c9e617dbc5ab2bddc4d0a3135dc161b4e2020e0d", size = 47444, upload-time = "2026-01-06T11:21:12.441Z" },
+]
+
+[[package]]
+name = "types-orjson"
+version = "3.6.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8c/97/3f78cfdf663e5668e8b490d8c84d6de089d2d8dbad935f0dc43555d52a90/types-orjson-3.6.2.tar.gz", hash = "sha256:cf9afcc79a86325c7aff251790338109ed6f6b1bab09d2d4262dd18c85a3c638", size = 1999, upload-time = "2022-01-07T11:31:10.518Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/55/84/b34abd2d08381c5113e475908a1d79d27dc9a15f669213cee4ca03d1a891/types_orjson-3.6.2-py3-none-any.whl", hash = "sha256:22ee9a79236b6b0bfb35a0684eded62ad930a88a56797fa3c449b026cf7dbfe4", size = 2224, upload-time = "2022-01-07T11:31:09.271Z" },
+]
+
+[[package]]
+name = "types-requests"
+version = "2.31.0.6"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "types-urllib3", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f9/b8/c1e8d39996b4929b918aba10dba5de07a8b3f4c8487bb61bb79882544e69/types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0", size = 15535, upload-time = "2023-09-27T06:19:38.443Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5c/a1/6f8dc74d9069e790d604ddae70cb46dcbac668f1bb08136e7b0f2f5cd3bf/types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9", size = 14516, upload-time = "2023-09-27T06:19:36.373Z" },
+]
+
+[[package]]
+name = "types-requests"
+version = "2.32.4.20260107"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" },
+]
+
+[[package]]
+name = "types-ujson"
+version = "5.10.0.20240515"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/12/49/abb4bcb9f2258f785edbf236b517c3e7ba8a503a8cbce6b5895930586cc0/types-ujson-5.10.0.20240515.tar.gz", hash = "sha256:ceae7127f0dafe4af5dd0ecf98ee13e9d75951ef963b5c5a9b7ea92e0d71f0d7", size = 3571, upload-time = "2024-05-15T02:24:43.704Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3f/1f/9d018cee3d09ab44a5211f0b5ed9b0422ad9a8c226bf3967f5884498d8f0/types_ujson-5.10.0.20240515-py3-none-any.whl", hash = "sha256:02bafc36b3a93d2511757a64ff88bd505e0a57fba08183a9150fbcfcb2015310", size = 2757, upload-time = "2024-05-15T02:24:42.315Z" },
+]
+
+[[package]]
+name = "types-urllib3"
+version = "1.26.25.14"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/73/de/b9d7a68ad39092368fb21dd6194b362b98a1daeea5dcfef5e1adb5031c7e/types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f", size = 11239, upload-time = "2023-07-20T15:19:31.307Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/11/7b/3fc711b2efea5e85a7a0bbfe269ea944aa767bbba5ec52f9ee45d362ccf3/types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e", size = 15377, upload-time = "2023-07-20T15:19:30.379Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
+]
+
+[[package]]
+name = "ujson"
+version = "5.11.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/d9/3f17e3c5773fb4941c68d9a37a47b1a79c9649d6c56aefbed87cc409d18a/ujson-5.11.0.tar.gz", hash = "sha256:e204ae6f909f099ba6b6b942131cee359ddda2b6e4ea39c12eb8b991fe2010e0", size = 7156583, upload-time = "2025-08-20T11:57:02.452Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/86/0c/8bf7a4fabfd01c7eed92d9b290930ce6d14910dec708e73538baa38885d1/ujson-5.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:446e8c11c06048611c9d29ef1237065de0af07cabdd97e6b5b527b957692ec25", size = 55248, upload-time = "2025-08-20T11:55:02.368Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/2e/eeab0b8b641817031ede4f790db4c4942df44a12f44d72b3954f39c6a115/ujson-5.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16ccb973b7ada0455201808ff11d48fe9c3f034a6ab5bd93b944443c88299f89", size = 53157, upload-time = "2025-08-20T11:55:04.012Z" },
+ { url = "https://files.pythonhosted.org/packages/21/1b/a4e7a41870797633423ea79618526747353fd7be9191f3acfbdee0bf264b/ujson-5.11.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3134b783ab314d2298d58cda7e47e7a0f7f71fc6ade6ac86d5dbeaf4b9770fa6", size = 57657, upload-time = "2025-08-20T11:55:05.169Z" },
+ { url = "https://files.pythonhosted.org/packages/94/ae/4e0d91b8f6db7c9b76423b3649612189506d5a06ddd3b6334b6d37f77a01/ujson-5.11.0-cp310-cp310-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:185f93ebccffebc8baf8302c869fac70dd5dd78694f3b875d03a31b03b062cdb", size = 59780, upload-time = "2025-08-20T11:55:06.325Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/cc/46b124c2697ca2da7c65c4931ed3cb670646978157aa57a7a60f741c530f/ujson-5.11.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d06e87eded62ff0e5f5178c916337d2262fdbc03b31688142a3433eabb6511db", size = 57307, upload-time = "2025-08-20T11:55:07.493Z" },
+ { url = "https://files.pythonhosted.org/packages/39/eb/20dd1282bc85dede2f1c62c45b4040bc4c389c80a05983515ab99771bca7/ujson-5.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:181fb5b15703a8b9370b25345d2a1fd1359f0f18776b3643d24e13ed9c036d4c", size = 1036369, upload-time = "2025-08-20T11:55:09.192Z" },
+ { url = "https://files.pythonhosted.org/packages/64/a2/80072439065d493e3a4b1fbeec991724419a1b4c232e2d1147d257cac193/ujson-5.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a4df61a6df0a4a8eb5b9b1ffd673429811f50b235539dac586bb7e9e91994138", size = 1195738, upload-time = "2025-08-20T11:55:11.402Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/7e/d77f9e9c039d58299c350c978e086a804d1fceae4fd4a1cc6e8d0133f838/ujson-5.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6eff24e1abd79e0ec6d7eae651dd675ddbc41f9e43e29ef81e16b421da896915", size = 1088718, upload-time = "2025-08-20T11:55:13.297Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/f1/697559d45acc849cada6b3571d53522951b1a64027400507aabc6a710178/ujson-5.11.0-cp310-cp310-win32.whl", hash = "sha256:30f607c70091483550fbd669a0b37471e5165b317d6c16e75dba2aa967608723", size = 39653, upload-time = "2025-08-20T11:55:14.869Z" },
+ { url = "https://files.pythonhosted.org/packages/86/a2/70b73a0f55abe0e6b8046d365d74230c20c5691373e6902a599b2dc79ba1/ujson-5.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:3d2720e9785f84312b8e2cb0c2b87f1a0b1c53aaab3b2af3ab817d54409012e0", size = 43720, upload-time = "2025-08-20T11:55:15.897Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/5f/b19104afa455630b43efcad3a24495b9c635d92aa8f2da4f30e375deb1a2/ujson-5.11.0-cp310-cp310-win_arm64.whl", hash = "sha256:85e6796631165f719084a9af00c79195d3ebf108151452fefdcb1c8bb50f0105", size = 38410, upload-time = "2025-08-20T11:55:17.556Z" },
+ { url = "https://files.pythonhosted.org/packages/da/ea/80346b826349d60ca4d612a47cdf3533694e49b45e9d1c07071bb867a184/ujson-5.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d7c46cb0fe5e7056b9acb748a4c35aa1b428025853032540bb7e41f46767321f", size = 55248, upload-time = "2025-08-20T11:55:19.033Z" },
+ { url = "https://files.pythonhosted.org/packages/57/df/b53e747562c89515e18156513cc7c8ced2e5e3fd6c654acaa8752ffd7cd9/ujson-5.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8951bb7a505ab2a700e26f691bdfacf395bc7e3111e3416d325b513eea03a58", size = 53156, upload-time = "2025-08-20T11:55:20.174Z" },
+ { url = "https://files.pythonhosted.org/packages/41/b8/ab67ec8c01b8a3721fd13e5cb9d85ab2a6066a3a5e9148d661a6870d6293/ujson-5.11.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952c0be400229940248c0f5356514123d428cba1946af6fa2bbd7503395fef26", size = 57657, upload-time = "2025-08-20T11:55:21.296Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/c7/fb84f27cd80a2c7e2d3c6012367aecade0da936790429801803fa8d4bffc/ujson-5.11.0-cp311-cp311-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:94fcae844f1e302f6f8095c5d1c45a2f0bfb928cccf9f1b99e3ace634b980a2a", size = 59779, upload-time = "2025-08-20T11:55:22.772Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/7c/48706f7c1e917ecb97ddcfb7b1d756040b86ed38290e28579d63bd3fcc48/ujson-5.11.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e0ec1646db172beb8d3df4c32a9d78015e671d2000af548252769e33079d9a6", size = 57284, upload-time = "2025-08-20T11:55:24.01Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/ce/48877c6eb4afddfd6bd1db6be34456538c07ca2d6ed233d3f6c6efc2efe8/ujson-5.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:da473b23e3a54448b008d33f742bcd6d5fb2a897e42d1fc6e7bf306ea5d18b1b", size = 1036395, upload-time = "2025-08-20T11:55:25.725Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/7a/2c20dc97ad70cd7c31ad0596ba8e2cf8794d77191ba4d1e0bded69865477/ujson-5.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:aa6b3d4f1c0d3f82930f4cbd7fe46d905a4a9205a7c13279789c1263faf06dba", size = 1195731, upload-time = "2025-08-20T11:55:27.915Z" },
+ { url = "https://files.pythonhosted.org/packages/15/f5/ca454f2f6a2c840394b6f162fff2801450803f4ff56c7af8ce37640b8a2a/ujson-5.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4843f3ab4fe1cc596bb7e02228ef4c25d35b4bb0809d6a260852a4bfcab37ba3", size = 1088710, upload-time = "2025-08-20T11:55:29.426Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/d3/9ba310e07969bc9906eb7548731e33a0f448b122ad9705fed699c9b29345/ujson-5.11.0-cp311-cp311-win32.whl", hash = "sha256:e979fbc469a7f77f04ec2f4e853ba00c441bf2b06720aa259f0f720561335e34", size = 39648, upload-time = "2025-08-20T11:55:31.194Z" },
+ { url = "https://files.pythonhosted.org/packages/57/f7/da05b4a8819f1360be9e71fb20182f0bb3ec611a36c3f213f4d20709e099/ujson-5.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:683f57f0dd3acdd7d9aff1de0528d603aafcb0e6d126e3dc7ce8b020a28f5d01", size = 43717, upload-time = "2025-08-20T11:55:32.241Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/cc/f3f9ac0f24f00a623a48d97dc3814df5c2dc368cfb00031aa4141527a24b/ujson-5.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:7855ccea3f8dad5e66d8445d754fc1cf80265a4272b5f8059ebc7ec29b8d0835", size = 38402, upload-time = "2025-08-20T11:55:33.641Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/ef/a9cb1fce38f699123ff012161599fb9f2ff3f8d482b4b18c43a2dc35073f/ujson-5.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7895f0d2d53bd6aea11743bd56e3cb82d729980636cd0ed9b89418bf66591702", size = 55434, upload-time = "2025-08-20T11:55:34.987Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/05/dba51a00eb30bd947791b173766cbed3492269c150a7771d2750000c965f/ujson-5.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12b5e7e22a1fe01058000d1b317d3b65cc3daf61bd2ea7a2b76721fe160fa74d", size = 53190, upload-time = "2025-08-20T11:55:36.384Z" },
+ { url = "https://files.pythonhosted.org/packages/03/3c/fd11a224f73fbffa299fb9644e425f38b38b30231f7923a088dd513aabb4/ujson-5.11.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0180a480a7d099082501cad1fe85252e4d4bf926b40960fb3d9e87a3a6fbbc80", size = 57600, upload-time = "2025-08-20T11:55:37.692Z" },
+ { url = "https://files.pythonhosted.org/packages/55/b9/405103cae24899df688a3431c776e00528bd4799e7d68820e7ebcf824f92/ujson-5.11.0-cp312-cp312-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:fa79fdb47701942c2132a9dd2297a1a85941d966d8c87bfd9e29b0cf423f26cc", size = 59791, upload-time = "2025-08-20T11:55:38.877Z" },
+ { url = "https://files.pythonhosted.org/packages/17/7b/2dcbc2bbfdbf68f2368fb21ab0f6735e872290bb604c75f6e06b81edcb3f/ujson-5.11.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8254e858437c00f17cb72e7a644fc42dad0ebb21ea981b71df6e84b1072aaa7c", size = 57356, upload-time = "2025-08-20T11:55:40.036Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/71/fea2ca18986a366c750767b694430d5ded6b20b6985fddca72f74af38a4c/ujson-5.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1aa8a2ab482f09f6c10fba37112af5f957689a79ea598399c85009f2f29898b5", size = 1036313, upload-time = "2025-08-20T11:55:41.408Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/bb/d4220bd7532eac6288d8115db51710fa2d7d271250797b0bfba9f1e755af/ujson-5.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a638425d3c6eed0318df663df44480f4a40dc87cc7c6da44d221418312f6413b", size = 1195782, upload-time = "2025-08-20T11:55:43.357Z" },
+ { url = "https://files.pythonhosted.org/packages/80/47/226e540aa38878ce1194454385701d82df538ccb5ff8db2cf1641dde849a/ujson-5.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e3cff632c1d78023b15f7e3a81c3745cd3f94c044d1e8fa8efbd6b161997bbc", size = 1088817, upload-time = "2025-08-20T11:55:45.262Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/81/546042f0b23c9040d61d46ea5ca76f0cc5e0d399180ddfb2ae976ebff5b5/ujson-5.11.0-cp312-cp312-win32.whl", hash = "sha256:be6b0eaf92cae8cdee4d4c9e074bde43ef1c590ed5ba037ea26c9632fb479c88", size = 39757, upload-time = "2025-08-20T11:55:46.522Z" },
+ { url = "https://files.pythonhosted.org/packages/44/1b/27c05dc8c9728f44875d74b5bfa948ce91f6c33349232619279f35c6e817/ujson-5.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:b7b136cc6abc7619124fd897ef75f8e63105298b5ca9bdf43ebd0e1fa0ee105f", size = 43859, upload-time = "2025-08-20T11:55:47.987Z" },
+ { url = "https://files.pythonhosted.org/packages/22/2d/37b6557c97c3409c202c838aa9c960ca3896843b4295c4b7bb2bbd260664/ujson-5.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:6cd2df62f24c506a0ba322d5e4fe4466d47a9467b57e881ee15a31f7ecf68ff6", size = 38361, upload-time = "2025-08-20T11:55:49.122Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/ec/2de9dd371d52c377abc05d2b725645326c4562fc87296a8907c7bcdf2db7/ujson-5.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:109f59885041b14ee9569bf0bb3f98579c3fa0652317b355669939e5fc5ede53", size = 55435, upload-time = "2025-08-20T11:55:50.243Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/a4/f611f816eac3a581d8a4372f6967c3ed41eddbae4008d1d77f223f1a4e0a/ujson-5.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a31c6b8004438e8c20fc55ac1c0e07dad42941db24176fe9acf2815971f8e752", size = 53193, upload-time = "2025-08-20T11:55:51.373Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/c5/c161940967184de96f5cbbbcce45b562a4bf851d60f4c677704b1770136d/ujson-5.11.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78c684fb21255b9b90320ba7e199780f653e03f6c2528663768965f4126a5b50", size = 57603, upload-time = "2025-08-20T11:55:52.583Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/d6/c7b2444238f5b2e2d0e3dab300b9ddc3606e4b1f0e4bed5a48157cebc792/ujson-5.11.0-cp313-cp313-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:4c9f5d6a27d035dd90a146f7761c2272cf7103de5127c9ab9c4cd39ea61e878a", size = 59794, upload-time = "2025-08-20T11:55:53.69Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/a3/292551f936d3d02d9af148f53e1bc04306b00a7cf1fcbb86fa0d1c887242/ujson-5.11.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:837da4d27fed5fdc1b630bd18f519744b23a0b5ada1bbde1a36ba463f2900c03", size = 57363, upload-time = "2025-08-20T11:55:54.843Z" },
+ { url = "https://files.pythonhosted.org/packages/90/a6/82cfa70448831b1a9e73f882225980b5c689bf539ec6400b31656a60ea46/ujson-5.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:787aff4a84da301b7f3bac09bc696e2e5670df829c6f8ecf39916b4e7e24e701", size = 1036311, upload-time = "2025-08-20T11:55:56.197Z" },
+ { url = "https://files.pythonhosted.org/packages/84/5c/96e2266be50f21e9b27acaee8ca8f23ea0b85cb998c33d4f53147687839b/ujson-5.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6dd703c3e86dc6f7044c5ac0b3ae079ed96bf297974598116aa5fb7f655c3a60", size = 1195783, upload-time = "2025-08-20T11:55:58.081Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/20/78abe3d808cf3bb3e76f71fca46cd208317bf461c905d79f0d26b9df20f1/ujson-5.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3772e4fe6b0c1e025ba3c50841a0ca4786825a4894c8411bf8d3afe3a8061328", size = 1088822, upload-time = "2025-08-20T11:55:59.469Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/50/8856e24bec5e2fc7f775d867aeb7a3f137359356200ac44658f1f2c834b2/ujson-5.11.0-cp313-cp313-win32.whl", hash = "sha256:8fa2af7c1459204b7a42e98263b069bd535ea0cd978b4d6982f35af5a04a4241", size = 39753, upload-time = "2025-08-20T11:56:01.345Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/d8/1baee0f4179a4d0f5ce086832147b6cc9b7731c24ca08e14a3fdb8d39c32/ujson-5.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:34032aeca4510a7c7102bd5933f59a37f63891f30a0706fb46487ab6f0edf8f0", size = 43866, upload-time = "2025-08-20T11:56:02.552Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/8c/6d85ef5be82c6d66adced3ec5ef23353ed710a11f70b0b6a836878396334/ujson-5.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:ce076f2df2e1aa62b685086fbad67f2b1d3048369664b4cdccc50707325401f9", size = 38363, upload-time = "2025-08-20T11:56:03.688Z" },
+ { url = "https://files.pythonhosted.org/packages/28/08/4518146f4984d112764b1dfa6fb7bad691c44a401adadaa5e23ccd930053/ujson-5.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65724738c73645db88f70ba1f2e6fb678f913281804d5da2fd02c8c5839af302", size = 55462, upload-time = "2025-08-20T11:56:04.873Z" },
+ { url = "https://files.pythonhosted.org/packages/29/37/2107b9a62168867a692654d8766b81bd2fd1e1ba13e2ec90555861e02b0c/ujson-5.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29113c003ca33ab71b1b480bde952fbab2a0b6b03a4ee4c3d71687cdcbd1a29d", size = 53246, upload-time = "2025-08-20T11:56:06.054Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/f8/25583c70f83788edbe3ca62ce6c1b79eff465d78dec5eb2b2b56b3e98b33/ujson-5.11.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c44c703842024d796b4c78542a6fcd5c3cb948b9fc2a73ee65b9c86a22ee3638", size = 57631, upload-time = "2025-08-20T11:56:07.374Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/ca/19b3a632933a09d696f10dc1b0dfa1d692e65ad507d12340116ce4f67967/ujson-5.11.0-cp314-cp314-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:e750c436fb90edf85585f5c62a35b35082502383840962c6983403d1bd96a02c", size = 59877, upload-time = "2025-08-20T11:56:08.534Z" },
+ { url = "https://files.pythonhosted.org/packages/55/7a/4572af5324ad4b2bfdd2321e898a527050290147b4ea337a79a0e4e87ec7/ujson-5.11.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f278b31a7c52eb0947b2db55a5133fbc46b6f0ef49972cd1a80843b72e135aba", size = 57363, upload-time = "2025-08-20T11:56:09.758Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/71/a2b8c19cf4e1efe53cf439cdf7198ac60ae15471d2f1040b490c1f0f831f/ujson-5.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ab2cb8351d976e788669c8281465d44d4e94413718af497b4e7342d7b2f78018", size = 1036394, upload-time = "2025-08-20T11:56:11.168Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/3e/7b98668cba3bb3735929c31b999b374ebc02c19dfa98dfebaeeb5c8597ca/ujson-5.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:090b4d11b380ae25453100b722d0609d5051ffe98f80ec52853ccf8249dfd840", size = 1195837, upload-time = "2025-08-20T11:56:12.6Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/ea/8870f208c20b43571a5c409ebb2fe9b9dba5f494e9e60f9314ac01ea8f78/ujson-5.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:80017e870d882d5517d28995b62e4e518a894f932f1e242cbc802a2fd64d365c", size = 1088837, upload-time = "2025-08-20T11:56:14.15Z" },
+ { url = "https://files.pythonhosted.org/packages/63/b6/c0e6607e37fa47929920a685a968c6b990a802dec65e9c5181e97845985d/ujson-5.11.0-cp314-cp314-win32.whl", hash = "sha256:1d663b96eb34c93392e9caae19c099ec4133ba21654b081956613327f0e973ac", size = 41022, upload-time = "2025-08-20T11:56:15.509Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/56/f4fe86b4c9000affd63e9219e59b222dc48b01c534533093e798bf617a7e/ujson-5.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:849e65b696f0d242833f1df4182096cedc50d414215d1371fca85c541fbff629", size = 45111, upload-time = "2025-08-20T11:56:16.597Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/f3/669437f0280308db4783b12a6d88c00730b394327d8334cc7a32ef218e64/ujson-5.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:e73df8648c9470af2b6a6bf5250d4744ad2cf3d774dcf8c6e31f018bdd04d764", size = 39682, upload-time = "2025-08-20T11:56:17.763Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/cd/e9809b064a89fe5c4184649adeb13c1b98652db3f8518980b04227358574/ujson-5.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:de6e88f62796372fba1de973c11138f197d3e0e1d80bcb2b8aae1e826096d433", size = 55759, upload-time = "2025-08-20T11:56:18.882Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/be/ae26a6321179ebbb3a2e2685b9007c71bcda41ad7a77bbbe164005e956fc/ujson-5.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e56ef8066f11b80d620985ae36869a3ff7e4b74c3b6129182ec5d1df0255f3", size = 53634, upload-time = "2025-08-20T11:56:20.012Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/e9/fb4a220ee6939db099f4cfeeae796ecb91e7584ad4d445d4ca7f994a9135/ujson-5.11.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a325fd2c3a056cf6c8e023f74a0c478dd282a93141356ae7f16d5309f5ff823", size = 58547, upload-time = "2025-08-20T11:56:21.175Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/f8/fc4b952b8f5fea09ea3397a0bd0ad019e474b204cabcb947cead5d4d1ffc/ujson-5.11.0-cp314-cp314t-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:a0af6574fc1d9d53f4ff371f58c96673e6d988ed2b5bf666a6143c782fa007e9", size = 60489, upload-time = "2025-08-20T11:56:22.342Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/e5/af5491dfda4f8b77e24cf3da68ee0d1552f99a13e5c622f4cef1380925c3/ujson-5.11.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10f29e71ecf4ecd93a6610bd8efa8e7b6467454a363c3d6416db65de883eb076", size = 58035, upload-time = "2025-08-20T11:56:23.92Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/09/0945349dd41f25cc8c38d78ace49f14c5052c5bbb7257d2f466fa7bdb533/ujson-5.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a0a9b76a89827a592656fe12e000cf4f12da9692f51a841a4a07aa4c7ecc41c", size = 1037212, upload-time = "2025-08-20T11:56:25.274Z" },
+ { url = "https://files.pythonhosted.org/packages/49/44/8e04496acb3d5a1cbee3a54828d9652f67a37523efa3d3b18a347339680a/ujson-5.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b16930f6a0753cdc7d637b33b4e8f10d5e351e1fb83872ba6375f1e87be39746", size = 1196500, upload-time = "2025-08-20T11:56:27.517Z" },
+ { url = "https://files.pythonhosted.org/packages/64/ae/4bc825860d679a0f208a19af2f39206dfd804ace2403330fdc3170334a2f/ujson-5.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:04c41afc195fd477a59db3a84d5b83a871bd648ef371cf8c6f43072d89144eef", size = 1089487, upload-time = "2025-08-20T11:56:29.07Z" },
+ { url = "https://files.pythonhosted.org/packages/30/ed/5a057199fb0a5deabe0957073a1c1c1c02a3e99476cd03daee98ea21fa57/ujson-5.11.0-cp314-cp314t-win32.whl", hash = "sha256:aa6d7a5e09217ff93234e050e3e380da62b084e26b9f2e277d2606406a2fc2e5", size = 41859, upload-time = "2025-08-20T11:56:30.495Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/03/b19c6176bdf1dc13ed84b886e99677a52764861b6cc023d5e7b6ebda249d/ujson-5.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:48055e1061c1bb1f79e75b4ac39e821f3f35a9b82de17fce92c3140149009bec", size = 46183, upload-time = "2025-08-20T11:56:31.574Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/ca/a0413a3874b2dc1708b8796ca895bf363292f9c70b2e8ca482b7dbc0259d/ujson-5.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1194b943e951092db611011cb8dbdb6cf94a3b816ed07906e14d3bc6ce0e90ab", size = 40264, upload-time = "2025-08-20T11:56:32.773Z" },
+ { url = "https://files.pythonhosted.org/packages/39/bf/c6f59cdf74ce70bd937b97c31c42fd04a5ed1a9222d0197e77e4bd899841/ujson-5.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65f3c279f4ed4bf9131b11972040200c66ae040368abdbb21596bf1564899694", size = 55283, upload-time = "2025-08-20T11:56:33.947Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/c1/a52d55638c0c644b8a63059f95ad5ffcb4ad8f60d8bc3e8680f78e77cc75/ujson-5.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:99c49400572cd77050894e16864a335225191fd72a818ea6423ae1a06467beac", size = 53168, upload-time = "2025-08-20T11:56:35.141Z" },
+ { url = "https://files.pythonhosted.org/packages/75/6c/e64e19a01d59c8187d01ffc752ee3792a09f5edaaac2a0402de004459dd7/ujson-5.11.0-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0654a2691fc252c3c525e3d034bb27b8a7546c9d3eb33cd29ce6c9feda361a6a", size = 57809, upload-time = "2025-08-20T11:56:36.293Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/36/910117b7a8a1c188396f6194ca7bc8fd75e376d8f7e3cf5eb6219fc8b09d/ujson-5.11.0-cp39-cp39-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:6b6ec7e7321d7fc19abdda3ad809baef935f49673951a8bab486aea975007e02", size = 59797, upload-time = "2025-08-20T11:56:37.746Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/17/bcc85d282ee2f4cdef5f577e0a43533eedcae29cc6405edf8c62a7a50368/ujson-5.11.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f62b9976fabbcde3ab6e413f4ec2ff017749819a0786d84d7510171109f2d53c", size = 57378, upload-time = "2025-08-20T11:56:39.123Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/39/120bb76441bf835f3c3f42db9c206f31ba875711637a52a8209949ab04b0/ujson-5.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f1a27ab91083b4770e160d17f61b407f587548f2c2b5fbf19f94794c495594a", size = 1036515, upload-time = "2025-08-20T11:56:40.848Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/ae/fe1b4ff6388f681f6710e9494656957725b1e73ae50421ec04567df9fb75/ujson-5.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ecd6ff8a3b5a90c292c2396c2d63c687fd0ecdf17de390d852524393cd9ed052", size = 1195753, upload-time = "2025-08-20T11:56:42.341Z" },
+ { url = "https://files.pythonhosted.org/packages/92/20/005b93f2cf846ae50b46812fcf24bbdd127521197e5f1e1a82e3b3e730a1/ujson-5.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9aacbeb23fdbc4b256a7d12e0beb9063a1ba5d9e0dbb2cfe16357c98b4334596", size = 1088844, upload-time = "2025-08-20T11:56:43.777Z" },
+ { url = "https://files.pythonhosted.org/packages/41/9e/3142023c30008e2b24d7368a389b26d28d62fcd3f596d3d898a72dd09173/ujson-5.11.0-cp39-cp39-win32.whl", hash = "sha256:674f306e3e6089f92b126eb2fe41bcb65e42a15432c143365c729fdb50518547", size = 39652, upload-time = "2025-08-20T11:56:45.034Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/89/f4de0a3c485d0163f85f552886251876645fb62cbbe24fcdc0874b9fae03/ujson-5.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:c6618f480f7c9ded05e78a1938873fde68baf96cdd74e6d23c7e0a8441175c4b", size = 43783, upload-time = "2025-08-20T11:56:46.156Z" },
+ { url = "https://files.pythonhosted.org/packages/48/b1/2d50987a7b7cccb5c1fbe9ae7b184211106237b32c7039118c41d79632ea/ujson-5.11.0-cp39-cp39-win_arm64.whl", hash = "sha256:5600202a731af24a25e2d7b6eb3f648e4ecd4bb67c4d5cf12f8fab31677469c9", size = 38430, upload-time = "2025-08-20T11:56:47.653Z" },
+ { url = "https://files.pythonhosted.org/packages/50/17/30275aa2933430d8c0c4ead951cc4fdb922f575a349aa0b48a6f35449e97/ujson-5.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:abae0fb58cc820092a0e9e8ba0051ac4583958495bfa5262a12f628249e3b362", size = 51206, upload-time = "2025-08-20T11:56:48.797Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/15/42b3924258eac2551f8f33fa4e35da20a06a53857ccf3d4deb5e5d7c0b6c/ujson-5.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fac6c0649d6b7c3682a0a6e18d3de6857977378dce8d419f57a0b20e3d775b39", size = 48907, upload-time = "2025-08-20T11:56:50.136Z" },
+ { url = "https://files.pythonhosted.org/packages/94/7e/0519ff7955aba581d1fe1fb1ca0e452471250455d182f686db5ac9e46119/ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b42c115c7c6012506e8168315150d1e3f76e7ba0f4f95616f4ee599a1372bbc", size = 50319, upload-time = "2025-08-20T11:56:51.63Z" },
+ { url = "https://files.pythonhosted.org/packages/74/cf/209d90506b7d6c5873f82c5a226d7aad1a1da153364e9ebf61eff0740c33/ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:86baf341d90b566d61a394869ce77188cc8668f76d7bb2c311d77a00f4bdf844", size = 56584, upload-time = "2025-08-20T11:56:52.89Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/97/bd939bb76943cb0e1d2b692d7e68629f51c711ef60425fa5bb6968037ecd/ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4598bf3965fc1a936bd84034312bcbe00ba87880ef1ee33e33c1e88f2c398b49", size = 51588, upload-time = "2025-08-20T11:56:54.054Z" },
+ { url = "https://files.pythonhosted.org/packages/52/5b/8c5e33228f7f83f05719964db59f3f9f276d272dc43752fa3bbf0df53e7b/ujson-5.11.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:416389ec19ef5f2013592f791486bef712ebce0cd59299bf9df1ba40bb2f6e04", size = 43835, upload-time = "2025-08-20T11:56:55.237Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "1.26.20"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380, upload-time = "2024-08-29T15:43:11.37Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225, upload-time = "2024-08-29T15:43:08.921Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.6.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
+]
+
+[[package]]
+name = "uvicorn"
+version = "0.39.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "h11", marker = "python_full_version < '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ae/4f/f9fdac7cf6dd79790eb165639b5c452ceeabc7bbabbba4569155470a287d/uvicorn-0.39.0.tar.gz", hash = "sha256:610512b19baa93423d2892d7823741f6d27717b642c8964000d7194dded19302", size = 82001, upload-time = "2025-12-21T13:05:17.973Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6b/25/db2b1c6c35bf22e17fe5412d2ee5d3fd7a20d07ebc9dac8b58f7db2e23a0/uvicorn-0.39.0-py3-none-any.whl", hash = "sha256:7beec21bd2693562b386285b188a7963b06853c0d006302b3e4cfed950c9929a", size = 68491, upload-time = "2025-12-21T13:05:16.291Z" },
+]
+
+[package.optional-dependencies]
+standard = [
+ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
+ { name = "httptools", marker = "python_full_version < '3.10'" },
+ { name = "python-dotenv", marker = "python_full_version < '3.10'" },
+ { name = "pyyaml", marker = "python_full_version < '3.10'" },
+ { name = "uvloop", marker = "python_full_version < '3.10' and platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" },
+ { name = "watchfiles", marker = "python_full_version < '3.10'" },
+ { name = "websockets", marker = "python_full_version < '3.10'" },
+]
+
+[[package]]
+name = "uvicorn"
+version = "0.40.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version >= '3.10' and python_full_version < '3.14'",
+]
+dependencies = [
+ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "h11", marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" },
+]
+
+[package.optional-dependencies]
+standard = [
+ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
+ { name = "httptools", marker = "python_full_version >= '3.10'" },
+ { name = "python-dotenv", marker = "python_full_version >= '3.10'" },
+ { name = "pyyaml", marker = "python_full_version >= '3.10'" },
+ { name = "uvloop", marker = "python_full_version >= '3.10' and platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" },
+ { name = "watchfiles", marker = "python_full_version >= '3.10'" },
+ { name = "websockets", marker = "python_full_version >= '3.10'" },
+]
+
+[[package]]
+name = "uvloop"
+version = "0.22.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c", size = 1343335, upload-time = "2025-10-16T22:16:11.43Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792", size = 742903, upload-time = "2025-10-16T22:16:12.979Z" },
+ { url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86", size = 3648499, upload-time = "2025-10-16T22:16:14.451Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd", size = 3700133, upload-time = "2025-10-16T22:16:16.272Z" },
+ { url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2", size = 3512681, upload-time = "2025-10-16T22:16:18.07Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec", size = 3615261, upload-time = "2025-10-16T22:16:19.596Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" },
+ { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" },
+ { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" },
+ { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" },
+ { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" },
+ { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" },
+ { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" },
+ { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" },
+ { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" },
+ { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" },
+ { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" },
+ { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" },
+ { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" },
+ { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/1b/6fbd611aeba01ef802c5876c94d7be603a9710db055beacbad39e75a31aa/uvloop-0.22.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b45649628d816c030dba3c80f8e2689bab1c89518ed10d426036cdc47874dfc4", size = 1345858, upload-time = "2025-10-16T22:17:11.106Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/91/2c84f00bdbe3c51023cc83b027bac1fe959ba4a552e970da5ef0237f7945/uvloop-0.22.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ea721dd3203b809039fcc2983f14608dae82b212288b346e0bfe46ec2fab0b7c", size = 743913, upload-time = "2025-10-16T22:17:12.165Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/10/76aec83886d41a88aca5681db6a2c0601622d0d2cb66cd0d200587f962ad/uvloop-0.22.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ae676de143db2b2f60a9696d7eca5bb9d0dd6cc3ac3dad59a8ae7e95f9e1b54", size = 3635818, upload-time = "2025-10-16T22:17:13.812Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/9a/733fcb815d345979fc54d3cdc3eb50bc75a47da3e4003ea7ada58e6daa65/uvloop-0.22.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17d4e97258b0172dfa107b89aa1eeba3016f4b1974ce85ca3ef6a66b35cbf659", size = 3685477, upload-time = "2025-10-16T22:17:15.307Z" },
+ { url = "https://files.pythonhosted.org/packages/83/fb/bee1eb11cc92bd91f76d97869bb6a816e80d59fd73721b0a3044dc703d9c/uvloop-0.22.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:05e4b5f86e621cf3927631789999e697e58f0d2d32675b67d9ca9eb0bca55743", size = 3496128, upload-time = "2025-10-16T22:17:16.558Z" },
+ { url = "https://files.pythonhosted.org/packages/76/ee/3fdfeaa9776c0fd585d358c92b1dbca669720ffa476f0bbe64ed8f245bd7/uvloop-0.22.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:286322a90bea1f9422a470d5d2ad82d38080be0a29c4dd9b3e6384320a4d11e7", size = 3602565, upload-time = "2025-10-16T22:17:17.755Z" },
+]
+
+[[package]]
+name = "watchdog"
+version = "6.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" },
+ { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" },
+ { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" },
+ { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" },
+ { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" },
+ { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" },
+ { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
+ { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
+ { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" },
+ { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
+ { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
+ { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
+ { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
+ { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
+]
+
+[[package]]
+name = "watchfiles"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318, upload-time = "2025-10-14T15:04:18.753Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478, upload-time = "2025-10-14T15:04:20.297Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894, upload-time = "2025-10-14T15:04:21.527Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065, upload-time = "2025-10-14T15:04:22.795Z" },
+ { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377, upload-time = "2025-10-14T15:04:24.138Z" },
+ { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837, upload-time = "2025-10-14T15:04:25.057Z" },
+ { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456, upload-time = "2025-10-14T15:04:26.497Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614, upload-time = "2025-10-14T15:04:27.539Z" },
+ { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690, upload-time = "2025-10-14T15:04:28.495Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459, upload-time = "2025-10-14T15:04:29.491Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663, upload-time = "2025-10-14T15:04:30.435Z" },
+ { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453, upload-time = "2025-10-14T15:04:31.53Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" },
+ { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" },
+ { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" },
+ { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" },
+ { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" },
+ { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" },
+ { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" },
+ { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" },
+ { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" },
+ { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" },
+ { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" },
+ { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" },
+ { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" },
+ { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" },
+ { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" },
+ { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" },
+ { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" },
+ { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" },
+ { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" },
+ { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" },
+ { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" },
+ { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" },
+ { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" },
+ { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" },
+ { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" },
+ { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" },
+ { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" },
+ { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" },
+ { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" },
+ { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" },
+ { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/68/a7303a15cc797ab04d58f1fea7f67c50bd7f80090dfd7e750e7576e07582/watchfiles-1.1.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c882d69f6903ef6092bedfb7be973d9319940d56b8427ab9187d1ecd73438a70", size = 409220, upload-time = "2025-10-14T15:05:51.917Z" },
+ { url = "https://files.pythonhosted.org/packages/99/b8/d1857ce9ac76034c053fa7ef0e0ef92d8bd031e842ea6f5171725d31e88f/watchfiles-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6ff426a7cb54f310d51bfe83fe9f2bbe40d540c741dc974ebc30e6aa238f52e", size = 396712, upload-time = "2025-10-14T15:05:53.437Z" },
+ { url = "https://files.pythonhosted.org/packages/41/7a/da7ada566f48beaa6a30b13335b49d1f6febaf3a5ddbd1d92163a1002cf4/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79ff6c6eadf2e3fc0d7786331362e6ef1e51125892c75f1004bd6b52155fb956", size = 451462, upload-time = "2025-10-14T15:05:54.742Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/b2/7cb9e0d5445a8d45c4cccd68a590d9e3a453289366b96ff37d1075aaebef/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c1f5210f1b8fc91ead1283c6fd89f70e76fb07283ec738056cf34d51e9c1d62c", size = 460811, upload-time = "2025-10-14T15:05:55.743Z" },
+ { url = "https://files.pythonhosted.org/packages/04/9d/b07d4491dde6db6ea6c680fdec452f4be363d65c82004faf2d853f59b76f/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9c4702f29ca48e023ffd9b7ff6b822acdf47cb1ff44cb490a3f1d5ec8987e9c", size = 490576, upload-time = "2025-10-14T15:05:56.983Z" },
+ { url = "https://files.pythonhosted.org/packages/56/03/e64dcab0a1806157db272a61b7891b062f441a30580a581ae72114259472/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acb08650863767cbc58bca4813b92df4d6c648459dcaa3d4155681962b2aa2d3", size = 597726, upload-time = "2025-10-14T15:05:57.986Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/8e/a827cf4a8d5f2903a19a934dcf512082eb07675253e154d4cd9367978a58/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08af70fd77eee58549cd69c25055dc344f918d992ff626068242259f98d598a2", size = 474900, upload-time = "2025-10-14T15:05:59.378Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/a6/94fed0b346b85b22303a12eee5f431006fae6af70d841cac2f4403245533/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c3631058c37e4a0ec440bf583bc53cdbd13e5661bb6f465bc1d88ee9a0a4d02", size = 457521, upload-time = "2025-10-14T15:06:00.419Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/64/bc3331150e8f3c778d48a4615d4b72b3d2d87868635e6c54bbd924946189/watchfiles-1.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cf57a27fb986c6243d2ee78392c503826056ffe0287e8794503b10fb51b881be", size = 632191, upload-time = "2025-10-14T15:06:01.621Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/84/f39e19549c2f3ec97225dcb2ceb9a7bb3c5004ed227aad1f321bf0ff2051/watchfiles-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d7e7067c98040d646982daa1f37a33d3544138ea155536c2e0e63e07ff8a7e0f", size = 623923, upload-time = "2025-10-14T15:06:02.671Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/24/0759ae15d9a0c9c5fe946bd4cf45ab9e7bad7cfede2c06dc10f59171b29f/watchfiles-1.1.1-cp39-cp39-win32.whl", hash = "sha256:6c9c9262f454d1c4d8aaa7050121eb4f3aea197360553699520767daebf2180b", size = 274010, upload-time = "2025-10-14T15:06:03.779Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/3b/eb26cddd4dfa081e2bf6918be3b2fc05ee3b55c1d21331d5562ee0c6aaad/watchfiles-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:74472234c8370669850e1c312490f6026d132ca2d396abfad8830b4f1c096957", size = 289090, upload-time = "2025-10-14T15:06:04.821Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611, upload-time = "2025-10-14T15:06:05.809Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889, upload-time = "2025-10-14T15:06:07.035Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616, upload-time = "2025-10-14T15:06:08.072Z" },
+ { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413, upload-time = "2025-10-14T15:06:09.209Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" },
+ { url = "https://files.pythonhosted.org/packages/00/db/38a2c52fdbbfe2fc7ffaaaaaebc927d52b9f4d5139bba3186c19a7463001/watchfiles-1.1.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdab464fee731e0884c35ae3588514a9bcf718d0e2c82169c1c4a85cc19c3c7f", size = 409210, upload-time = "2025-10-14T15:06:14.492Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/43/d7e8b71f6c21ff813ee8da1006f89b6c7fff047fb4c8b16ceb5e840599c5/watchfiles-1.1.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3dbd8cbadd46984f802f6d479b7e3afa86c42d13e8f0f322d669d79722c8ec34", size = 397286, upload-time = "2025-10-14T15:06:16.177Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/5d/884074a5269317e75bd0b915644b702b89de73e61a8a7446e2b225f45b1f/watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5524298e3827105b61951a29c3512deb9578586abf3a7c5da4a8069df247cccc", size = 451768, upload-time = "2025-10-14T15:06:18.266Z" },
+ { url = "https://files.pythonhosted.org/packages/17/71/7ffcaa9b5e8961a25026058058c62ec8f604d2a6e8e1e94bee8a09e1593f/watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b943d3668d61cfa528eb949577479d3b077fd25fb83c641235437bc0b5bc60e", size = 458561, upload-time = "2025-10-14T15:06:19.323Z" },
+]
+
+[[package]]
+name = "wcwidth"
+version = "0.2.14"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" },
+]
+
+[[package]]
+name = "webencodings"
+version = "0.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" },
+]
+
+[[package]]
+name = "websockets"
+version = "15.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" },
+ { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" },
+ { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" },
+ { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" },
+ { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" },
+ { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" },
+ { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" },
+ { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" },
+ { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" },
+ { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" },
+ { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" },
+ { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" },
+ { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" },
+ { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" },
+ { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" },
+ { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" },
+ { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" },
+ { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" },
+ { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" },
+ { url = "https://files.pythonhosted.org/packages/36/db/3fff0bcbe339a6fa6a3b9e3fbc2bfb321ec2f4cd233692272c5a8d6cf801/websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5", size = 175424, upload-time = "2025-03-05T20:02:56.505Z" },
+ { url = "https://files.pythonhosted.org/packages/46/e6/519054c2f477def4165b0ec060ad664ed174e140b0d1cbb9fafa4a54f6db/websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a", size = 173077, upload-time = "2025-03-05T20:02:58.37Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/21/c0712e382df64c93a0d16449ecbf87b647163485ca1cc3f6cbadb36d2b03/websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b", size = 173324, upload-time = "2025-03-05T20:02:59.773Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/cb/51ba82e59b3a664df54beed8ad95517c1b4dc1a913730e7a7db778f21291/websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770", size = 182094, upload-time = "2025-03-05T20:03:01.827Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/0f/bf3788c03fec679bcdaef787518dbe60d12fe5615a544a6d4cf82f045193/websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb", size = 181094, upload-time = "2025-03-05T20:03:03.123Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/da/9fb8c21edbc719b66763a571afbaf206cb6d3736d28255a46fc2fe20f902/websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054", size = 181397, upload-time = "2025-03-05T20:03:04.443Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/65/65f379525a2719e91d9d90c38fe8b8bc62bd3c702ac651b7278609b696c4/websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee", size = 181794, upload-time = "2025-03-05T20:03:06.708Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/26/31ac2d08f8e9304d81a1a7ed2851c0300f636019a57cbaa91342015c72cc/websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed", size = 181194, upload-time = "2025-03-05T20:03:08.844Z" },
+ { url = "https://files.pythonhosted.org/packages/98/72/1090de20d6c91994cd4b357c3f75a4f25ee231b63e03adea89671cc12a3f/websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880", size = 181164, upload-time = "2025-03-05T20:03:10.242Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/37/098f2e1c103ae8ed79b0e77f08d83b0ec0b241cf4b7f2f10edd0126472e1/websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411", size = 176381, upload-time = "2025-03-05T20:03:12.77Z" },
+ { url = "https://files.pythonhosted.org/packages/75/8b/a32978a3ab42cebb2ebdd5b05df0696a09f4d436ce69def11893afa301f0/websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4", size = 176841, upload-time = "2025-03-05T20:03:14.367Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" },
+ { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" },
+ { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/48/4b67623bac4d79beb3a6bb27b803ba75c1bdedc06bd827e465803690a4b2/websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940", size = 173106, upload-time = "2025-03-05T20:03:29.404Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/f0/adb07514a49fe5728192764e04295be78859e4a537ab8fcc518a3dbb3281/websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e", size = 173339, upload-time = "2025-03-05T20:03:30.755Z" },
+ { url = "https://files.pythonhosted.org/packages/87/28/bd23c6344b18fb43df40d0700f6d3fffcd7cef14a6995b4f976978b52e62/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9", size = 174597, upload-time = "2025-03-05T20:03:32.247Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/79/ca288495863d0f23a60f546f0905ae8f3ed467ad87f8b6aceb65f4c013e4/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b", size = 174205, upload-time = "2025-03-05T20:03:33.731Z" },
+ { url = "https://files.pythonhosted.org/packages/04/e4/120ff3180b0872b1fe6637f6f995bcb009fb5c87d597c1fc21456f50c848/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f", size = 174150, upload-time = "2025-03-05T20:03:35.757Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/c3/30e2f9c539b8da8b1d76f64012f3b19253271a63413b2d3adb94b143407f/websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123", size = 176877, upload-time = "2025-03-05T20:03:37.199Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
+]
+
+[[package]]
+name = "werkzeug"
+version = "3.1.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5a/70/1469ef1d3542ae7c2c7b72bd5e3a4e6ee69d7978fa8a3af05a38eca5becf/werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67", size = 864754, upload-time = "2026-01-08T17:49:23.247Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ad/e4/8d97cca767bcc1be76d16fb76951608305561c6e056811587f36cb1316a8/werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc", size = 225025, upload-time = "2026-01-08T17:49:21.859Z" },
+]
+
+[[package]]
+name = "zipp"
+version = "3.23.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
+]