diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8979aabf8..fdca00387 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 c5b1f84f3..57c5e1120 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'
@@ -30,6 +31,7 @@ internal:
- .pre-commit-config.yaml
- pdm_build.py
- requirements*.txt
+ - uv.lock
- docs/en/data/sponsors.yml
- docs/en/overrides/main.html
- all-globs-to-all-files:
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
index 7de1590b1..77bce055c 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,15 +28,13 @@ 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
langs:
needs:
- changes
@@ -47,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@v6
+ 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:
@@ -89,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@v6
+ 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@v4
+ 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 ee8bfafb4..f3ced6aa3 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@v6
+ 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 ffa07b94d..734fc244d 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,32 +20,32 @@ 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@v6
+ 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 }}
RUN_ID: ${{ github.run_id }}
+ STATE: "pending"
- name: Clean site
run: |
rm -rf ./site
mkdir ./site
- - uses: actions/download-artifact@v5
+ - uses: actions/download-artifact@v7
with:
path: ./site/
pattern: docs-site-*
@@ -67,11 +64,19 @@ jobs:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy ./site --project-name=${{ env.PROJECT_NAME }} --branch=${{ env.BRANCH }}
+ - name: Deploy Docs Status Error
+ if: failure()
+ 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 }}
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
RUN_ID: ${{ github.run_id }}
- IS_DONE: "true"
+ STATE: "success"
diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml
index 6a7e6143e..2ae588da1 100644
--- a/.github/workflows/issue-manager.yml
+++ b/.github/workflows/issue-manager.yml
@@ -27,7 +27,7 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: tiangolo/issue-manager@0.5.1
+ - uses: tiangolo/issue-manager@0.6.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
config: >
@@ -38,10 +38,18 @@ jobs:
},
"waiting": {
"delay": 2628000,
- "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."
+ "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."
+ }
},
"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 76ac77298..1307fb8c2 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@v6
+ 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 2fa832fab..b9e45ea62 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 ef3990d31..31f3eb402 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@v6
+ 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 e6e56bf04..cb3b74278 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@v6
+ 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 000000000..f027140ed
--- /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 441eb4560..2232498cb 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 cde0ca308..f23b962b7 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@v6
+ 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@v5
+ 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 1e245346d..88590ffa6 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@v6
+ 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 a44f0b681..0491d33ba 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 b76afe01e..891f76717 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,89 +13,80 @@ 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@v6
- 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.13"
- - "3.12"
- - "3.11"
- - "3.10"
- - "3.9"
- - "3.8"
- pydantic-version: ["pydantic-v1", "pydantic-v2"]
+ 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:
python-version: ${{ matrix.python-version }}
- name: Setup uv
- uses: astral-sh/setup-uv@v6
+ 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: 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:
+ 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
- uses: actions/upload-artifact@v4
+ if: matrix.coverage == 'coverage'
+ uses: actions/upload-artifact@v6
with:
- name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }}
+ name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/coverage/.coverage.*') }}
path: coverage
include-hidden-files: true
@@ -107,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@v6
+ 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@v5
+ 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@v4
+ 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 cb98698d3..46f6d6084 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@v6
+ 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 fa4e8f463..83518614b 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:
+ 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@v6
+ 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 ef6364a9a..243cdb93a 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 8b24c8c0c..d88b70b7b 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.13.1
+ - --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 000000000..2c0733315
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.11
diff --git a/README.md b/README.md
index a8a0e37b5..1057b8694 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
+
+
- FastAPI framework, yüksək məshuldarlı, öyrənməsi asan, çevik kodlama, istifadəyə hazırdır
-
- FastAPI উচ্চক্ষমতা সম্পন্ন, সহজে শেখার এবং দ্রুত কোড করে প্রোডাকশনের জন্য ফ্রামওয়ার্ক।
-
- FastAPI 🛠️, ↕ 🎭, ⏩ 💡, ⏩ 📟, 🔜 🏭
-
+
+### Gold and Silver Sponsors
@@ -55,6 +60,8 @@ The key features are:
+
+
@@ -113,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
@@ -125,7 +138,7 @@ If you are building a CLI app to be
FastAPI stands on the shoulders of giants:
-* Starlette for the web parts.
+* Starlette for the web parts.
* Pydantic for the data parts.
## Installation
@@ -231,7 +244,7 @@ INFO: Application startup complete.
About the command
-The command `fastapi dev` reads your `main.py` file, detects the **FastAPI** app in it, and starts a server using Uvicorn.
+The command `fastapi dev` reads your `main.py` file, detects the **FastAPI** app in it, and starts a server using Uvicorn.
By default, `fastapi dev` will start with auto-reload enabled for local development.
@@ -446,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:
+
+fastapi dev main.py...uvicorn - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving.
+* uvicorn - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving.
* `fastapi-cli[standard]` - to provide the `fastapi` command.
* This includes `fastapi-cloud-cli`, which allows you to deploy your FastAPI application to FastAPI Cloud.
diff --git a/docs/az/docs/index.md b/docs/az/docs/index.md
deleted file mode 100644
index 4e14d7060..000000000
--- a/docs/az/docs/index.md
+++ /dev/null
@@ -1,467 +0,0 @@
-
-
-{% endfor -%}`
-{%- for sponsor in sponsors.silver -%}
-
-{% endfor %}
-{% endif %}
-
-
-
-Digər sponsorlar
-
-## Rəylər
-
-"_[...] Son günlərdə **FastAPI**-ı çox istifadə edirəm. [...] Əslində onu komandamın bütün **Microsoftda ML sevislərində** istifadə etməyi planlayıram. Onların bəziləri **windows**-un əsas məhsuluna və bəzi **Office** məhsullarına inteqrasiya olunurlar._"
-
-
-
-Əgər siz veb API əvəzinə terminalda istifadə ediləcək CLI proqramı qurursunuzsa, **Typer**-a baxa bilərsiniz.
-
-**Typer** FastAPI-ın kiçik qardaşıdır. Və o, CLI-lərin **FastAPI**-ı olmaq üçün nəzərdə tutulub. ⌨️ 🚀
-
-## Tələblər
-
-FastAPI nəhənglərin çiyinlərində dayanır:
-
-* Web tərəfi üçün Starlette.
-* Data tərəfi üçün Pydantic.
-
-## Quraşdırma
-
-
Və ya
-
-Əgər kodunuzda `async` və ya `await` vardırsa `async def` istifadə edə bilərik:
-
-```Python hl_lines="9 14"
-from typing import Union
-
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/")
-async def read_root():
- return {"Hello": "World"}
-
-
-@app.get("/items/{item_id}")
-async def read_item(item_id: int, q: Union[str, None] = None):
- return {"item_id": item_id, "q": q}
-```
-
-**Qeyd**:
-
-Əgər bu mövzu haqqında məlumatınız yoxdursa `async` və `await` sənədindəki _"Tələsirsən?"_ bölməsinə baxa bilərsiniz.
-
-async def...
-
-`uvicorn main:app` əmri aşağıdakılara instinad edir:
-
-* `main`: `main.py` faylı (yəni Python "modulu").
-* `app`: `main.py` faylında `app = FastAPI()` sətrində yaratdığımız `FastAPI` obyektidir.
-* `--reload`: kod dəyişikliyindən sonra avtomatik olaraq serveri yenidən işə salır. Bu parametrdən yalnız development mərhələsində istifadə etməliyik.
-
-uvicorn main:app --reload əmri haqqında...email-validator - e-poçtun yoxlanılması üçün.
-* pydantic-settings - parametrlərin idarə edilməsi üçün.
-* pydantic-extra-types - Pydantic ilə istifadə edilə bilən əlavə tiplər üçün.
-
-Starlette tərəfindən istifadə olunanlar:
-
-* httpx - Əgər `TestClient` strukturundan istifadə edəcəksinizsə, tələb olunur.
-* jinja2 - Standart şablon konfiqurasiyasından istifadə etmək istəyirsinizsə, tələb olunur.
-* python-multipart - `request.form()` ilə forma "çevirmə" dəstəyindən istifadə etmək istəyirsinizsə, tələb olunur.
-* itsdangerous - `SessionMiddleware` dəstəyi üçün tələb olunur.
-* pyyaml - `SchemaGenerator` dəstəyi üçün tələb olunur (Çox güman ki, FastAPI istifadə edərkən buna ehtiyacınız olmayacaq).
-* ujson - `UJSONResponse` istifadə etmək istəyirsinizsə, tələb olunur.
-
-Həm FastAPI, həm də Starlette tərəfindən istifadə olunur:
-
-* uvicorn - Yaratdığımız proqramı servis edəcək veb server kimi fəaliyyət göstərir.
-* orjson - `ORJSONResponse` istifadə edəcəksinizsə tələb olunur.
-
-Bütün bunları `pip install fastapi[all]` ilə quraşdıra bilərsiniz.
-
-## Lisenziya
-
-Bu layihə MIT lisenziyasının şərtlərinə əsasən lisenziyalaşdırılıb.
diff --git a/docs/az/docs/learn/index.md b/docs/az/docs/learn/index.md
deleted file mode 100644
index cc32108bf..000000000
--- a/docs/az/docs/learn/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Öyrən
-
-Burada **FastAPI** öyrənmək üçün giriş bölmələri və dərsliklər yer alır.
-
-Siz bunu kitab, kurs, FastAPI öyrənmək üçün rəsmi və tövsiyə olunan üsul hesab edə bilərsiniz. 😎
diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml
deleted file mode 100644
index de18856f4..000000000
--- a/docs/az/mkdocs.yml
+++ /dev/null
@@ -1 +0,0 @@
-INHERIT: ../en/mkdocs.yml
diff --git a/docs/bn/docs/about/index.md b/docs/bn/docs/about/index.md
deleted file mode 100644
index b6d611ae9..000000000
--- a/docs/bn/docs/about/index.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# সম্পর্কে
-
-**FastAPI** সম্পর্কে বিস্তারিত — এর ডিজাইন, অনুপ্রেরণা ও আরও অনেক কিছু। 🤓
diff --git a/docs/bn/docs/environment-variables.md b/docs/bn/docs/environment-variables.md
deleted file mode 100644
index 9122ca5bf..000000000
--- a/docs/bn/docs/environment-variables.md
+++ /dev/null
@@ -1,298 +0,0 @@
-# এনভায়রনমেন্ট ভেরিয়েবলস
-
-/// tip
-
-আপনি যদি "এনভায়রনমেন্ট ভেরিয়েবলস" কী এবং সেগুলো কীভাবে ব্যবহার করতে হয় সেটা জানেন, তাহলে এই অংশটি স্কিপ করে যেতে পারেন।
-
-///
-
-এনভায়রনমেন্ট ভেরিয়েবল (সংক্ষেপে "**env var**" নামেও পরিচিত) হলো এমন একটি ভেরিয়েবল যা পাইথন কোডের **বাইরে**, **অপারেটিং সিস্টেমে** থাকে এবং আপনার পাইথন কোড (বা অন্যান্য প্রোগ্রাম) দ্বারা যাকে রিড করা যায়।
-
-এনভায়রনমেন্ট ভেরিয়েবলস অ্যাপ্লিকেশনের **সেটিংস** পরিচালনা করতে, পাইথনের **ইনস্টলেশন** প্রক্রিয়ার অংশ হিসেবে, ইত্যাদি কাজে উপযোগী হতে পারে।
-
-## Env Vars তৈরী এবং ব্যবহার
-
-আপনি **শেল (টার্মিনাল)**-এ, পাইথনের প্রয়োজন ছাড়াই, এনভায়রনমেন্ট ভেরিয়েবলস **তৈরি** এবং ব্যবহার করতে পারবেনঃ
-
-//// tab | লিনাক্স, ম্যাকওএস, উইন্ডোজ Bash
-
-
-{% endfor -%}
-{%- for sponsor in sponsors.silver -%}
-
-{% endfor %}
-{% endif %}
-
-
-
-অন্যান্য স্পনসর গণ
-
-## মতামত সমূহ
-
-"_আমি আজকাল **FastAPI** ব্যবহার করছি। [...] আমরা ভাবছি মাইক্রোসফ্টে **ML সার্ভিস** এ সকল দলের জন্য এটি ব্যবহার করব। যার মধ্যে কিছু পণ্য **Windows** এ সংযোযন হয় এবং কিছু **Office** এর সাথে সংযোযন হচ্ছে।_"
-
-
-
-আপনি যদি CLI অ্যাপ বানাতে চান, যা কিনা ওয়েব API এর পরিবর্তে টার্মিনালে ব্যবহার হবে, তাহলে দেখুন**Typer**.
-
-**টাইপার** হল FastAPI এর ছোট ভাইয়ের মত। এবং এটির উদ্দেশ্য ছিল **CLIs এর FastAPI** হওয়া। ⌨️ 🚀
-
-## প্রয়োজনীয়তা গুলো
-
-Python 3.7+
-
-FastAPI কিছু দানবেদের কাঁধে দাঁড়িয়ে আছে:
-
-- Starlette ওয়েব অংশের জন্য.
-- Pydantic ডেটা অংশগুলির জন্য.
-
-## ইনস্টলেশন প্রক্রিয়া
-
-
অথবা ব্যবহার করুন
-
-যদি আপনার কোড `async` / `await`, ব্যবহার করে তাহলে `async def` ব্যবহার করুন:
-
-```Python hl_lines="9 14"
-from typing import Union
-
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/")
-async def read_root():
- return {"Hello": "World"}
-
-
-@app.get("/items/{item_id}")
-async def read_item(item_id: int, q: Union[str, None] = None):
- return {"item_id": item_id, "q": q}
-```
-
-**টীকা**:
-
-আপনি যদি না জানেন, _"তাড়াহুড়ো?"_ বিভাগটি দেখুন `async` এবং `await` নথির মধ্যে দেখুন .
-
-async def...নির্দেশনা সম্পর্কে
-
-`uvicorn main:app` নির্দেশনাটি দ্বারা বোঝায়:
-
-- `main`: ফাইল `main.py` (পাইথন "মডিউল")।
-- `app`: `app = FastAPI()` লাইন দিয়ে `main.py` এর ভিতরে তৈরি করা অবজেক্ট।
-- `--reload`: কোড পরিবর্তনের পরে সার্ভার পুনরায় চালু করুন। এটি শুধুমাত্র ডেভেলপমেন্ট এর সময় ব্যবহার করুন।
-
-uvicorn main:app --reload...email-validator - ইমেল যাচাইকরণের জন্য।
-
-স্টারলেট দ্বারা ব্যবহৃত:
-
-- httpx - আপনি যদি `TestClient` ব্যবহার করতে চান তাহলে আবশ্যক।
-- jinja2 - আপনি যদি প্রদত্ত টেমপ্লেট রূপরেখা ব্যবহার করতে চান তাহলে প্রয়োজন।
-- python-multipart - আপনি যদি ফর্ম সহায়তা করতে চান তাহলে প্রয়োজন "parsing", `request.form()` সহ।
-- itsdangerous - `SessionMiddleware` সহায়তার জন্য প্রয়োজন।
-- pyyaml - স্টারলেটের SchemaGenerator সাপোর্ট এর জন্য প্রয়োজন (আপনার সম্ভাবত FastAPI প্রয়োজন নেই)।
-- graphene - `GraphQLApp` সহায়তার জন্য প্রয়োজন।
-
-FastAPI / Starlette দ্বারা ব্যবহৃত:
-
-- uvicorn - সার্ভারের জন্য যা আপনার অ্যাপ্লিকেশন লোড করে এবং পরিবেশন করে।
-- orjson - আপনি `ORJSONResponse` ব্যবহার করতে চাইলে প্রয়োজন।
-- ujson - আপনি `UJSONResponse` ব্যবহার করতে চাইলে প্রয়োজন।
-
-আপনি এই সব ইনস্টল করতে পারেন `pip install fastapi[all]` দিয়ে.
-
-## লাইসেন্স
-
-এই প্রজেক্ট MIT লাইসেন্স নীতিমালার অধীনে শর্তায়িত।
diff --git a/docs/bn/docs/learn/index.md b/docs/bn/docs/learn/index.md
deleted file mode 100644
index 4e4c62038..000000000
--- a/docs/bn/docs/learn/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# শিখুন
-
-এখানে **FastAPI** শিখার জন্য প্রাথমিক বিভাগগুলি এবং টিউটোরিয়ালগুলি রয়েছে।
-
-আপনি এটিকে একটি **বই**, একটি **কোর্স**, এবং FastAPI শিখার **অফিসিয়াল** এবং প্রস্তাবিত উপায় বিবেচনা করতে পারেন। 😎
diff --git a/docs/bn/docs/python-types.md b/docs/bn/docs/python-types.md
deleted file mode 100644
index d98c2ec87..000000000
--- a/docs/bn/docs/python-types.md
+++ /dev/null
@@ -1,586 +0,0 @@
-# পাইথন এর টাইপ্স পরিচিতি
-
-Python-এ ঐচ্ছিক "টাইপ হিন্ট" (যা "টাইপ অ্যানোটেশন" নামেও পরিচিত) এর জন্য সাপোর্ট রয়েছে।
-
-এই **"টাইপ হিন্ট"** বা অ্যানোটেশনগুলি এক ধরণের বিশেষ সিনট্যাক্স যা একটি ভেরিয়েবলের টাইপ ঘোষণা করতে দেয়।
-
-ভেরিয়েবলগুলির জন্য টাইপ ঘোষণা করলে, এডিটর এবং টুলগুলি আপনাকে আরও ভালো সাপোর্ট দিতে পারে।
-
-এটি পাইথন টাইপ হিন্ট সম্পর্কে একটি দ্রুত **টিউটোরিয়াল / রিফ্রেশার** মাত্র। এটি **FastAPI** এর সাথে ব্যবহার করার জন্য শুধুমাত্র ন্যূনতম প্রয়োজনীয়তা কভার করে... যা আসলে খুব একটা বেশি না।
-
-**FastAPI** এই টাইপ হিন্টগুলির উপর ভিত্তি করে নির্মিত, যা এটিকে অনেক সুবিধা এবং লাভ প্রদান করে।
-
-তবে, আপনি যদি কখনো **FastAPI** ব্যবহার নাও করেন, তবুও এগুলি সম্পর্কে একটু শেখা আপনার উপকারে আসবে।
-
-/// note
-
-যদি আপনি একজন Python বিশেষজ্ঞ হন, এবং টাইপ হিন্ট সম্পর্কে সবকিছু জানেন, তাহলে পরবর্তী অধ্যায়ে চলে যান।
-
-///
-
-## প্রেরণা
-
-চলুন একটি সাধারণ উদাহরণ দিয়ে শুরু করি:
-
-{* ../../docs_src/python_types/tutorial001.py *}
-
-
-এই প্রোগ্রামটি কল করলে আউটপুট হয়:
-
-```
-John Doe
-```
-
-ফাংশনটি নিম্নলিখিত কাজ করে:
-
-* `first_name` এবং `last_name` নেয়।
-* প্রতিটির প্রথম অক্ষরকে `title()` ব্যবহার করে বড় হাতের অক্ষরে রূপান্তর করে।
-* তাদেরকে মাঝখানে একটি স্পেস দিয়ে concatenate করে।
-
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
-
-
-### এটি সম্পাদনা করুন
-
-এটি একটি খুব সাধারণ প্রোগ্রাম।
-
-কিন্তু এখন কল্পনা করুন যে আপনি এটি শুরু থেকে লিখছিলেন।
-
-এক পর্যায়ে আপনি ফাংশনের সংজ্ঞা শুরু করেছিলেন, আপনার প্যারামিটারগুলি প্রস্তুত ছিল...
-
-কিন্তু তারপর আপনাকে "সেই method কল করতে হবে যা প্রথম অক্ষরকে বড় হাতের অক্ষরে রূপান্তর করে"।
-
-এটা কি `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`। এবং অভ্যন্তরীণ মানগুলোরও নিজেদের টাইপ থাকতে পারে।
-
-এই ধরনের টাইপগুলিকে বলা হয় "**জেনেরিক**" টাইপ এবং এগুলিকে তাদের অভ্যন্তরীণ টাইপগুলি সহ ঘোষণা করা সম্ভব।
-
-এই টাইপগুলি এবং অভ্যন্তরীণ টাইপগুলি ঘোষণা করতে, আপনি Python মডিউল `typing` ব্যবহার করতে পারেন। এটি বিশেষভাবে এই টাইপ হিন্টগুলি সমর্থন করার জন্য রয়েছে।
-
-#### Python এর নতুন সংস্করণ
-
-`typing` ব্যবহার করা সিনট্যাক্সটি Python 3.6 থেকে সর্বশেষ সংস্করণগুলি পর্যন্ত, অর্থাৎ Python 3.9, Python 3.10 ইত্যাদি সহ সকল সংস্করণের সাথে **সামঞ্জস্যপূর্ণ**।
-
-Python যত এগিয়ে যাচ্ছে, **নতুন সংস্করণগুলি** এই টাইপ অ্যানোটেশনগুলির জন্য তত উন্নত সাপোর্ট নিয়ে আসছে এবং অনেক ক্ষেত্রে আপনাকে টাইপ অ্যানোটেশন ঘোষণা করতে `typing` মডিউল ইম্পোর্ট এবং ব্যবহার করার প্রয়োজন হবে না।
-
-যদি আপনি আপনার প্রজেক্টের জন্য Python-এর আরও সাম্প্রতিক সংস্করণ নির্বাচন করতে পারেন, তাহলে আপনি সেই অতিরিক্ত সরলতা থেকে সুবিধা নিতে পারবেন।
-
-ডক্সে রয়েছে 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+**"।
-
-#### লিস্ট
-
-উদাহরণস্বরূপ, একটি ভেরিয়েবলকে `str`-এর একটি `list` হিসেবে সংজ্ঞায়িত করা যাক।
-
-//// 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!}
-```
-
-ভেরিয়েবলটি ঘোষণা করুন, একই কোলন (`:`) সিনট্যাক্স ব্যবহার করে।
-
-টাইপ হিসেবে, `typing` থেকে আপনার ইম্পোর্ট করা `List` ব্যবহার করুন।
-
-যেহেতু লিস্ট এমন একটি টাইপ যা অভ্যন্তরীণ টাইপগুলি ধারণ করে, আপনি তাদের স্কোয়ার ব্রাকেটের ভিতরে করুন:
-
-```Python hl_lines="4"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-////
-
-/// info
-
-স্কোয়ার ব্রাকেট এর ভিতরে ব্যবহৃত এইসব অভন্তরীন টাইপগুলোকে "ইন্টারনাল টাইপ" বলে।
-
-এই উদাহরণে, এটি হচ্ছে `List`(অথবা পাইথন ৩.৯ বা তার উপরের সংস্করণের ক্ষেত্রে `list`) এ পাস করা টাইপ প্যারামিটার।
-
-///
-
-এর অর্থ হচ্ছে: "ভেরিয়েবল `items` একটি `list`, এবং এই লিস্টের প্রতিটি আইটেম একটি `str`।"
-
-/// tip
-
-যদি আপনি Python 3.9 বা তার উপরে ব্যবহার করেন, আপনার `typing` থেকে `List` আমদানি করতে হবে না, আপনি সাধারণ `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` যা ৩টি আইটেম ধারণ করে, একটি `int`, অন্য একটি `int`, এবং একটি `str`।
-* ভেরিয়েবল `items_s` হল একটি `set`, এবং এর প্রতিটি আইটেম হল `bytes` টাইপের।
-
-#### ডিক্ট
-
-একটি `dict` সংজ্ঞায়িত করতে, আপনি ২টি টাইপ প্যারামিটার কমা দ্বারা পৃথক করে দেবেন।
-
-প্রথম টাইপ প্যারামিটারটি হল `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`।
-
-Python 3.6 এবং তার উপরের সংস্করণগুলিতে (Python 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.8+
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008b.py!}
-```
-
-////
-
-উভয় ক্ষেত্রেই এর মানে হল যে `item` হতে পারে একটি `int` অথবা `str`।
-
-#### সম্ভবত `None`
-
-আপনি এমনভাবে ঘোষণা করতে পারেন যে একটি মান হতে পারে এক টাইপের, যেমন `str`, আবার এটি `None`-ও হতে পারে।
-
-Python 3.6 এবং তার উপরের সংস্করণগুলিতে (Python 3.10 অনতর্ভুক্ত) আপনি `typing` মডিউল থেকে `Optional` ইমপোর্ট করে এটি ঘোষণা এবং ব্যবহার করতে পারেন।
-
-```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
-```
-
-`Optional[str]` ব্যবহার করা মানে হল শুধু `str` নয়, এটি হতে পারে `None`-ও, যা আপনার এডিটরকে সেই ত্রুটিগুলি শনাক্ত করতে সাহায্য করবে যেখানে আপনি ধরে নিচ্ছেন যে একটি মান সবসময় `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.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` ব্যবহার
-
-যদি আপনি Python 3.10-এর নীচের সংস্করণ ব্যবহার করেন, তবে এখানে আমার খুবই **ব্যক্তিগত** দৃষ্টিভঙ্গি থেকে একটি টিপস:
-
-* 🚨 `Optional[SomeType]` ব্যবহার এড়িয়ে চলুন।
-* এর পরিবর্তে ✨ **`Union[SomeType, None]` ব্যবহার করুন** ✨।
-
-উভয়ই সমতুল্য এবং মূলে একই, কিন্তু আমি `Union`-এর পক্ষে সুপারিশ করব কারণ "**অপশনাল**" শব্দটি মনে হতে পারে যে মানটি ঐচ্ছিক,অথচ এটি আসলে মানে "এটি হতে পারে `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 বৈধ 🎉
-```
-
-সুখবর হল, একবার আপনি Python 3.10 ব্যবহার করা শুরু করলে, আপনাকে এগুলোর ব্যাপারে আর চিন্তা করতে হবে না, যেহুতু আপনি | ব্যবহার করেই ইউনিয়ন ঘোষণা করতে পারবেন:
-
-{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
-
-
-এবং তারপর আপনাকে নামগুলি যেমন `Optional` এবং `Union` নিয়ে আর চিন্তা করতে হবে না। 😎
-
-#### জেনেরিক টাইপস
-
-স্কোয়ার ব্র্যাকেটে টাইপ প্যারামিটার নেওয়া এই টাইপগুলিকে **জেনেরিক টাইপ** বা **জেনেরিকস** বলা হয়, যেমন:
-
-//// tab | Python 3.10+
-
-আপনি সেই একই বিল্টইন টাইপ জেনেরিক্স হিসেবে ব্যবহার করতে পারবেন(ভিতরে টাইপ সহ স্কয়ারে ব্রাকেট দিয়ে):
-
-* `list`
-* `tuple`
-* `set`
-* `dict`
-
-এবং Python 3.8 এর মতোই, `typing` মডিউল থেকে:
-
-* `Union`
-* `Optional` (Python 3.8 এর মতোই)
-* ...এবং অন্যান্য।
-
-Python 3.10-এ, `Union` এবং `Optional` জেনেরিকস ব্যবহার করার বিকল্প হিসেবে, আপনি টাইপগুলির ইউনিয়ন ঘোষণা করতে ভার্টিকাল বার (`|`) ব্যবহার করতে পারেন, যা ওদের থেকে অনেক ভালো এবং সহজ।
-
-////
-
-//// tab | Python 3.9+
-
-আপনি সেই একই বিল্টইন টাইপ জেনেরিক্স হিসেবে ব্যবহার করতে পারবেন(ভিতরে টাইপ সহ স্কয়ারে ব্রাকেট দিয়ে):
-
-* `list`
-* `tuple`
-* `set`
-* `dict`
-
-এবং Python 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](https://docs.pydantic.dev/) হল একটি Python লাইব্রেরি যা ডাটা ভ্যালিডেশন সম্পাদন করে।
-
-আপনি ডাটার "আকার" এট্রিবিউট সহ ক্লাস হিসেবে ঘোষণা করেন।
-
-এবং প্রতিটি এট্রিবিউট এর একটি টাইপ থাকে।
-
-তারপর আপনি যদি কিছু মান দিয়ে সেই ক্লাসের একটি ইন্সট্যান্স তৈরি করেন-- এটি মানগুলিকে ভ্যালিডেট করবে, প্রয়োজন অনুযায়ী তাদেরকে উপযুক্ত টাইপে রূপান্তর করবে এবং আপনাকে সমস্ত ডাটা সহ একটি অবজেক্ট প্রদান করবে।
-
-এবং আপনি সেই ফলাফল অবজেক্টের সাথে এডিটর সাপোর্ট পাবেন।
-
-অফিসিয়াল 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 সম্পর্কে আরও জানতে, এর ডকুমেন্টেশন দেখুন](https://docs.pydantic.dev/)।
-
-///
-
-**FastAPI** মূলত Pydantic-এর উপর নির্মিত।
-
-আপনি এই সমস্ত কিছুর অনেক বাস্তবসম্মত উদাহরণ পাবেন [টিউটোরিয়াল - ইউজার গাইডে](https://fastapi.tiangolo.com/tutorial/)।
-
-/// tip
-
-যখন আপনি `Optional` বা `Union[Something, None]` ব্যবহার করেন এবং কোনো ডিফল্ট মান না থাকে, Pydantic-এর একটি বিশেষ আচরণ রয়েছে, আপনি Pydantic ডকুমেন্টেশনে [Required Optional fields](https://docs.pydantic.dev/latest/concepts/models/#required-optional-fields) সম্পর্কে আরও পড়তে পারেন।
-
-///
-
-## মেটাডাটা অ্যানোটেশন সহ টাইপ হিন্টস
-
-Python-এ এমন একটি ফিচার আছে যা `Annotated` ব্যবহার করে এই টাইপ হিন্টগুলিতে **অতিরিক্ত মেটাডাটা** রাখতে দেয়।
-
-//// tab | Python 3.9+
-
-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!}
-```
-
-////
-
-Python নিজে এই `Annotated` দিয়ে কিছুই করে না। এবং এডিটর এবং অন্যান্য টুলগুলির জন্য, টাইপটি এখনও `str`।
-
-কিন্তু আপনি এই `Annotated` এর মধ্যকার জায়গাটির মধ্যে **FastAPI**-এ কীভাবে আপনার অ্যাপ্লিকেশন আচরণ করুক তা সম্পর্কে অতিরিক্ত মেটাডাটা প্রদান করার জন্য ব্যবহার করতে পারেন।
-
-মনে রাখার গুরুত্বপূর্ণ বিষয় হল যে **প্রথম *টাইপ প্যারামিটার*** আপনি `Annotated`-এ পাস করেন সেটি হল **আসল টাইপ**। বাকি শুধুমাত্র অন্যান্য টুলগুলির জন্য মেটাডাটা।
-
-এখন আপনার কেবল জানা প্রয়োজন যে `Annotated` বিদ্যমান, এবং এটি স্ট্যান্ডার্ড Python। 😎
-
-পরবর্তীতে আপনি দেখবেন এটি কতটা **শক্তিশালী** হতে পারে।
-
-/// tip
-
-এটি **স্ট্যান্ডার্ড Python** হওয়ার মানে হল আপনি আপনার এডিটরে, আপনি যে টুলগুলি কোড বিশ্লেষণ এবং রিফ্যাক্টর করার জন্য ব্যবহার করেন তাতে **সেরা সম্ভাব্য ডেভেলপার এক্সপেরিয়েন্স** পাবেন। ✨
-
-এবং এছাড়াও আপনার কোড অন্যান্য অনেক Python টুল এবং লাইব্রেরিগুলির সাথে খুব সামঞ্জস্যপূর্ণ হবে। 🚀
-
-///
-
-## **FastAPI**-এ টাইপ হিন্টস
-
-**FastAPI** এই টাইপ হিন্টগুলি ব্যবহার করে বেশ কিছু জিনিস করে।
-
-**FastAPI**-এ আপনি টাইপ হিন্টগুলি সহ প্যারামিটার ঘোষণা করেন এবং আপনি পান:
-
-* **এডিটর সাপোর্ট**।
-* **টাইপচেক**।
-
-...এবং **FastAPI** একই ঘোষণাগুলি ব্যবহার করে:
-
-* **রিকুইরেমেন্টস সংজ্ঞায়িত করে**: রিকোয়েস্ট পাথ প্যারামিটার, কুয়েরি প্যারামিটার, হেডার, বডি, ডিপেন্ডেন্সিস, ইত্যাদি থেকে।
-* **ডেটা রূপান্তর করে**: রিকোয়েস্ট থেকে প্রয়োজনীয় টাইপে ডেটা।
-* **ডেটা যাচাই করে**: প্রতিটি রিকোয়েস্ট থেকে আসা ডেটা:
- * যখন ডেটা অবৈধ হয় তখন **স্বয়ংক্রিয় ত্রুটি** গ্রাহকের কাছে ফেরত পাঠানো।
-* **API ডকুমেন্টেশন তৈরি করে**: OpenAPI ব্যবহার করে:
- * যা স্বয়ংক্রিয় ইন্টার্যাক্টিভ ডকুমেন্টেশন ইউজার ইন্টারফেস দ্বারা ব্যবহৃত হয়।
-
-এই সব কিছু আপনার কাছে অস্পষ্ট মনে হতে পারে। চিন্তা করবেন না। আপনি [টিউটোরিয়াল - ইউজার গাইড](https://fastapi.tiangolo.com/tutorial/) এ এই সব কিছু প্র্যাকটিসে দেখতে পাবেন।
-
-গুরুত্বপূর্ণ বিষয় হল, আপনি যদি স্ট্যান্ডার্ড Python টাইপগুলি ব্যবহার করেন, তবে আরও বেশি ক্লাস, ডেকোরেটর ইত্যাদি যোগ না করেই একই স্থানে **FastAPI** আপনার অনেক কাজ করে দিবে।
-
-/// info
-
-যদি আপনি টিউটোরিয়ালের সমস্ত বিষয় পড়ে ফেলে থাকেন এবং টাইপ সম্পর্কে আরও জানতে চান, তবে একটি ভালো রিসোর্স হল [mypy এর "cheat sheet"](https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html)। এই "cheat sheet" এ আপনি Python টাইপ হিন্ট সম্পর্কে বেসিক থেকে উন্নত লেভেলের ধারণা পেতে পারেন, যা আপনার কোডে টাইপ সেফটি এবং স্পষ্টতা বাড়াতে সাহায্য করবে।
-
-///
diff --git a/docs/bn/mkdocs.yml b/docs/bn/mkdocs.yml
deleted file mode 100644
index de18856f4..000000000
--- a/docs/bn/mkdocs.yml
+++ /dev/null
@@ -1 +0,0 @@
-INHERIT: ../en/mkdocs.yml
diff --git a/docs/de/docs/_llm-test.md b/docs/de/docs/_llm-test.md
index 4a5e5392c..0b95fe3a8 100644
--- a/docs/de/docs/_llm-test.md
+++ b/docs/de/docs/_llm-test.md
@@ -15,7 +15,7 @@ So verwenden:
Die Tests:
-## Codeschnipsel { #code-snippets}
+## Codeschnipsel { #code-snippets }
//// tab | Test
@@ -47,13 +47,13 @@ Das LLM wird dies wahrscheinlich falsch übersetzen. Interessant ist nur, ob es
//// tab | Info
-Der Prompt-Designer kann entscheiden, ob neutrale Anführungszeichen in typografische Anführungszeichen umgewandelt werden sollen. Es ist in Ordnung, sie unverändert zu lassen.
+Der Promptdesigner kann entscheiden, ob neutrale Anführungszeichen in typografische Anführungszeichen umgewandelt werden sollen. Es ist in Ordnung, sie unverändert zu lassen.
Siehe zum Beispiel den Abschnitt `### Quotes` in `docs/de/llm-prompt.md`.
////
-## Anführungszeichen in Codeschnipseln { #quotes-in-code-snippets}
+## Anführungszeichen in Codeschnipseln { #quotes-in-code-snippets }
//// tab | Test
@@ -189,7 +189,7 @@ Siehe Abschnitt `### Links` im allgemeinen Prompt in `scripts/translate.py`.
////
-## HTML „abbr“-Elemente { #html-abbr-elements }
+## HTML-„abbr“-Elemente { #html-abbr-elements }
//// tab | Test
@@ -443,7 +443,7 @@ Für einige sprachspezifische Anweisungen, siehe z. B. den Abschnitt `### Headin
* die Workload
* das Deployment
-* bereitstellen
+* deployen
* das SDK
* das Software Development Kit
@@ -459,7 +459,7 @@ Für einige sprachspezifische Anweisungen, siehe z. B. den Abschnitt `### Headin
* der Commit
* der Contextmanager
* die Coroutine
-* die Datenbank-Session
+* die Datenbanksession
* die Festplatte
* die Domain
* die Engine
@@ -496,7 +496,7 @@ Für einige sprachspezifische Anweisungen, siehe z. B. den Abschnitt `### Headin
//// tab | Info
-Dies ist eine nicht vollständige und nicht normative Liste von (meist) technischen Begriffen, die in der Dokumentation vorkommen. Sie kann dem Prompt-Designer helfen herauszufinden, bei welchen Begriffen das LLM Unterstützung braucht. Zum Beispiel, wenn es eine gute Übersetzung immer wieder auf eine suboptimale Übersetzung zurücksetzt. Oder wenn es Probleme hat, einen Begriff in Ihrer Sprache zu konjugieren/deklinieren.
+Dies ist eine nicht vollständige und nicht normative Liste von (meist) technischen Begriffen, die in der Dokumentation vorkommen. Sie kann dem Promptdesigner helfen herauszufinden, bei welchen Begriffen das LLM Unterstützung braucht. Zum Beispiel, wenn es eine gute Übersetzung immer wieder auf eine suboptimale Übersetzung zurücksetzt. Oder wenn es Probleme hat, einen Begriff in Ihrer Sprache zu konjugieren/deklinieren.
Siehe z. B. den Abschnitt `### List of English terms and their preferred German translations` in `docs/de/llm-prompt.md`.
diff --git a/docs/de/docs/advanced/additional-responses.md b/docs/de/docs/advanced/additional-responses.md
index 218dd6c4f..0f9b12251 100644
--- a/docs/de/docs/advanced/additional-responses.md
+++ b/docs/de/docs/advanced/additional-responses.md
@@ -26,7 +26,7 @@ Jedes dieser Response-`dict`s kann einen Schlüssel `model` haben, welcher ein P
Um beispielsweise eine weitere Response mit dem Statuscode `404` und einem Pydantic-Modell `Message` zu deklarieren, können Sie schreiben:
-{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *}
+{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *}
/// note | Hinweis
@@ -175,7 +175,7 @@ Sie können denselben `responses`-Parameter verwenden, um verschiedene Medientyp
Sie können beispielsweise einen zusätzlichen Medientyp `image/png` hinzufügen und damit deklarieren, dass Ihre *Pfadoperation* ein JSON-Objekt (mit dem Medientyp `application/json`) oder ein PNG-Bild zurückgeben kann:
-{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *}
+{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
/// note | Hinweis
@@ -203,7 +203,7 @@ Sie können beispielsweise eine Response mit dem Statuscode `404` deklarieren, d
Und eine Response mit dem Statuscode `200`, die Ihr `response_model` verwendet, aber ein benutzerdefiniertes Beispiel (`example`) enthält:
-{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *}
+{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *}
Es wird alles kombiniert und in Ihre OpenAPI eingebunden und in der API-Dokumentation angezeigt:
@@ -237,7 +237,7 @@ Mit dieser Technik können Sie einige vordefinierte Responses in Ihren *Pfadoper
Zum Beispiel:
-{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
+{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *}
## Weitere Informationen zu OpenAPI-Responses { #more-information-about-openapi-responses }
diff --git a/docs/de/docs/advanced/advanced-dependencies.md b/docs/de/docs/advanced/advanced-dependencies.md
index a153665cc..e60df2883 100644
--- a/docs/de/docs/advanced/advanced-dependencies.md
+++ b/docs/de/docs/advanced/advanced-dependencies.md
@@ -63,3 +63,101 @@ In den Kapiteln zum Thema Sicherheit gibt es Hilfsfunktionen, die auf die gleich
Wenn Sie das hier alles verstanden haben, wissen Sie bereits, wie diese Sicherheits-Hilfswerkzeuge unter der Haube funktionieren.
///
+
+## Abhängigkeiten mit `yield`, `HTTPException`, `except` und Hintergrundtasks { #dependencies-with-yield-httpexception-except-and-background-tasks }
+
+/// warning | Achtung
+
+Sie benötigen diese technischen Details höchstwahrscheinlich nicht.
+
+Diese Details sind hauptsächlich nützlich, wenn Sie eine FastAPI-Anwendung haben, die älter als 0.121.0 ist, und Sie auf Probleme mit Abhängigkeiten mit `yield` stoßen.
+
+///
+
+Abhängigkeiten mit `yield` haben sich im Laufe der Zeit weiterentwickelt, um verschiedene Anwendungsfälle abzudecken und einige Probleme zu beheben, hier ist eine Zusammenfassung der Änderungen.
+
+### Abhängigkeiten mit `yield` und `scope` { #dependencies-with-yield-and-scope }
+
+In Version 0.121.0 hat FastAPI Unterstützung für `Depends(scope="function")` für Abhängigkeiten mit `yield` hinzugefügt.
+
+Mit `Depends(scope="function")` wird der Exit-Code nach `yield` direkt nach dem Ende der *Pfadoperation-Funktion* ausgeführt, bevor die Response an den Client gesendet wird.
+
+Und bei Verwendung von `Depends(scope="request")` (dem Default) wird der Exit-Code nach `yield` ausgeführt, nachdem die Response gesendet wurde.
+
+Mehr dazu finden Sie in der Dokumentation zu [Abhängigkeiten mit `yield` – Frühes Beenden und `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope).
+
+### Abhängigkeiten mit `yield` und `StreamingResponse`, Technische Details { #dependencies-with-yield-and-streamingresponse-technical-details }
+
+Vor FastAPI 0.118.0 wurde bei Verwendung einer Abhängigkeit mit `yield` der Exit-Code nach der *Pfadoperation-Funktion* ausgeführt, aber unmittelbar bevor die Response gesendet wurde.
+
+Die Absicht war, Ressourcen nicht länger als nötig zu halten, während darauf gewartet wird, dass die Response durchs Netzwerk reist.
+
+Diese Änderung bedeutete auch, dass bei Rückgabe einer `StreamingResponse` der Exit-Code der Abhängigkeit mit `yield` bereits ausgeführt worden wäre.
+
+Wenn Sie beispielsweise eine Datenbanksession in einer Abhängigkeit mit `yield` hatten, konnte die `StreamingResponse` diese Session während des Streamens von Daten nicht verwenden, weil die Session im Exit-Code nach `yield` bereits geschlossen worden wäre.
+
+Dieses Verhalten wurde in 0.118.0 zurückgenommen, sodass der Exit-Code nach `yield` ausgeführt wird, nachdem die Response gesendet wurde.
+
+/// info | Info
+
+Wie Sie unten sehen werden, ähnelt dies sehr dem Verhalten vor Version 0.106.0, jedoch mit mehreren Verbesserungen und Bugfixes für Sonderfälle.
+
+///
+
+#### Anwendungsfälle mit frühem Exit-Code { #use-cases-with-early-exit-code }
+
+Es gibt einige Anwendungsfälle mit spezifischen Bedingungen, die vom alten Verhalten profitieren könnten, den Exit-Code von Abhängigkeiten mit `yield` vor dem Senden der Response auszuführen.
+
+Stellen Sie sich zum Beispiel vor, Sie haben Code, der in einer Abhängigkeit mit `yield` eine Datenbanksession verwendet, nur um einen Benutzer zu verifizieren, die Datenbanksession wird aber in der *Pfadoperation-Funktion* nie wieder verwendet, sondern nur in der Abhängigkeit, und die Response benötigt lange, um gesendet zu werden, wie eine `StreamingResponse`, die Daten langsam sendet, aus irgendeinem Grund aber die Datenbank nicht verwendet.
+
+In diesem Fall würde die Datenbanksession gehalten, bis das Senden der Response abgeschlossen ist, aber wenn Sie sie nicht verwenden, wäre es nicht notwendig, sie zu halten.
+
+So könnte es aussehen:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py *}
+
+Der Exit-Code, das automatische Schließen der `Session` in:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
+
+... würde ausgeführt, nachdem die Response das langsame Senden der Daten beendet:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
+
+Da `generate_stream()` die Datenbanksession jedoch nicht verwendet, ist es nicht wirklich notwendig, die Session während des Sendens der Response offen zu halten.
+
+Wenn Sie diesen spezifischen Anwendungsfall mit SQLModel (oder SQLAlchemy) haben, könnten Sie die Session explizit schließen, nachdem Sie sie nicht mehr benötigen:
+
+{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *}
+
+Auf diese Weise würde die Session die Datenbankverbindung freigeben, sodass andere Requests sie verwenden könnten.
+
+Wenn Sie einen anderen Anwendungsfall haben, der ein frühes Beenden aus einer Abhängigkeit mit `yield` benötigt, erstellen Sie bitte eine GitHub-Diskussion-Frage mit Ihrem spezifischen Anwendungsfall und warum Sie von einem frühen Schließen für Abhängigkeiten mit `yield` profitieren würden.
+
+Wenn es überzeugende Anwendungsfälle für ein frühes Schließen bei Abhängigkeiten mit `yield` gibt, würde ich erwägen, eine neue Möglichkeit hinzuzufügen, um ein frühes Schließen optional zu aktivieren.
+
+### Abhängigkeiten mit `yield` und `except`, Technische Details { #dependencies-with-yield-and-except-technical-details }
+
+Vor FastAPI 0.110.0 war es so, dass wenn Sie eine Abhängigkeit mit `yield` verwendet und dann in dieser Abhängigkeit mit `except` eine Exception abgefangen haben und die Exception nicht erneut geworfen haben, die Exception automatisch an beliebige Exceptionhandler oder den Handler für interne Serverfehler weitergereicht/weitergeworfen wurde.
+
+Dies wurde in Version 0.110.0 geändert, um unbehandelten Speicherverbrauch durch weitergeleitete Exceptions ohne Handler (interne Serverfehler) zu beheben und um es mit dem Verhalten von normalem Python-Code konsistent zu machen.
+
+### Hintergrundtasks und Abhängigkeiten mit `yield`, Technische Details { #background-tasks-and-dependencies-with-yield-technical-details }
+
+Vor FastAPI 0.106.0 war das Werfen von Exceptions nach `yield` nicht möglich, der Exit-Code in Abhängigkeiten mit `yield` wurde ausgeführt, nachdem die Response gesendet wurde, sodass [Exceptionhandler](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} bereits ausgeführt worden wären.
+
+Dies war so designt, hauptsächlich um die Verwendung derselben von Abhängigkeiten „geyieldeten“ Objekte in Hintergrundtasks zu ermöglichen, da der Exit-Code erst ausgeführt wurde, nachdem die Hintergrundtasks abgeschlossen waren.
+
+Dies wurde in FastAPI 0.106.0 geändert mit der Absicht, keine Ressourcen zu halten, während darauf gewartet wird, dass die Response durchs Netzwerk reist.
+
+/// tip | Tipp
+
+Zusätzlich ist ein Hintergrundtask normalerweise ein unabhängiger Logikblock, der separat gehandhabt werden sollte, mit eigenen Ressourcen (z. B. eigener Datenbankverbindung).
+
+So haben Sie wahrscheinlich saubereren Code.
+
+///
+
+Wenn Sie sich bisher auf dieses Verhalten verlassen haben, sollten Sie jetzt die Ressourcen für Hintergrundtasks innerhalb des Hintergrundtasks selbst erstellen und intern nur Daten verwenden, die nicht von den Ressourcen von Abhängigkeiten mit `yield` abhängen.
+
+Anstatt beispielsweise dieselbe Datenbanksession zu verwenden, würden Sie innerhalb des Hintergrundtasks eine neue Datenbanksession erstellen und die Objekte aus der Datenbank mithilfe dieser neuen Session beziehen. Und anstatt das Objekt aus der Datenbank als Parameter an die Hintergrundtask-Funktion zu übergeben, würden Sie die ID dieses Objekts übergeben und das Objekt dann innerhalb der Hintergrundtask-Funktion erneut beziehen.
diff --git a/docs/de/docs/advanced/async-tests.md b/docs/de/docs/advanced/async-tests.md
index ad8245205..7206f136f 100644
--- a/docs/de/docs/advanced/async-tests.md
+++ b/docs/de/docs/advanced/async-tests.md
@@ -32,11 +32,11 @@ Betrachten wir als einfaches Beispiel eine Dateistruktur ähnlich der in [Größ
Die Datei `main.py` hätte als Inhalt:
-{* ../../docs_src/async_tests/main.py *}
+{* ../../docs_src/async_tests/app_a_py39/main.py *}
Die Datei `test_main.py` hätte die Tests für `main.py`, das könnte jetzt so aussehen:
-{* ../../docs_src/async_tests/test_main.py *}
+{* ../../docs_src/async_tests/app_a_py39/test_main.py *}
## Es ausführen { #run-it }
@@ -56,7 +56,7 @@ $ pytest
Der Marker `@pytest.mark.anyio` teilt pytest mit, dass diese Testfunktion asynchron aufgerufen werden soll:
-{* ../../docs_src/async_tests/test_main.py hl[7] *}
+{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *}
/// tip | Tipp
@@ -66,7 +66,7 @@ Beachten Sie, dass die Testfunktion jetzt `async def` ist und nicht nur `def` wi
Dann können wir einen `AsyncClient` mit der App erstellen und mit `await` asynchrone Requests an ihn senden.
-{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
+{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *}
Das ist das Äquivalent zu:
diff --git a/docs/de/docs/advanced/behind-a-proxy.md b/docs/de/docs/advanced/behind-a-proxy.md
index 036916cbe..1c7459050 100644
--- a/docs/de/docs/advanced/behind-a-proxy.md
+++ b/docs/de/docs/advanced/behind-a-proxy.md
@@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*"
Angenommen, Sie definieren eine *Pfadoperation* `/items/`:
-{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *}
+{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *}
Wenn der Client versucht, zu `/items` zu gehen, würde er standardmäßig zu `/items/` umgeleitet.
@@ -64,7 +64,7 @@ Wenn Sie mehr über HTTPS erfahren möchten, lesen Sie den Leitfaden [Über HTTP
///
-### Wie Proxy-Forwarded-Header funktionieren
+### Wie Proxy-Forwarded-Header funktionieren { #how-proxy-forwarded-headers-work }
Hier ist eine visuelle Darstellung, wie der **Proxy** weitergeleitete Header zwischen dem Client und dem **Anwendungsserver** hinzufügt:
@@ -115,7 +115,7 @@ In diesem Fall würde der ursprüngliche Pfad `/app` tatsächlich unter `/api/v1
Auch wenn Ihr gesamter Code unter der Annahme geschrieben ist, dass es nur `/app` gibt.
-{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *}
+{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *}
Und der Proxy würde das **Pfadpräfix** on-the-fly **„entfernen“**, bevor er den Request an den Anwendungsserver (wahrscheinlich Uvicorn via FastAPI CLI) übermittelt, dafür sorgend, dass Ihre Anwendung davon überzeugt ist, dass sie unter `/app` bereitgestellt wird, sodass Sie nicht Ihren gesamten Code dahingehend aktualisieren müssen, das Präfix `/api/v1` zu verwenden.
@@ -193,7 +193,7 @@ Sie können den aktuellen `root_path` abrufen, der von Ihrer Anwendung für jede
Hier fügen wir ihn, nur zu Demonstrationszwecken, in die Nachricht ein.
-{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *}
+{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *}
Wenn Sie Uvicorn dann starten mit:
@@ -220,7 +220,7 @@ wäre die http://127.0.0.1:8000/app gehen, sehen Sie die normale Response:
+Aber wenn Sie mit Ihrem Browser auf http://127.0.0.1:8000/app gehen, sehen Sie die normale Response:
```JSON
{
@@ -400,7 +400,7 @@ Wenn Sie eine benutzerdefinierte Liste von Servern (`servers`) übergeben und es
Zum Beispiel:
-{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *}
+{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *}
Erzeugt ein OpenAPI-Schema, wie:
@@ -443,11 +443,19 @@ Die Dokumentationsoberfläche interagiert mit dem von Ihnen ausgewählten Server
///
+/// note | Technische Details
+
+Die Eigenschaft `servers` in der OpenAPI-Spezifikation ist optional.
+
+Wenn Sie den Parameter `servers` nicht angeben und `root_path` den Wert `/` hat, wird die Eigenschaft `servers` im generierten OpenAPI-Schema standardmäßig vollständig weggelassen, was dem Äquivalent eines einzelnen Servers mit einem `url`-Wert von `/` entspricht.
+
+///
+
### Den automatischen Server von `root_path` deaktivieren { #disable-automatic-server-from-root-path }
Wenn Sie nicht möchten, dass **FastAPI** einen automatischen Server inkludiert, welcher `root_path` verwendet, können Sie den Parameter `root_path_in_servers=False` verwenden:
-{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *}
+{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *}
Dann wird er nicht in das OpenAPI-Schema aufgenommen.
diff --git a/docs/de/docs/advanced/custom-response.md b/docs/de/docs/advanced/custom-response.md
index 8714086e5..1226fcb20 100644
--- a/docs/de/docs/advanced/custom-response.md
+++ b/docs/de/docs/advanced/custom-response.md
@@ -30,7 +30,7 @@ Das liegt daran, dass FastAPI standardmäßig jedes enthaltene Element überprü
Wenn Sie jedoch sicher sind, dass der von Ihnen zurückgegebene Inhalt **mit JSON serialisierbar** ist, können Sie ihn direkt an die Response-Klasse übergeben und die zusätzliche Arbeit vermeiden, die FastAPI hätte, indem es Ihren zurückgegebenen Inhalt durch den `jsonable_encoder` leitet, bevor es ihn an die Response-Klasse übergibt.
-{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *}
+{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *}
/// info | Info
@@ -55,7 +55,7 @@ Um eine Response mit HTML direkt von **FastAPI** zurückzugeben, verwenden Sie `
* Importieren Sie `HTMLResponse`.
* Übergeben Sie `HTMLResponse` als den Parameter `response_class` Ihres *Pfadoperation-Dekorators*.
-{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *}
+{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *}
/// info | Info
@@ -73,7 +73,7 @@ Wie in [Eine Response direkt zurückgeben](response-directly.md){.internal-link
Das gleiche Beispiel von oben, das eine `HTMLResponse` zurückgibt, könnte so aussehen:
-{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *}
+{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *}
/// warning | Achtung
@@ -97,7 +97,7 @@ Die `response_class` wird dann nur zur Dokumentation der OpenAPI-*Pfadoperation*
Es könnte zum Beispiel so etwas sein:
-{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *}
+{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *}
In diesem Beispiel generiert die Funktion `generate_html_response()` bereits eine `Response` und gibt sie zurück, anstatt das HTML in einem `str` zurückzugeben.
@@ -136,7 +136,7 @@ Sie akzeptiert die folgenden Parameter:
FastAPI (eigentlich Starlette) fügt automatisch einen Content-Length-Header ein. Außerdem wird es einen Content-Type-Header einfügen, der auf dem media_type basiert, und für Texttypen einen Zeichensatz (charset) anfügen.
-{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
+{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
### `HTMLResponse` { #htmlresponse }
@@ -146,7 +146,7 @@ Nimmt Text oder Bytes entgegen und gibt eine HTML-Response zurück, wie Sie oben
Nimmt Text oder Bytes entgegen und gibt eine Plain-Text-Response zurück.
-{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *}
### `JSONResponse` { #jsonresponse }
@@ -180,7 +180,7 @@ Dazu muss `ujson` installiert werden, z. B. mit `pip install ujson`.
///
-{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
+{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *}
/// tip | Tipp
@@ -194,13 +194,13 @@ Gibt eine HTTP-Weiterleitung (HTTP-Redirect) zurück. Verwendet standardmäßig
Sie können eine `RedirectResponse` direkt zurückgeben:
-{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
+{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *}
---
Oder Sie können sie im Parameter `response_class` verwenden:
-{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *}
Wenn Sie das tun, können Sie die URL direkt von Ihrer *Pfadoperation*-Funktion zurückgeben.
@@ -210,13 +210,13 @@ In diesem Fall ist der verwendete `status_code` der Standardcode für die `Redir
Sie können den Parameter `status_code` auch in Kombination mit dem Parameter `response_class` verwenden:
-{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *}
+{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *}
### `StreamingResponse` { #streamingresponse }
Nimmt einen asynchronen Generator oder einen normalen Generator/Iterator und streamt den Responsebody.
-{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
+{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *}
#### Verwendung von `StreamingResponse` mit dateiartigen Objekten { #using-streamingresponse-with-file-like-objects }
@@ -226,7 +226,7 @@ Auf diese Weise müssen Sie nicht alles zuerst in den Arbeitsspeicher lesen und
Das umfasst viele Bibliotheken zur Interaktion mit Cloud-Speicher, Videoverarbeitung und anderen.
-{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *}
+{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *}
1. Das ist die Generatorfunktion. Es handelt sich um eine „Generatorfunktion“, da sie `yield`-Anweisungen enthält.
2. Durch die Verwendung eines `with`-Blocks stellen wir sicher, dass das dateiartige Objekt geschlossen wird, nachdem die Generatorfunktion fertig ist. Also, nachdem sie mit dem Senden der Response fertig ist.
@@ -255,11 +255,11 @@ Nimmt zur Instanziierung einen anderen Satz von Argumenten entgegen als die ande
Datei-Responses enthalten die entsprechenden `Content-Length`-, `Last-Modified`- und `ETag`-Header.
-{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
+{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *}
Sie können auch den Parameter `response_class` verwenden:
-{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *}
+{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *}
In diesem Fall können Sie den Dateipfad direkt von Ihrer *Pfadoperation*-Funktion zurückgeben.
@@ -273,7 +273,7 @@ Sie möchten etwa, dass Ihre Response eingerücktes und formatiertes JSON zurüc
Sie könnten eine `CustomORJSONResponse` erstellen. Das Wichtigste, was Sie tun müssen, ist, eine `Response.render(content)`-Methode zu erstellen, die den Inhalt als `bytes` zurückgibt:
-{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *}
+{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *}
Statt:
@@ -299,7 +299,7 @@ Der Parameter, der das definiert, ist `default_response_class`.
Im folgenden Beispiel verwendet **FastAPI** standardmäßig `ORJSONResponse` in allen *Pfadoperationen*, anstelle von `JSONResponse`.
-{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *}
+{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *}
/// tip | Tipp
diff --git a/docs/de/docs/advanced/dataclasses.md b/docs/de/docs/advanced/dataclasses.md
index 12ea8e9ec..52b9634ae 100644
--- a/docs/de/docs/advanced/dataclasses.md
+++ b/docs/de/docs/advanced/dataclasses.md
@@ -4,7 +4,7 @@ FastAPI basiert auf **Pydantic**, und ich habe Ihnen gezeigt, wie Sie Pydantic-M
Aber FastAPI unterstützt auf die gleiche Weise auch die Verwendung von `dataclasses`:
-{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *}
+{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
Das ist dank **Pydantic** ebenfalls möglich, da es `dataclasses` intern unterstützt.
@@ -32,7 +32,7 @@ Wenn Sie jedoch eine Menge Datenklassen herumliegen haben, ist dies ein guter Tr
Sie können `dataclasses` auch im Parameter `response_model` verwenden:
-{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
+{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
Die Datenklasse wird automatisch in eine Pydantic-Datenklasse konvertiert.
@@ -48,7 +48,7 @@ In einigen Fällen müssen Sie möglicherweise immer noch Pydantics Version von
In diesem Fall können Sie einfach die Standard-`dataclasses` durch `pydantic.dataclasses` ersetzen, was einen direkten Ersatz darstellt:
-{* ../../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. Wir importieren `field` weiterhin von Standard-`dataclasses`.
diff --git a/docs/de/docs/advanced/events.md b/docs/de/docs/advanced/events.md
index 2ceef1190..7e1191b55 100644
--- a/docs/de/docs/advanced/events.md
+++ b/docs/de/docs/advanced/events.md
@@ -30,7 +30,7 @@ Beginnen wir mit einem Beispiel und sehen es uns dann im Detail an.
Wir erstellen eine asynchrone Funktion `lifespan()` mit `yield` wie folgt:
-{* ../../docs_src/events/tutorial003.py hl[16,19] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *}
Hier simulieren wir den langsamen *Startup*, das Laden des Modells, indem wir die (Fake-)Modellfunktion vor dem `yield` in das Dictionary mit Modellen für maschinelles Lernen einfügen. Dieser Code wird ausgeführt, **bevor** die Anwendung **beginnt, Requests entgegenzunehmen**, während des *Startups*.
@@ -48,7 +48,7 @@ Möglicherweise müssen Sie eine neue Version starten, oder Sie haben es einfach
Das Erste, was auffällt, ist, dass wir eine asynchrone Funktion mit `yield` definieren. Das ist sehr ähnlich zu Abhängigkeiten mit `yield`.
-{* ../../docs_src/events/tutorial003.py hl[14:19] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *}
Der erste Teil der Funktion, vor dem `yield`, wird ausgeführt **bevor** die Anwendung startet.
@@ -60,7 +60,7 @@ Wie Sie sehen, ist die Funktion mit einem `@asynccontextmanager` versehen.
Dadurch wird die Funktion in einen sogenannten „**asynchronen Kontextmanager**“ umgewandelt.
-{* ../../docs_src/events/tutorial003.py hl[1,13] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *}
Ein **Kontextmanager** in Python ist etwas, das Sie in einer `with`-Anweisung verwenden können, zum Beispiel kann `open()` als Kontextmanager verwendet werden:
@@ -82,7 +82,7 @@ In unserem obigen Codebeispiel verwenden wir ihn nicht direkt, sondern übergebe
Der Parameter `lifespan` der `FastAPI`-App benötigt einen **asynchronen Kontextmanager**, wir können ihm also unseren neuen asynchronen Kontextmanager `lifespan` übergeben.
-{* ../../docs_src/events/tutorial003.py hl[22] *}
+{* ../../docs_src/events/tutorial003_py39.py hl[22] *}
## Alternative Events (deprecatet) { #alternative-events-deprecated }
@@ -104,7 +104,7 @@ Diese Funktionen können mit `async def` oder normalem `def` deklariert werden.
Um eine Funktion hinzuzufügen, die vor dem Start der Anwendung ausgeführt werden soll, deklarieren Sie diese mit dem Event `startup`:
-{* ../../docs_src/events/tutorial001.py hl[8] *}
+{* ../../docs_src/events/tutorial001_py39.py hl[8] *}
In diesem Fall initialisiert die Eventhandler-Funktion `startup` die „Datenbank“ der Items (nur ein `dict`) mit einigen Werten.
@@ -116,7 +116,7 @@ Und Ihre Anwendung empfängt erst dann Requests, wenn alle `startup`-Eventhandle
Um eine Funktion hinzuzufügen, die beim Shutdown der Anwendung ausgeführt werden soll, deklarieren Sie sie mit dem Event `shutdown`:
-{* ../../docs_src/events/tutorial002.py hl[6] *}
+{* ../../docs_src/events/tutorial002_py39.py hl[6] *}
Hier schreibt die `shutdown`-Eventhandler-Funktion eine Textzeile `"Application shutdown"` in eine Datei `log.txt`.
@@ -154,7 +154,7 @@ In der technischen ASGI-Spezifikation ist dies Teil des Starlettes Lifespan-Dokumentation.
+Weitere Informationen zu Starlettes `lifespan`-Handlern finden Sie in Starlettes Lifespan-Dokumentation.
Einschließlich, wie man Lifespan-Zustand handhabt, der in anderen Bereichen Ihres Codes verwendet werden kann.
diff --git a/docs/de/docs/advanced/generate-clients.md b/docs/de/docs/advanced/generate-clients.md
index d8836295b..659343f5b 100644
--- a/docs/de/docs/advanced/generate-clients.md
+++ b/docs/de/docs/advanced/generate-clients.md
@@ -167,7 +167,7 @@ Aber für den generierten Client könnten wir die OpenAPI-Operation-IDs direkt v
Wir könnten das OpenAPI-JSON in eine Datei `openapi.json` herunterladen und dann mit einem Skript wie dem folgenden **den präfixierten Tag entfernen**:
-{* ../../docs_src/generate_clients/tutorial004.py *}
+{* ../../docs_src/generate_clients/tutorial004_py39.py *}
//// tab | Node.js
diff --git a/docs/de/docs/advanced/middleware.md b/docs/de/docs/advanced/middleware.md
index 0a2a39699..ccc6a64c3 100644
--- a/docs/de/docs/advanced/middleware.md
+++ b/docs/de/docs/advanced/middleware.md
@@ -57,13 +57,13 @@ Erzwingt, dass alle eingehenden Uvicorns `ProxyHeadersMiddleware`
* MessagePack
-Um mehr über weitere verfügbare Middlewares herauszufinden, besuchen Sie Starlettes Middleware-Dokumentation und die ASGI Awesome List.
+Um mehr über weitere verfügbare Middlewares herauszufinden, besuchen Sie Starlettes Middleware-Dokumentation und die ASGI Awesome List.
diff --git a/docs/de/docs/advanced/openapi-callbacks.md b/docs/de/docs/advanced/openapi-callbacks.md
index afc48bbb8..fd68ab8dc 100644
--- a/docs/de/docs/advanced/openapi-callbacks.md
+++ b/docs/de/docs/advanced/openapi-callbacks.md
@@ -31,7 +31,7 @@ Sie verfügt über eine *Pfadoperation*, die einen `Invoice`-Body empfängt, und
Dieser Teil ist ziemlich normal, der größte Teil des Codes ist Ihnen wahrscheinlich bereits bekannt:
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *}
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
/// tip | Tipp
@@ -90,7 +90,7 @@ Wenn Sie diese Sichtweise (des *externen Entwicklers*) vorübergehend übernehme
Erstellen Sie zunächst einen neuen `APIRouter`, der einen oder mehrere Callbacks enthält.
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *}
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
### Die Callback-*Pfadoperation* erstellen { #create-the-callback-path-operation }
@@ -101,7 +101,7 @@ Sie sollte wie eine normale FastAPI-*Pfadoperation* aussehen:
* Sie sollte wahrscheinlich eine Deklaration des Bodys enthalten, die sie erhalten soll, z. B. `body: InvoiceEvent`.
* Und sie könnte auch eine Deklaration der Response enthalten, die zurückgegeben werden soll, z. B. `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] *}
Es gibt zwei Hauptunterschiede zu einer normalen *Pfadoperation*:
@@ -169,7 +169,7 @@ An diesem Punkt haben Sie die benötigte(n) *Callback-Pfadoperation(en)* (diejen
Verwenden Sie nun den Parameter `callbacks` im *Pfadoperation-Dekorator Ihrer API*, um das Attribut `.routes` (das ist eigentlich nur eine `list`e von Routen/*Pfadoperationen*) dieses Callback-Routers zu übergeben:
-{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *}
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
/// tip | Tipp
diff --git a/docs/de/docs/advanced/openapi-webhooks.md b/docs/de/docs/advanced/openapi-webhooks.md
index a09a675ed..ca9c63960 100644
--- a/docs/de/docs/advanced/openapi-webhooks.md
+++ b/docs/de/docs/advanced/openapi-webhooks.md
@@ -32,7 +32,7 @@ Webhooks sind in OpenAPI 3.1.0 und höher verfügbar und werden von FastAPI `0.9
Wenn Sie eine **FastAPI**-Anwendung erstellen, gibt es ein `webhooks`-Attribut, das Sie verwenden können, um *Webhooks* zu definieren, genauso wie Sie *Pfadoperationen* definieren würden, zum Beispiel mit `@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] *}
Die von Ihnen definierten Webhooks landen im **OpenAPI**-Schema und der automatischen **Dokumentations-Oberfläche**.
diff --git a/docs/de/docs/advanced/path-operation-advanced-configuration.md b/docs/de/docs/advanced/path-operation-advanced-configuration.md
index f5ec7c49e..c7ac1cf61 100644
--- a/docs/de/docs/advanced/path-operation-advanced-configuration.md
+++ b/docs/de/docs/advanced/path-operation-advanced-configuration.md
@@ -12,7 +12,7 @@ Mit dem Parameter `operation_id` können Sie die OpenAPI `operationId` festlegen
Sie müssten sicherstellen, dass sie für jede Operation eindeutig ist.
-{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
### Verwendung des Namens der *Pfadoperation-Funktion* als operationId { #using-the-path-operation-function-name-as-the-operationid }
@@ -20,7 +20,7 @@ Wenn Sie die Funktionsnamen Ihrer API als `operationId`s verwenden möchten, kö
Sie sollten dies tun, nachdem Sie alle Ihre *Pfadoperationen* hinzugefügt haben.
-{* ../../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 | Tipp
@@ -40,7 +40,7 @@ Auch wenn diese sich in unterschiedlichen Modulen (Python-Dateien) befinden.
Um eine *Pfadoperation* aus dem generierten OpenAPI-Schema (und damit aus den automatischen Dokumentationssystemen) auszuschließen, verwenden Sie den Parameter `include_in_schema` und setzen Sie ihn auf `False`:
-{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *}
## Fortgeschrittene Beschreibung mittels Docstring { #advanced-description-from-docstring }
@@ -48,9 +48,9 @@ Sie können die verwendeten Zeilen aus dem Docstring einer *Pfadoperation-Funkti
Das Hinzufügen eines `\f` (ein maskiertes „Form Feed“-Zeichen) führt dazu, dass **FastAPI** die für OpenAPI verwendete Ausgabe an dieser Stelle abschneidet.
-Sie wird nicht in der Dokumentation angezeigt, aber andere Tools (z. B. Sphinx) können den Rest verwenden.
+Sie wird nicht in der Dokumentation angezeigt, aber andere Tools (wie z. B. Sphinx) können den Rest verwenden.
-{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
## Zusätzliche Responses { #additional-responses }
@@ -92,7 +92,7 @@ Sie können das OpenAPI-Schema für eine *Pfadoperation* erweitern, indem Sie de
Dieses `openapi_extra` kann beispielsweise hilfreich sein, um [OpenAPI-Erweiterungen](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) zu deklarieren:
-{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *}
Wenn Sie die automatische API-Dokumentation öffnen, wird Ihre Erweiterung am Ende der spezifischen *Pfadoperation* angezeigt.
@@ -139,7 +139,7 @@ Sie könnten sich beispielsweise dafür entscheiden, den geparst, sondern direkt als `bytes` gelesen und die Funktion `magic_data_reader()` wäre dafür verantwortlich, ihn in irgendeiner Weise zu parsen.
@@ -153,47 +153,15 @@ Und Sie könnten dies auch tun, wenn der Datentyp im Request nicht JSON ist.
In der folgenden Anwendung verwenden wir beispielsweise weder die integrierte Funktionalität von FastAPI zum Extrahieren des JSON-Schemas aus Pydantic-Modellen noch die automatische Validierung für JSON. Tatsächlich deklarieren wir den Request-Content-Type als YAML und nicht als JSON:
-//// tab | Pydantic v2
-
-{* ../../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 | Info
-
-In Pydantic Version 1 hieß die Methode zum Abrufen des JSON-Schemas für ein Modell `Item.schema()`, in Pydantic Version 2 heißt die Methode `Item.model_json_schema()`.
-
-///
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
Obwohl wir nicht die standardmäßig integrierte Funktionalität verwenden, verwenden wir dennoch ein Pydantic-Modell, um das JSON-Schema für die Daten, die wir in YAML empfangen möchten, manuell zu generieren.
-Dann verwenden wir den Request direkt und extrahieren den Body als `bytes`. Das bedeutet, dass FastAPI nicht einmal versucht, den Request-Payload als JSON zu parsen.
+Dann verwenden wir den Request direkt und extrahieren den Body als `bytes`. Das bedeutet, dass FastAPI nicht einmal versucht, die Request-Payload als JSON zu parsen.
Und dann parsen wir in unserem Code diesen YAML-Inhalt direkt und verwenden dann wieder dasselbe Pydantic-Modell, um den YAML-Inhalt zu validieren:
-//// 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 | Info
-
-In Pydantic Version 1 war die Methode zum Parsen und Validieren eines Objekts `Item.parse_obj()`, in Pydantic Version 2 heißt die Methode `Item.model_validate()`.
-
-///
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
/// tip | Tipp
diff --git a/docs/de/docs/advanced/response-change-status-code.md b/docs/de/docs/advanced/response-change-status-code.md
index b079e241d..b209c2d67 100644
--- a/docs/de/docs/advanced/response-change-status-code.md
+++ b/docs/de/docs/advanced/response-change-status-code.md
@@ -20,7 +20,7 @@ Sie können einen Parameter vom Typ `Response` in Ihrer *Pfadoperation-Funktion*
Anschließend können Sie den `status_code` in diesem *vorübergehenden* Response-Objekt festlegen.
-{* ../../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] *}
Und dann können Sie jedes benötigte Objekt zurückgeben, wie Sie es normalerweise tun würden (ein `dict`, ein Datenbankmodell usw.).
diff --git a/docs/de/docs/advanced/response-cookies.md b/docs/de/docs/advanced/response-cookies.md
index 0dd4175dd..87e636cfa 100644
--- a/docs/de/docs/advanced/response-cookies.md
+++ b/docs/de/docs/advanced/response-cookies.md
@@ -6,7 +6,7 @@ Sie können einen Parameter vom Typ `Response` in Ihrer *Pfadoperation-Funktion*
Und dann können Sie Cookies in diesem *vorübergehenden* Response-Objekt setzen.
-{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *}
+{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *}
Anschließend können Sie wie gewohnt jedes gewünschte Objekt zurückgeben (ein `dict`, ein Datenbankmodell, usw.).
@@ -24,7 +24,7 @@ Dazu können Sie eine Response erstellen, wie unter [Eine Response direkt zurüc
Setzen Sie dann Cookies darin und geben Sie sie dann zurück:
-{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
+{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *}
/// tip | Tipp
@@ -48,4 +48,4 @@ Und da die `Response` häufig zum Setzen von Headern und Cookies verwendet wird,
///
-Um alle verfügbaren Parameter und Optionen anzuzeigen, sehen Sie sich deren Dokumentation in Starlette an.
+Um alle verfügbaren Parameter und Optionen anzuzeigen, sehen Sie sich deren Dokumentation in Starlette an.
diff --git a/docs/de/docs/advanced/response-directly.md b/docs/de/docs/advanced/response-directly.md
index d99517373..0a28a6d0e 100644
--- a/docs/de/docs/advanced/response-directly.md
+++ b/docs/de/docs/advanced/response-directly.md
@@ -34,7 +34,7 @@ Sie können beispielsweise kein Pydantic-Modell in eine `JSONResponse` einfügen
In diesen Fällen können Sie den `jsonable_encoder` verwenden, um Ihre Daten zu konvertieren, bevor Sie sie an eine Response übergeben:
-{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *}
+{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *}
/// note | Technische Details
@@ -54,7 +54,7 @@ Nehmen wir an, Sie möchten eine Response-Objekt festlegen.
-{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *}
+{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *}
Anschließend können Sie wie gewohnt jedes gewünschte Objekt zurückgeben (ein `dict`, ein Datenbankmodell, usw.).
@@ -22,7 +22,7 @@ Sie können auch Header hinzufügen, wenn Sie eine `Response` direkt zurückgebe
Erstellen Sie eine Response wie in [Eine Response direkt zurückgeben](response-directly.md){.internal-link target=_blank} beschrieben und übergeben Sie die Header als zusätzlichen Parameter:
-{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *}
+{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *}
/// note | Technische Details
@@ -38,4 +38,4 @@ Und da die `Response` häufig zum Setzen von Headern und Cookies verwendet wird,
Beachten Sie, dass benutzerdefinierte proprietäre Header mittels des Präfix `X-` hinzugefügt werden können.
-Wenn Sie jedoch benutzerdefinierte Header haben, die ein Client in einem Browser sehen können soll, müssen Sie diese zu Ihrer CORS-Konfiguration hinzufügen (weitere Informationen finden Sie unter [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), unter Verwendung des Parameters `expose_headers`, dokumentiert in Starlettes CORS-Dokumentation.
+Wenn Sie jedoch benutzerdefinierte Header haben, die ein Client in einem Browser sehen können soll, müssen Sie diese zu Ihrer CORS-Konfiguration hinzufügen (weitere Informationen finden Sie unter [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), unter Verwendung des Parameters `expose_headers`, dokumentiert in Starlettes CORS-Dokumentation.
diff --git a/docs/de/docs/advanced/settings.md b/docs/de/docs/advanced/settings.md
index ccd7f373d..ea4540e10 100644
--- a/docs/de/docs/advanced/settings.md
+++ b/docs/de/docs/advanced/settings.md
@@ -60,23 +60,7 @@ Auf die gleiche Weise wie bei Pydantic-Modellen deklarieren Sie Klassenattribute
Sie können dieselben Validierungs-Funktionen und -Tools verwenden, die Sie für Pydantic-Modelle verwenden, z. B. verschiedene Datentypen und zusätzliche Validierungen mit `Field()`.
-//// tab | Pydantic v2
-
-{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *}
-
-////
-
-//// tab | Pydantic v1
-
-/// info | Info
-
-In Pydantic v1 würden Sie `BaseSettings` direkt von `pydantic` statt von `pydantic_settings` importieren.
-
-///
-
-{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *}
-
-////
+{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *}
/// tip | Tipp
@@ -92,7 +76,7 @@ Als Nächstes werden die Daten konvertiert und validiert. Wenn Sie also dieses `
Dann können Sie das neue `settings`-Objekt in Ihrer Anwendung verwenden:
-{* ../../docs_src/settings/tutorial001.py hl[18:20] *}
+{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *}
### Den Server ausführen { #run-the-server }
@@ -126,11 +110,11 @@ Sie könnten diese Einstellungen in eine andere Moduldatei einfügen, wie Sie in
Sie könnten beispielsweise eine Datei `config.py` haben mit:
-{* ../../docs_src/settings/app01/config.py *}
+{* ../../docs_src/settings/app01_py39/config.py *}
Und dann verwenden Sie diese in einer Datei `main.py`:
-{* ../../docs_src/settings/app01/main.py hl[3,11:13] *}
+{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *}
/// tip | Tipp
@@ -148,7 +132,7 @@ Dies könnte besonders beim Testen nützlich sein, da es sehr einfach ist, eine
Ausgehend vom vorherigen Beispiel könnte Ihre Datei `config.py` so aussehen:
-{* ../../docs_src/settings/app02/config.py hl[10] *}
+{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *}
Beachten Sie, dass wir jetzt keine Standardinstanz `settings = Settings()` erstellen.
@@ -174,7 +158,7 @@ Und dann können wir das von der *Pfadoperation-Funktion* als Abhängigkeit einf
Dann wäre es sehr einfach, beim Testen ein anderes Einstellungsobjekt bereitzustellen, indem man eine Abhängigkeitsüberschreibung für `get_settings` erstellt:
-{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *}
+{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *}
Bei der Abhängigkeitsüberschreibung legen wir einen neuen Wert für `admin_email` fest, wenn wir das neue `Settings`-Objekt erstellen, und geben dann dieses neue Objekt zurück.
@@ -215,9 +199,7 @@ APP_NAME="ChimichangApp"
Und dann aktualisieren Sie Ihre `config.py` mit:
-//// tab | Pydantic v2
-
-{* ../../docs_src/settings/app03_an/config.py hl[9] *}
+{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *}
/// tip | Tipp
@@ -225,26 +207,6 @@ Das Attribut `model_config` wird nur für die Pydantic-Konfiguration verwendet.
///
-////
-
-//// tab | Pydantic v1
-
-{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *}
-
-/// tip | Tipp
-
-Die Klasse `Config` wird nur für die Pydantic-Konfiguration verwendet. Weitere Informationen finden Sie unter Pydantic Model Config.
-
-///
-
-////
-
-/// info | Info
-
-In Pydantic Version 1 erfolgte die Konfiguration in einer internen Klasse `Config`, in Pydantic Version 2 erfolgt sie in einem Attribut `model_config`. Dieses Attribut akzeptiert ein `dict`. Um automatische Codevervollständigung und Inline-Fehlerberichte zu erhalten, können Sie `SettingsConfigDict` importieren und verwenden, um dieses `dict` zu definieren.
-
-///
-
Hier definieren wir die Konfiguration `env_file` innerhalb Ihrer Pydantic-`Settings`-Klasse und setzen den Wert auf den Dateinamen mit der dotenv-Datei, die wir verwenden möchten.
### Die `Settings` nur einmal laden mittels `lru_cache` { #creating-the-settings-only-once-with-lru-cache }
diff --git a/docs/de/docs/advanced/sub-applications.md b/docs/de/docs/advanced/sub-applications.md
index d634aac23..081574d0a 100644
--- a/docs/de/docs/advanced/sub-applications.md
+++ b/docs/de/docs/advanced/sub-applications.md
@@ -10,7 +10,7 @@ Wenn Sie zwei unabhängige FastAPI-Anwendungen mit deren eigenen unabhängigen O
Erstellen Sie zunächst die Hauptanwendung **FastAPI** und deren *Pfadoperationen*:
-{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *}
+{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *}
### Unteranwendung { #sub-application }
@@ -18,7 +18,7 @@ Erstellen Sie dann Ihre Unteranwendung und deren *Pfadoperationen*.
Diese Unteranwendung ist nur eine weitere Standard-FastAPI-Anwendung, aber diese wird „gemountet“:
-{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *}
+{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *}
### Die Unteranwendung mounten { #mount-the-sub-application }
@@ -26,7 +26,7 @@ Mounten Sie in Ihrer Top-Level-Anwendung `app` die Unteranwendung `subapi`.
In diesem Fall wird sie im Pfad `/subapi` gemountet:
-{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *}
+{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *}
### Die automatische API-Dokumentation testen { #check-the-automatic-api-docs }
diff --git a/docs/de/docs/advanced/templates.md b/docs/de/docs/advanced/templates.md
index fdaeb3413..97a45e612 100644
--- a/docs/de/docs/advanced/templates.md
+++ b/docs/de/docs/advanced/templates.md
@@ -27,7 +27,7 @@ $ pip install jinja2
* Deklarieren Sie einen `Request`-Parameter in der *Pfadoperation*, welcher ein Template zurückgibt.
* Verwenden Sie die von Ihnen erstellten `templates`, um eine `TemplateResponse` zu rendern und zurückzugeben, übergeben Sie den Namen des Templates, das Requestobjekt und ein „Kontext“-Dictionary mit Schlüssel-Wert-Paaren, die innerhalb des Jinja2-Templates verwendet werden sollen.
-{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *}
+{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *}
/// note | Hinweis
@@ -123,4 +123,4 @@ Und da Sie `StaticFiles` verwenden, wird diese CSS-Datei automatisch von Ihrer *
## Mehr Details { #more-details }
-Weitere Informationen, einschließlich, wie man Templates testet, finden Sie in Starlettes Dokumentation zu Templates.
+Weitere Informationen, einschließlich, wie man Templates testet, finden Sie in Starlettes Dokumentation zu Templates.
diff --git a/docs/de/docs/advanced/testing-events.md b/docs/de/docs/advanced/testing-events.md
index 1a68b7714..5b12f3f18 100644
--- a/docs/de/docs/advanced/testing-events.md
+++ b/docs/de/docs/advanced/testing-events.md
@@ -2,11 +2,11 @@
Wenn Sie `lifespan` in Ihren Tests ausführen müssen, können Sie den `TestClient` mit einer `with`-Anweisung verwenden:
-{* ../../docs_src/app_testing/tutorial004.py hl[9:15,18,27:28,30:32,41:43] *}
+{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *}
-Sie können mehr Details unter [„Lifespan in Tests ausführen in der offiziellen Starlette-Dokumentation.“](https://www.starlette.io/lifespan/#running-lifespan-in-tests) nachlesen.
+Sie können mehr Details unter [„Lifespan in Tests ausführen in der offiziellen Starlette-Dokumentation.“](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) nachlesen.
Für die deprecateten Events `startup` und `shutdown` können Sie den `TestClient` wie folgt verwenden:
-{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *}
+{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *}
diff --git a/docs/de/docs/advanced/testing-websockets.md b/docs/de/docs/advanced/testing-websockets.md
index a71310cbf..9ecca7a4f 100644
--- a/docs/de/docs/advanced/testing-websockets.md
+++ b/docs/de/docs/advanced/testing-websockets.md
@@ -4,10 +4,10 @@ Sie können den schon bekannten `TestClient` zum Testen von WebSockets verwenden
Dazu verwenden Sie den `TestClient` in einer `with`-Anweisung, eine Verbindung zum WebSocket herstellend:
-{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *}
+{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *}
/// note | Hinweis
-Weitere Informationen finden Sie in Starlettes Dokumentation zum Testen von WebSockets.
+Weitere Informationen finden Sie in Starlettes Dokumentation zum Testen von WebSockets.
///
diff --git a/docs/de/docs/advanced/using-request-directly.md b/docs/de/docs/advanced/using-request-directly.md
index 7782237ec..36d73b806 100644
--- a/docs/de/docs/advanced/using-request-directly.md
+++ b/docs/de/docs/advanced/using-request-directly.md
@@ -15,7 +15,7 @@ Es gibt jedoch Situationen, in denen Sie möglicherweise direkt auf das `Request
## Details zum `Request`-Objekt { #details-about-the-request-object }
-Da **FastAPI** unter der Haube eigentlich **Starlette** ist, mit einer Ebene von mehreren Tools darüber, können Sie Starlettes `Request`-Objekt direkt verwenden, wenn Sie es benötigen.
+Da **FastAPI** unter der Haube eigentlich **Starlette** ist, mit einer Ebene von mehreren Tools darüber, können Sie Starlettes `Request`-Objekt direkt verwenden, wenn Sie es benötigen.
Das bedeutet allerdings auch, dass, wenn Sie Daten direkt vom `Request`-Objekt nehmen (z. B. dessen Body lesen), diese von FastAPI nicht validiert, konvertiert oder dokumentiert werden (mit OpenAPI, für die automatische API-Benutzeroberfläche).
@@ -29,7 +29,7 @@ Angenommen, Sie möchten auf die IP-Adresse/den Host des Clients in Ihrer *Pfado
Dazu müssen Sie direkt auf den Request zugreifen.
-{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *}
+{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *}
Durch die Deklaration eines *Pfadoperation-Funktionsparameters*, dessen Typ der `Request` ist, weiß **FastAPI**, dass es den `Request` diesem Parameter übergeben soll.
@@ -45,7 +45,7 @@ Auf die gleiche Weise können Sie wie gewohnt jeden anderen Parameter deklariere
## `Request`-Dokumentation { #request-documentation }
-Weitere Details zum `Request`-Objekt finden Sie in der offiziellen Starlette-Dokumentation.
+Weitere Details zum `Request`-Objekt finden Sie in der offiziellen Starlette-Dokumentation.
/// note | Technische Details
diff --git a/docs/de/docs/advanced/websockets.md b/docs/de/docs/advanced/websockets.md
index ad1f6f5b1..05ae5a4b3 100644
--- a/docs/de/docs/advanced/websockets.md
+++ b/docs/de/docs/advanced/websockets.md
@@ -38,13 +38,13 @@ In der Produktion hätten Sie eine der oben genannten Optionen.
Aber es ist der einfachste Weg, sich auf die Serverseite von WebSockets zu konzentrieren und ein funktionierendes Beispiel zu haben:
-{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *}
+{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *}
## Einen `websocket` erstellen { #create-a-websocket }
Erstellen Sie in Ihrer **FastAPI**-Anwendung einen `websocket`:
-{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *}
+{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *}
/// note | Technische Details
@@ -58,7 +58,7 @@ Sie könnten auch `from starlette.websockets import WebSocket` verwenden.
In Ihrer WebSocket-Route können Sie Nachrichten `await`en und Nachrichten senden.
-{* ../../docs_src/websockets/tutorial001.py hl[48:52] *}
+{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *}
Sie können Binär-, Text- und JSON-Daten empfangen und senden.
@@ -182,5 +182,5 @@ Wenn Sie etwas benötigen, das sich leicht in FastAPI integrieren lässt, aber r
Weitere Informationen zu Optionen finden Sie in der Dokumentation von Starlette:
-* Die `WebSocket`-Klasse.
-* Klassen-basierte Handhabung von WebSockets.
+* Die `WebSocket`-Klasse.
+* Klassen-basierte Handhabung von WebSockets.
diff --git a/docs/de/docs/advanced/wsgi.md b/docs/de/docs/advanced/wsgi.md
index 1de9739dd..3cd776a6a 100644
--- a/docs/de/docs/advanced/wsgi.md
+++ b/docs/de/docs/advanced/wsgi.md
@@ -12,7 +12,7 @@ Wrappen Sie dann die WSGI-Anwendung (z. B. Flask) mit der Middleware.
Und dann mounten Sie das auf einem Pfad.
-{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *}
+{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *}
## Es testen { #check-it }
diff --git a/docs/de/docs/alternatives.md b/docs/de/docs/alternatives.md
index 15c0719fb..4dd127dba 100644
--- a/docs/de/docs/alternatives.md
+++ b/docs/de/docs/alternatives.md
@@ -417,7 +417,7 @@ Die gesamte Datenvalidierung, Datenserialisierung und automatische Modelldokumen
///
-### Starlette { #starlette }
+### Starlette { #starlette }
Starlette ist ein leichtgewichtiges ASGI-Framework/Toolkit, welches sich ideal für die Erstellung hochperformanter asynchroner Dienste eignet.
@@ -462,7 +462,7 @@ Alles, was Sie also mit Starlette machen können, können Sie direkt mit **FastA
///
-### Uvicorn { #uvicorn }
+### Uvicorn { #uvicorn }
Uvicorn ist ein blitzschneller ASGI-Server, der auf uvloop und httptools basiert.
diff --git a/docs/de/docs/deployment/cloud.md b/docs/de/docs/deployment/cloud.md
index ca1ba3b3b..ad3ff76db 100644
--- a/docs/de/docs/deployment/cloud.md
+++ b/docs/de/docs/deployment/cloud.md
@@ -1,16 +1,24 @@
-# FastAPI bei Cloudanbietern bereitstellen { #deploy-fastapi-on-cloud-providers }
+# FastAPI bei Cloudanbietern deployen { #deploy-fastapi-on-cloud-providers }
Sie können praktisch **jeden Cloudanbieter** verwenden, um Ihre FastAPI-Anwendung bereitzustellen.
-In den meisten Fällen bieten die großen Cloudanbieter Anleitungen zum Bereitstellen von FastAPI an.
+In den meisten Fällen bieten die großen Cloudanbieter Anleitungen zum Deployment von FastAPI an.
-## Cloudanbieter – Sponsoren { #cloud-providers-sponsors }
+## FastAPI Cloud { #fastapi-cloud }
+
+**FastAPI Cloud** wurde vom selben Autor und Team hinter **FastAPI** entwickelt.
+
+Es vereinfacht den Prozess des **Erstellens**, **Deployens** und **Zugreifens** auf eine API mit minimalem Aufwand.
-Einige Cloudanbieter ✨ [**sponsern FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, dies stellt die kontinuierliche und gesunde **Entwicklung** von FastAPI und seinem **Ökosystem** sicher.
+Es bringt die gleiche **Developer-Experience** beim Erstellen von Apps mit FastAPI auch zum **Deployment** in der Cloud. 🎉
+
+FastAPI Cloud ist der Hauptsponsor und Finanzierungsgeber für die *FastAPI and friends* Open-Source-Projekte. ✨
+
+## Cloudanbieter – Sponsoren { #cloud-providers-sponsors }
-Und es zeigt ihr wahres Engagement für FastAPI und seine **Community** (Sie), da sie Ihnen nicht nur einen **guten Service** bieten möchten, sondern auch sicherstellen möchten, dass Sie ein **gutes und gesundes Framework**, FastAPI, haben. 🙇
+Einige andere Cloudanbieter ✨ [**sponsern FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ ebenfalls. 🙇
-Vielleicht möchten Sie deren Dienste ausprobieren und deren Anleitungen folgen:
+Sie könnten diese ebenfalls in Betracht ziehen, deren Anleitungen folgen und ihre Dienste ausprobieren:
* Render
* Railway
diff --git a/docs/de/docs/deployment/concepts.md b/docs/de/docs/deployment/concepts.md
index ef0f458a7..dde922805 100644
--- a/docs/de/docs/deployment/concepts.md
+++ b/docs/de/docs/deployment/concepts.md
@@ -1,6 +1,6 @@
# Deployment-Konzepte { #deployments-concepts }
-Bei dem Deployment – der Bereitstellung – einer **FastAPI**-Anwendung, oder eigentlich jeder Art von Web-API, gibt es mehrere Konzepte, die Sie wahrscheinlich interessieren, und mithilfe der Sie die **am besten geeignete** Methode zur **Bereitstellung Ihrer Anwendung** finden können.
+Bei dem Deployment – der Bereitstellung – einer **FastAPI**-Anwendung, oder eigentlich jeder Art von Web-API, gibt es mehrere Konzepte, die Sie wahrscheinlich interessieren, und mithilfe der Sie die **am besten geeignete** Methode zum **Deployment Ihrer Anwendung** finden können.
Einige wichtige Konzepte sind:
@@ -15,11 +15,11 @@ Wir werden sehen, wie diese sich auf das **Deployment** auswirken.
Letztendlich besteht das ultimative Ziel darin, **Ihre API-Clients** auf **sichere** Weise zu versorgen, um **Unterbrechungen** zu vermeiden und die **Rechenressourcen** (z. B. entfernte Server/virtuelle Maschinen) so effizient wie möglich zu nutzen. 🚀
-Ich erzähle Ihnen hier etwas mehr über diese **Konzepte**, was Ihnen hoffentlich die **Intuition** gibt, die Sie benötigen, um zu entscheiden, wie Sie Ihre API in sehr unterschiedlichen Umgebungen bereitstellen, möglicherweise sogar in **zukünftigen**, die jetzt noch nicht existieren.
+Ich erzähle Ihnen hier etwas mehr über diese **Konzepte**, was Ihnen hoffentlich die **Intuition** gibt, die Sie benötigen, um zu entscheiden, wie Sie Ihre API in sehr unterschiedlichen Umgebungen deployen, möglicherweise sogar in **zukünftigen**, die jetzt noch nicht existieren.
-Durch die Berücksichtigung dieser Konzepte können Sie die beste Variante der Bereitstellung **Ihrer eigenen APIs** **evaluieren und konzipieren**.
+Durch die Berücksichtigung dieser Konzepte können Sie die beste Variante des Deployments **Ihrer eigenen APIs** **evaluieren und konzipieren**.
-In den nächsten Kapiteln werde ich Ihnen mehr **konkrete Rezepte** für die Bereitstellung von FastAPI-Anwendungen geben.
+In den nächsten Kapiteln werde ich Ihnen mehr **konkrete Rezepte** für das Deployment von FastAPI-Anwendungen geben.
Aber schauen wir uns zunächst einmal diese grundlegenden **konzeptionellen Ideen** an. Diese Konzepte gelten auch für jede andere Art von Web-API. 💡
@@ -271,7 +271,7 @@ In diesem Fall müssen Sie sich darüber keine Sorgen machen. 🤷
### Beispiele für Strategien für Vorab-Schritte { #examples-of-previous-steps-strategies }
-Es hängt **stark** davon ab, wie Sie **Ihr System bereitstellen**, und hängt wahrscheinlich mit der Art und Weise zusammen, wie Sie Programme starten, Neustarts durchführen, usw.
+Es hängt **stark** davon ab, wie Sie **Ihr System deployen**, und hängt wahrscheinlich mit der Art und Weise zusammen, wie Sie Programme starten, Neustarts durchführen, usw.
Hier sind einige mögliche Ideen:
@@ -307,7 +307,7 @@ Sie können einfache Tools wie `htop` verwenden, um die in Ihrem Server verwende
## Zusammenfassung { #recap }
-Sie haben hier einige der wichtigsten Konzepte gelesen, die Sie wahrscheinlich berücksichtigen müssen, wenn Sie entscheiden, wie Sie Ihre Anwendung bereitstellen:
+Sie haben hier einige der wichtigsten Konzepte gelesen, die Sie wahrscheinlich berücksichtigen müssen, wenn Sie entscheiden, wie Sie Ihre Anwendung deployen:
* Sicherheit – HTTPS
* Beim Hochfahren ausführen
diff --git a/docs/de/docs/deployment/docker.md b/docs/de/docs/deployment/docker.md
index 52ac99913..d4b74635d 100644
--- a/docs/de/docs/deployment/docker.md
+++ b/docs/de/docs/deployment/docker.md
@@ -1,6 +1,6 @@
# FastAPI in Containern – Docker { #fastapi-in-containers-docker }
-Beim Deployment von FastAPI-Anwendungen besteht ein gängiger Ansatz darin, ein **Linux-Containerimage** zu erstellen. Normalerweise erfolgt dies mit **Docker**. Sie können dieses Containerimage dann auf eine von mehreren möglichen Arten bereitstellen.
+Beim Deployment von FastAPI-Anwendungen besteht ein gängiger Ansatz darin, ein **Linux-Containerimage** zu erstellen. Normalerweise erfolgt dies mit **Docker**. Sie können dieses Containerimage dann auf eine von mehreren möglichen Arten deployen.
Die Verwendung von Linux-Containern bietet mehrere Vorteile, darunter **Sicherheit**, **Replizierbarkeit**, **Einfachheit** und andere.
@@ -40,7 +40,7 @@ Linux-Container werden mit demselben Linux-Kernel des Hosts (Maschine, virtuelle
Auf diese Weise verbrauchen Container **wenig Ressourcen**, eine Menge vergleichbar mit der direkten Ausführung der Prozesse (eine virtuelle Maschine würde viel mehr verbrauchen).
-Container verfügen außerdem über ihre eigenen **isoliert** laufenden Prozesse (üblicherweise nur einen Prozess), über ihr eigenes Dateisystem und ihr eigenes Netzwerk, was die Bereitstellung, Sicherheit, Entwicklung usw. vereinfacht.
+Container verfügen außerdem über ihre eigenen **isoliert** laufenden Prozesse (üblicherweise nur einen Prozess), über ihr eigenes Dateisystem und ihr eigenes Netzwerk, was Deployment, Sicherheit, Entwicklung usw. vereinfacht.
## Was ist ein Containerimage { #what-is-a-container-image }
@@ -598,7 +598,7 @@ Zum Beispiel:
* Mit einem **Kubernetes**-Cluster
* Mit einem Docker Swarm Mode-Cluster
* Mit einem anderen Tool wie Nomad
-* Mit einem Cloud-Dienst, der Ihr Containerimage nimmt und es bereitstellt
+* Mit einem Cloud-Dienst, der Ihr Containerimage nimmt und es deployt
## Docker-Image mit `uv` { #docker-image-with-uv }
diff --git a/docs/de/docs/deployment/fastapicloud.md b/docs/de/docs/deployment/fastapicloud.md
new file mode 100644
index 000000000..18c3bb8a4
--- /dev/null
+++ b/docs/de/docs/deployment/fastapicloud.md
@@ -0,0 +1,65 @@
+# FastAPI Cloud { #fastapi-cloud }
+
+Sie können Ihre FastAPI-App in der FastAPI Cloud mit **einem einzigen Befehl** deployen – tragen Sie sich in die Warteliste ein, falls noch nicht geschehen. 🚀
+
+## Anmelden { #login }
+
+Stellen Sie sicher, dass Sie bereits ein **FastAPI-Cloud-Konto** haben (wir haben Sie von der Warteliste eingeladen 😉).
+
+Melden Sie sich dann an:
+
+
+{% endfor -%}
+
+### Gold- und Silber-Sponsoren { #gold-and-silver-sponsors }
+
{% for sponsor in sponsors.gold -%}
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
{% endfor %}
-{% endif %}
@@ -111,6 +117,12 @@ Seine Schlüssel-Merkmale sind:
---
+## FastAPI Mini-Dokumentarfilm { #fastapi-mini-documentary }
+
+Es gibt einen FastAPI-Mini-Dokumentarfilm, veröffentlicht Ende 2025, Sie können ihn online ansehen:
+
+
+
## **Typer**, das FastAPI der CLIs { #typer-the-fastapi-of-clis }
@@ -123,7 +135,7 @@ Wenn Sie eine Starlette für die Webanteile.
+* Starlette für die Webanteile.
* Pydantic für die Datenanteile.
## Installation { #installation }
@@ -227,9 +239,9 @@ INFO: Application startup complete.
Was der Befehl
+fastapi dev main.py macht ...Über den Befehl
-Der Befehl `fastapi dev` liest Ihre `main.py`-Datei, erkennt die **FastAPI**-App darin und startet einen Server mit Uvicorn.
+Der Befehl `fastapi dev` liest Ihre `main.py`-Datei, erkennt die **FastAPI**-App darin und startet einen Server mit Uvicorn.
Standardmäßig wird `fastapi dev` mit aktiviertem Auto-Reload für die lokale Entwicklung gestartet.
@@ -270,7 +282,7 @@ Sie sehen die alternative automatische Dokumentation (bereitgestellt von Bodyfastapi dev main.py ...uvicorn – für den Server, der Ihre Anwendung lädt und bereitstellt. Dies umfasst `uvicorn[standard]`, das einige Abhängigkeiten (z. B. `uvloop`) beinhaltet, die für eine Bereitstellung mit hoher Performanz benötigt werden.
+* uvicorn – für den Server, der Ihre Anwendung lädt und bereitstellt. Dies umfasst `uvicorn[standard]`, das einige Abhängigkeiten (z. B. `uvloop`) beinhaltet, die für eine Bereitstellung mit hoher Performanz benötigt werden.
* `fastapi-cli[standard]` – um den `fastapi`-Befehl bereitzustellen.
* Dies beinhaltet `fastapi-cloud-cli`, das es Ihnen ermöglicht, Ihre FastAPI-Anwendung auf FastAPI Cloud bereitzustellen.
diff --git a/docs/de/docs/project-generation.md b/docs/de/docs/project-generation.md
index e6da4949c..62702d852 100644
--- a/docs/de/docs/project-generation.md
+++ b/docs/de/docs/project-generation.md
@@ -9,20 +9,20 @@ GitHub-Repository: Verkettet sie mit einem Leerzeichen in der Mitte.
+* Verkettet sie mit einem Leerzeichen in der Mitte.
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
+{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
### Es bearbeiten { #edit-it }
@@ -78,7 +78,7 @@ Das war's.
Das sind die „Typhinweise“:
-{* ../../docs_src/python_types/tutorial002.py hl[1] *}
+{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
Das ist nicht das gleiche wie das Deklarieren von Defaultwerten, wie es hier der Fall ist:
@@ -106,7 +106,7 @@ Hier können Sie durch die Optionen blättern, bis Sie diejenige finden, bei der
Sehen Sie sich diese Funktion an, sie hat bereits Typhinweise:
-{* ../../docs_src/python_types/tutorial003.py hl[1] *}
+{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}
Da der Editor die Typen der Variablen kennt, erhalten Sie nicht nur Code-Vervollständigung, sondern auch eine Fehlerprüfung:
@@ -114,7 +114,7 @@ Da der Editor die Typen der Variablen kennt, erhalten Sie nicht nur Code-Vervoll
Jetzt, da Sie wissen, dass Sie das reparieren müssen, konvertieren Sie `age` mittels `str(age)` in einen String:
-{* ../../docs_src/python_types/tutorial004.py hl[2] *}
+{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
## Deklarieren von Typen { #declaring-types }
@@ -133,7 +133,7 @@ Zum Beispiel diese:
* `bool`
* `bytes`
-{* ../../docs_src/python_types/tutorial005.py hl[1] *}
+{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
### Generische Typen mit Typ-Parametern { #generic-types-with-type-parameters }
@@ -161,56 +161,24 @@ Wenn Sie über die **neueste Version von Python** verfügen, verwenden Sie die B
Definieren wir zum Beispiel eine Variable, die eine `list` von `str` – eine Liste von Strings – sein soll.
-//// tab | Python 3.9+
-
Deklarieren Sie die Variable mit der gleichen Doppelpunkt-Syntax (`:`).
Als Typ nehmen Sie `list`.
Da die Liste ein Typ ist, welcher innere Typen enthält, werden diese von eckigen Klammern umfasst:
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-Von `typing` importieren Sie `List` (mit Großbuchstaben `L`):
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-Deklarieren Sie die Variable mit der gleichen Doppelpunkt-Syntax (`:`).
-
-Als Typ nehmen Sie das `List`, das Sie von `typing` importiert haben.
-
-Da die Liste ein Typ ist, welcher innere Typen enthält, werden diese von eckigen Klammern umfasst:
-
-```Python hl_lines="4"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
/// info | Info
Die inneren Typen in den eckigen Klammern werden als „Typ-Parameter“ bezeichnet.
-In diesem Fall ist `str` der Typ-Parameter, der an `List` übergeben wird (oder `list` in Python 3.9 und darüber).
+In diesem Fall ist `str` der Typ-Parameter, der an `list` übergeben wird.
///
Das bedeutet: Die Variable `items` ist eine Liste – `list` – und jedes der Elemente in dieser Liste ist ein String – `str`.
-/// tip | Tipp
-
-Wenn Sie Python 3.9 oder höher verwenden, müssen Sie `List` nicht von `typing` importieren, Sie können stattdessen den regulären `list`-Typ verwenden.
-
-///
-
Auf diese Weise kann Ihr Editor Sie auch bei der Bearbeitung von Einträgen aus der Liste unterstützen:
@@ -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+
-
-In Python 3.9 ist `Annotated` ein Teil der Standardbibliothek, Sie können es von `typing` importieren.
+Seit 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 2c5046c73..52b32b148 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 ea85207ce..1d34430dc 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:
@@ -63,7 +63,7 @@ Und dann schreibt ein weiterer Hintergrundtask, der in der *Pfadoperation-Funkti
## Technische Details { #technical-details }
-Die Klasse `BackgroundTasks` stammt direkt von `starlette.background`.
+Die Klasse `BackgroundTasks` stammt direkt von `starlette.background`.
Sie wird direkt in FastAPI importiert/inkludiert, sodass Sie sie von `fastapi` importieren können und vermeiden, versehentlich das alternative `BackgroundTask` (ohne das `s` am Ende) von `starlette.background` zu importieren.
@@ -71,7 +71,7 @@ Indem Sie nur `BackgroundTasks` (und nicht `BackgroundTask`) verwenden, ist es d
Es ist immer noch möglich, `BackgroundTask` allein in FastAPI zu verwenden, aber Sie müssen das Objekt in Ihrem Code erstellen und eine Starlette-`Response` zurückgeben, die es enthält.
-Weitere Details finden Sie in Starlettes offizieller Dokumentation für Hintergrundtasks.
+Weitere Details finden Sie in Starlettes offizieller Dokumentation für Hintergrundtasks.
## Vorbehalt { #caveat }
diff --git a/docs/de/docs/tutorial/bigger-applications.md b/docs/de/docs/tutorial/bigger-applications.md
index 928d50adf..d478d77c2 100644
--- a/docs/de/docs/tutorial/bigger-applications.md
+++ b/docs/de/docs/tutorial/bigger-applications.md
@@ -56,19 +56,19 @@ from app.routers import items
Die gleiche Dateistruktur mit Kommentaren:
-```
+```bash
.
-├── app # „app“ ist ein Python-Package
-│ ├── __init__.py # diese Datei macht „app“ zu einem „Python-Package“
-│ ├── main.py # „main“-Modul, z. B. import app.main
-│ ├── dependencies.py # „dependencies“-Modul, z. B. import app.dependencies
-│ └── routers # „routers“ ist ein „Python-Subpackage“
-│ │ ├── __init__.py # macht „routers“ zu einem „Python-Subpackage“
-│ │ ├── items.py # „items“-Submodul, z. B. import app.routers.items
-│ │ └── users.py # „users“-Submodul, z. B. import app.routers.users
-│ └── internal # „internal“ ist ein „Python-Subpackage“
-│ ├── __init__.py # macht „internal“ zu einem „Python-Subpackage“
-│ └── admin.py # „admin“-Submodul, z. B. import app.internal.admin
+├── app # "app" ist ein Python-Package
+│ ├── __init__.py # diese Datei macht "app" zu einem "Python-Package"
+│ ├── main.py # "main"-Modul, z. B. import app.main
+│ ├── dependencies.py # "dependencies"-Modul, z. B. import app.dependencies
+│ └── routers # "routers" ist ein "Python-Subpackage"
+│ │ ├── __init__.py # macht "routers" zu einem "Python-Subpackage"
+│ │ ├── items.py # "items"-Submodul, z. B. import app.routers.items
+│ │ └── users.py # "users"-Submodul, z. B. import app.routers.users
+│ └── internal # "internal" ist ein "Python-Subpackage"
+│ ├── __init__.py # macht "internal" zu einem "Python-Subpackage"
+│ └── admin.py # "admin"-Submodul, z. B. import app.internal.admin
```
## `APIRouter` { #apirouter }
@@ -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.
@@ -531,7 +479,7 @@ $ fastapi dev app/main.py
-und öffnen Sie die Dokumentation unter http://127.0.0.1:8000/docs.
+Und öffnen Sie die Dokumentation unter http://127.0.0.1:8000/docs.
Sie sehen die automatische API-Dokumentation, einschließlich der Pfade aller Submodule, mit den richtigen Pfaden (und Präfixen) und den richtigen Tags:
diff --git a/docs/de/docs/tutorial/body-nested-models.md b/docs/de/docs/tutorial/body-nested-models.md
index 324d31928..65a5d7c1d 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 aa62199fe..d260998e9 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 1e6382b6f..cdf3122f2 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 2baf3d70d..25718bd33 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 191a7b4ef..81f0f3605 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 0a31f8653..0d12877c1 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 3d4493f35..7df0842eb 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 178c2673e..0083e7e7e 100644
--- a/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md
+++ b/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md
@@ -1,6 +1,6 @@
# Abhängigkeiten mit `yield` { #dependencies-with-yield }
-FastAPI unterstützt Abhängigkeiten, die nach Abschluss einige zusätzliche Schritte ausführen.
+FastAPI unterstützt Abhängigkeiten, die nach Abschluss einige zusätzliche Schritte ausführen.
Verwenden Sie dazu `yield` statt `return` und schreiben Sie die zusätzlichen Schritte / den zusätzlichen Code danach.
@@ -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 ausgeführt, nachdem die Response erstellt wurde, aber bevor sie gesendet wird:
+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
@@ -51,13 +51,13 @@ Sie können `async`- oder reguläre Funktionen verwenden.
Wenn Sie einen `try`-Block in einer Abhängigkeit mit `yield` verwenden, empfangen Sie alle Exceptions, die bei Verwendung der Abhängigkeit geworfen wurden.
-Wenn beispielsweise ein Code irgendwann in der Mitte, in einer anderen Abhängigkeit oder in einer *Pfadoperation*, ein „Rollback“ einer Datenbanktransaktion oder einen anderen Fehler verursacht, empfangen Sie die resultierende Exception in Ihrer Abhängigkeit.
+Wenn beispielsweise ein Code irgendwann in der Mitte, in einer anderen Abhängigkeit oder in einer *Pfadoperation*, ein „Rollback“ einer Datenbanktransaktion macht oder eine andere Exception verursacht, empfangen Sie die Exception in Ihrer Abhängigkeit.
Sie können also mit `except SomeException` diese bestimmte Exception innerhalb der Abhängigkeit handhaben.
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 }
@@ -95,9 +95,11 @@ Dieses funktioniert dank Pythons > 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
+```
-Wenn Sie sich früher auf dieses Verhalten verlassen haben, sollten Sie jetzt die Ressourcen für Hintergrundtasks innerhalb des Hintergrundtasks selbst erstellen und intern nur Daten verwenden, die nicht von den Ressourcen von Abhängigkeiten mit `yield` abhängen.
+## Abhängigkeiten mit `yield`, `HTTPException`, `except` und Hintergrundtasks { #dependencies-with-yield-httpexception-except-and-background-tasks }
-Anstatt beispielsweise dieselbe Datenbanksitzung zu verwenden, würden Sie eine neue Datenbanksitzung innerhalb des Hintergrundtasks erstellen und die Objekte mithilfe dieser neuen Sitzung aus der Datenbank abrufen. Und anstatt das Objekt aus der Datenbank als Parameter an die Hintergrundtask-Funktion zu übergeben, würden Sie die ID dieses Objekts übergeben und das Objekt dann innerhalb der Hintergrundtask-Funktion erneut laden.
+Abhängigkeiten mit `yield` haben sich im Laufe der Zeit weiterentwickelt, um verschiedene Anwendungsfälle abzudecken und einige Probleme zu beheben.
+Wenn Sie sehen möchten, was sich in verschiedenen Versionen von FastAPI geändert hat, lesen Sie mehr dazu im fortgeschrittenen Teil, unter [Fortgeschrittene Abhängigkeiten – Abhängigkeiten mit `yield`, `HTTPException`, `except` und Hintergrundtasks](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}.
## Kontextmanager { #context-managers }
### Was sind „Kontextmanager“ { #what-are-context-managers }
@@ -254,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 1db288fb8..1de497315 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 7b77125cb..f759bb257 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 928003c3f..fd17c9933 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 e2ffed292..07fe8c5d9 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/security/oauth2-jwt.md b/docs/de/docs/tutorial/security/oauth2-jwt.md
index 4b81c8069..0d5ce587b 100644
--- a/docs/de/docs/tutorial/security/oauth2-jwt.md
+++ b/docs/de/docs/tutorial/security/oauth2-jwt.md
@@ -64,20 +64,20 @@ Wenn Ihre Datenbank gestohlen wird, hat der Dieb nicht die Klartext-Passwörter
Der Dieb kann also nicht versuchen, die gleichen Passwörter in einem anderen System zu verwenden (da viele Benutzer überall das gleiche Passwort verwenden, wäre dies gefährlich).
-## `passlib` installieren { #install-passlib }
+## `pwdlib` installieren { #install-pwdlib }
-PassLib ist ein großartiges Python-Package, um Passwort-Hashes zu handhaben.
+pwdlib ist ein großartiges Python-Package, um Passwort-Hashes zu handhaben.
Es unterstützt viele sichere Hashing-Algorithmen und Werkzeuge, um mit diesen zu arbeiten.
-Der empfohlene Algorithmus ist „Bcrypt“.
+Der empfohlene Algorithmus ist „Argon2“.
-Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren, und installieren Sie dann PassLib mit Bcrypt:
+Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren, und installieren Sie dann pwdlib mit Argon2:
-
-## 🌀 🔢 📨 & 🛃 🕐
-
-👆 💪 💚 ✔️ 🔁 📨 👈 ✔ 📚 *➡ 🛠️*, ✋️ 👆 💚 🌀 👫 ⏮️ 🛃 📨 💚 🔠 *➡ 🛠️*.
-
-📚 💼, 👆 💪 ⚙️ 🐍 ⚒ "🏗" `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 907c7e68e..000000000
--- 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 3404c2687..000000000
--- 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 283d4aa09..000000000
--- 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 ab95b3e7b..000000000
--- 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 4dd597262..000000000
--- 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 68adb6d65..000000000
--- 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 a680c9051..000000000
--- 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 48ef8e46d..000000000
--- 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 cb04fa3fb..000000000
--- 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 b0a821668..000000000
--- 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 9d9d5fa8d..000000000
--- 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 4933484dd..000000000
--- 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 d9fdbaa87..000000000
--- 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 29819a205..000000000
--- 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 e9e1b62d2..000000000
--- 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 73736f3b3..000000000
--- 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 5cdc47505..000000000
--- 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 9e3bc0058..000000000
--- 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 7fdd0d68a..000000000
--- 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 ad4d4fc71..000000000
--- 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 d923347d5..000000000
--- 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 59b587285..000000000
--- 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 cde778b0f..000000000
--- 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 003c3f62d..000000000
--- 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 bbb017277..000000000
--- 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️⃣ 👂 🔛 **⛴** 📢. & ⚫️ 🔜 📶 🌐 📻 👨🏭 🛠️.
-
-👈 👨🏭 🛠️ 🔜 🕐 🏃♂ 👆 🈸, 👫 🔜 🎭 👑 📊 📨 **📨** & 📨 **📨**, & 👫 🔜 📐 🕳 👆 🚮 🔢 💾.
-
-
-
-& ↗️, 🎏 🎰 🔜 🎲 ✔️ **🎏 🛠️** 🏃 👍, ↖️ ⚪️➡️ 👆 🈸.
-
-😌 ℹ 👈 🌐 **💽 ⚙️** 🔠 🛠️ 💪 **🪀** 📚 🤭 🕰, ✋️ **💾 (💾)** 🛎 🚧 🌖 ⚖️ 🌘 **⚖**.
-
-🚥 👆 ✔️ 🛠️ 👈 🔨 ⭐ 💸 📊 🔠 🕰 & 👆 ✔️ 📚 👩💻, ⤴️ **💽 🛠️** 🔜 🎲 *⚖* (↩️ 🕧 🔜 🆙 & 🔽 🔜).
-
-### 🖼 🧬 🧰 & 🎛
-
-📤 💪 📚 🎯 🏆 👉, & 👤 🔜 💬 👆 🌅 🔃 🎯 🎛 ⏭ 📃, 🖼 🕐❔ 💬 🔃 ☁ & 📦.
-
-👑 ⚛ 🤔 👈 📤 ✔️ **👁** 🦲 🚚 **⛴** **📢 📢**. & ⤴️ ⚫️ ✔️ ✔️ 🌌 **📶** 📻 🔁 **🛠️/👨🏭**.
-
-📥 💪 🌀 & 🎛:
-
-* **🐁** 🛠️ **Uvicorn 👨🏭**
- * 🐁 🔜 **🛠️ 👨💼** 👂 🔛 **📢** & **⛴**, 🧬 🔜 ✔️ **💗 Uvicorn 👨🏭 🛠️**
-* **Uvicorn** 🛠️ **Uvicorn 👨🏭**
- * 1️⃣ Uvicorn **🛠️ 👨💼** 🔜 👂 🔛 **📢** & **⛴**, & ⚫️ 🔜 ▶️ **💗 Uvicorn 👨🏭 🛠️**
-* **Kubernetes** & 🎏 📎 **📦 ⚙️**
- * 🕳 **☁** 🧽 🔜 👂 🔛 **📢** & **⛴**. 🧬 🔜 ✔️ **💗 📦**, 🔠 ⏮️ **1️⃣ Uvicorn 🛠️** 🏃♂
-* **☁ 🐕🦺** 👈 🍵 👉 👆
- * ☁ 🐕🦺 🔜 🎲 **🍵 🧬 👆**. ⚫️ 🔜 🎲 ➡️ 👆 🔬 **🛠️ 🏃**, ⚖️ **📦 🖼** ⚙️, 🙆 💼, ⚫️ 🔜 🌅 🎲 **👁 Uvicorn 🛠️**, & ☁ 🐕🦺 🔜 🈚 🔁 ⚫️.
-
-/// tip
-
-🚫 😟 🚥 👫 🏬 🔃 **📦**, ☁, ⚖️ Kubernetes 🚫 ⚒ 📚 🔑.
-
-👤 🔜 💬 👆 🌅 🔃 📦 🖼, ☁, Kubernetes, ♒️. 🔮 📃: [FastAPI 📦 - ☁](docker.md){.internal-link target=_blank}.
-
-///
-
-## ⏮️ 🔁 ⏭ ▶️
-
-📤 📚 💼 🌐❔ 👆 💚 🎭 📶 **⏭ ▶️** 👆 🈸.
-
-🖼, 👆 💪 💚 🏃 **💽 🛠️**.
-
-✋️ 🌅 💼, 👆 🔜 💚 🎭 👉 🔁 🕴 **🕐**.
-
-, 👆 🔜 💚 ✔️ **👁 🛠️** 🎭 👈 **⏮️ 🔁**, ⏭ ▶️ 🈸.
-
-& 👆 🔜 ✔️ ⚒ 💭 👈 ⚫️ 👁 🛠️ 🏃 👈 ⏮️ 🔁 ** 🚥 ⏮️, 👆 ▶️ **💗 🛠️** (💗 👨🏭) 🈸 ⚫️. 🚥 👈 🔁 🏃 **💗 🛠️**, 👫 🔜 **❎** 👷 🏃♂ ⚫️ 🔛 **🔗**, & 🚥 📶 🕳 💎 💖 💽 🛠️, 👫 💪 🤕 ⚔ ⏮️ 🔠 🎏.
-
-↗️, 📤 💼 🌐❔ 📤 🙅♂ ⚠ 🏃 ⏮️ 🔁 💗 🕰, 👈 💼, ⚫️ 📚 ⏩ 🍵.
-
-/// tip
-
-, ✔️ 🤯 👈 ⚓️ 🔛 👆 🖥, 💼 👆 **5️⃣📆 🚫 💪 🙆 ⏮️ 🔁** ⏭ ▶️ 👆 🈸.
-
-👈 💼, 👆 🚫🔜 ✔️ 😟 🔃 🙆 👉. 🤷
-
-///
-
-### 🖼 ⏮️ 🔁 🎛
-
-👉 🔜 **🪀 🙇** 🔛 🌌 👆 **🛠️ 👆 ⚙️**, & ⚫️ 🔜 🎲 🔗 🌌 👆 ▶️ 📋, 🚚 ⏏, ♒️.
-
-📥 💪 💭:
-
-* "🕑 📦" Kubernetes 👈 🏃 ⏭ 👆 📱 📦
-* 🎉 ✍ 👈 🏃 ⏮️ 🔁 & ⤴️ ▶️ 👆 🈸
- * 👆 🔜 💪 🌌 ▶️/⏏ *👈* 🎉 ✍, 🔍 ❌, ♒️.
-
-/// tip
-
-👤 🔜 🤝 👆 🌅 🧱 🖼 🔨 👉 ⏮️ 📦 🔮 📃: [FastAPI 📦 - ☁](docker.md){.internal-link target=_blank}.
-
-///
-
-## ℹ 🛠️
-
-👆 💽(Ⓜ) () **ℹ**, 👆 💪 🍴 ⚖️ **⚙️**, ⏮️ 👆 📋, 📊 🕰 🔛 💽, & 💾 💾 💪.
-
-❔ 🌅 ⚙️ ℹ 👆 💚 😩/♻ ❓ ⚫️ 💪 ⏩ 💭 "🚫 🌅", ✋️ 🌌, 👆 🔜 🎲 💚 🍴 **🌅 💪 🍵 💥**.
-
-🚥 👆 💸 3️⃣ 💽 ✋️ 👆 ⚙️ 🕴 🐥 🍖 👫 💾 & 💽, 👆 🎲 **🗑 💸** 👶, & 🎲 **🗑 💽 🔦 🏋️** 👶, ♒️.
-
-👈 💼, ⚫️ 💪 👻 ✔️ 🕴 2️⃣ 💽 & ⚙️ ↕ 🌐 👫 ℹ (💽, 💾, 💾, 🕸 💿, ♒️).
-
-🔛 🎏 ✋, 🚥 👆 ✔️ 2️⃣ 💽 & 👆 ⚙️ **1️⃣0️⃣0️⃣ 💯 👫 💽 & 💾**, ☝ 1️⃣ 🛠️ 🔜 💭 🌅 💾, & 💽 🔜 ✔️ ⚙️ 💾 "💾" (❔ 💪 💯 🕰 🐌), ⚖️ **💥**. ⚖️ 1️⃣ 🛠️ 💪 💪 📊 & 🔜 ✔️ ⌛ ⏭ 💽 🆓 🔄.
-
-👉 💼, ⚫️ 🔜 👍 🤚 **1️⃣ ➕ 💽** & 🏃 🛠️ 🔛 ⚫️ 👈 👫 🌐 ✔️ **🥃 💾 & 💽 🕰**.
-
-📤 🤞 👈 🤔 👆 ✔️ **🌵** ⚙️ 👆 🛠️. 🎲 ⚫️ 🚶 🦠, ⚖️ 🎲 🎏 🐕🦺 ⚖️ 🤖 ▶️ ⚙️ ⚫️. & 👆 💪 💚 ✔️ ➕ ℹ 🔒 👈 💼.
-
-👆 💪 🚮 **❌ 🔢** 🎯, 🖼, 🕳 **🖖 5️⃣0️⃣ 💯 9️⃣0️⃣ 💯** ℹ 🛠️. ☝ 👈 📚 🎲 👑 👜 👆 🔜 💚 ⚖ & ⚙️ ⚒ 👆 🛠️.
-
-👆 💪 ⚙️ 🙅 🧰 💖 `htop` 👀 💽 & 💾 ⚙️ 👆 💽 ⚖️ 💸 ⚙️ 🔠 🛠️. ⚖️ 👆 💪 ⚙️ 🌖 🏗 ⚖ 🧰, ❔ 5️⃣📆 📎 🤭 💽, ♒️.
-
-## 🌃
-
-👆 ✔️ 👂 📥 👑 🔧 👈 👆 🔜 🎲 💪 ✔️ 🤯 🕐❔ 🤔 ❔ 🛠️ 👆 🈸:
-
-* 💂♂ - 🇺🇸🔍
-* 🏃♂ 🔛 🕴
-* ⏏
-* 🧬 (🔢 🛠️ 🏃)
-* 💾
-* ⏮️ 🔁 ⏭ ▶️
-
-🤔 👉 💭 & ❔ ✔ 👫 🔜 🤝 👆 🤔 💪 ✊ 🙆 🚫 🕐❔ 🛠️ & 🛠️ 👆 🛠️. 👶
-
-⏭ 📄, 👤 🔜 🤝 👆 🌅 🧱 🖼 💪 🎛 👆 💪 ⏩. 👶
diff --git a/docs/em/docs/deployment/docker.md b/docs/em/docs/deployment/docker.md
deleted file mode 100644
index 2152f1a0e..000000000
--- a/docs/em/docs/deployment/docker.md
+++ /dev/null
@@ -1,731 +0,0 @@
-# FastAPI 📦 - ☁
-
-🕐❔ 🛠️ FastAPI 🈸 ⚠ 🎯 🏗 **💾 📦 🖼**. ⚫️ 🛎 🔨 ⚙️ **☁**. 👆 💪 ⤴️ 🛠️ 👈 📦 🖼 1️⃣ 👩❤👨 💪 🌌.
-
-⚙️ 💾 📦 ✔️ 📚 📈 ✅ **💂♂**, **🔬**, **🦁**, & 🎏.
-
-/// tip
-
-🏃 & ⏪ 💭 👉 💩 ❓ 🦘 [`Dockerfile` 🔛 👶](#fastapi).
-
-///
-
-
📁 🎮 👶
-
-```Dockerfile
-FROM python:3.9
-
-WORKDIR /code
-
-COPY ./requirements.txt /code/requirements.txt
-
-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"]
-
-# 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"]
-```
-
-
-
-### 🤝 🤝 ▶️
-
-🖥 🔜 ⤴️ 🔗 ⏮️ 👈 📢 📢 🔛 **⛴ 4️⃣4️⃣3️⃣** (🇺🇸🔍 ⛴).
-
-🥇 🍕 📻 🛠️ 🔗 🖖 👩💻 & 💽 & 💭 🔐 🔑 👫 🔜 ⚙️, ♒️.
-
-
-
-👉 🔗 🖖 👩💻 & 💽 🛠️ 🤝 🔗 🤙 **🤝 🤝**.
-
-### 🤝 ⏮️ 👲 ↔
-
-**🕴 1️⃣ 🛠️** 💽 💪 👂 🔛 🎯 **⛴** 🎯 **📢 📢**. 📤 💪 🎏 🛠️ 👂 🔛 🎏 ⛴ 🎏 📢 📢, ✋️ 🕴 1️⃣ 🔠 🌀 📢 📢 & ⛴.
-
-🤝 (🇺🇸🔍) ⚙️ 🎯 ⛴ `443` 🔢. 👈 ⛴ 👥 🔜 💪.
-
-🕴 1️⃣ 🛠️ 💪 👂 🔛 👉 ⛴, 🛠️ 👈 🔜 ⚫️ 🔜 **🤝 ❎ 🗳**.
-
-🤝 ❎ 🗳 🔜 ✔️ 🔐 1️⃣ ⚖️ 🌅 **🤝 📄** (🇺🇸🔍 📄).
-
-⚙️ **👲 ↔** 🔬 🔛, 🤝 ❎ 🗳 🔜 ✅ ❔ 🤝 (🇺🇸🔍) 📄 💪 ⚫️ 🔜 ⚙️ 👉 🔗, ⚙️ 1️⃣ 👈 🏏 🆔 📈 👩💻.
-
-👉 💼, ⚫️ 🔜 ⚙️ 📄 `someapp.example.com`.
-
-
-
-👩💻 ⏪ **💙** 👨💼 👈 🏗 👈 🤝 📄 (👉 💼 ➡️ 🗜, ✋️ 👥 🔜 👀 🔃 👈 ⏪), ⚫️ 💪 **✔** 👈 📄 ☑.
-
-⤴️, ⚙️ 📄, 👩💻 & 🤝 ❎ 🗳 **💭 ❔ 🗜** 🎂 **🕸 📻**. 👉 🏁 **🤝 🤝** 🍕.
-
-⏮️ 👉, 👩💻 & 💽 ✔️ **🗜 🕸 🔗**, 👉 ⚫️❔ 🤝 🚚. & ⤴️ 👫 💪 ⚙️ 👈 🔗 ▶️ ☑ **🇺🇸🔍 📻**.
-
-& 👈 ⚫️❔ **🇺🇸🔍** , ⚫️ ✅ **🇺🇸🔍** 🔘 **🔐 🤝 🔗** ↩️ 😁 (💽) 🕸 🔗.
-
-/// tip
-
-👀 👈 🔐 📻 🔨 **🕸 🎚**, 🚫 🇺🇸🔍 🎚.
-
-///
-
-### 🇺🇸🔍 📨
-
-🔜 👈 👩💻 & 💽 (🎯 🖥 & 🤝 ❎ 🗳) ✔️ **🗜 🕸 🔗**, 👫 💪 ▶️ **🇺🇸🔍 📻**.
-
-, 👩💻 📨 **🇺🇸🔍 📨**. 👉 🇺🇸🔍 📨 🔘 🗜 🤝 🔗.
-
-
-
-### 🗜 📨
-
-🤝 ❎ 🗳 🔜 ⚙️ 🔐 ✔ **🗜 📨**, & 🔜 📶 **✅ (🗜) 🇺🇸🔍 📨** 🛠️ 🏃 🈸 (🖼 🛠️ ⏮️ Uvicorn 🏃♂ FastAPI 🈸).
-
-
-
-### 🇺🇸🔍 📨
-
-🈸 🔜 🛠️ 📨 & 📨 **✅ (💽) 🇺🇸🔍 📨** 🤝 ❎ 🗳.
-
-
-
-### 🇺🇸🔍 📨
-
-🤝 ❎ 🗳 🔜 ⤴️ **🗜 📨** ⚙️ ⚛ ✔ ⏭ (👈 ▶️ ⏮️ 📄 `someapp.example.com`), & 📨 ⚫️ 🔙 🖥.
-
-⏭, 🖥 🔜 ✔ 👈 📨 ☑ & 🗜 ⏮️ ▶️️ 🔐 🔑, ♒️. ⚫️ 🔜 ⤴️ **🗜 📨** & 🛠️ ⚫️.
-
-
-
-👩💻 (🖥) 🔜 💭 👈 📨 👟 ⚪️➡️ ☑ 💽 ↩️ ⚫️ ⚙️ ⚛ 👫 ✔ ⚙️ **🇺🇸🔍 📄** ⏭.
-
-### 💗 🈸
-
-🎏 💽 (⚖️ 💽), 📤 💪 **💗 🈸**, 🖼, 🎏 🛠️ 📋 ⚖️ 💽.
-
-🕴 1️⃣ 🛠️ 💪 🚚 🎯 📢 & ⛴ (🤝 ❎ 🗳 👆 🖼) ✋️ 🎏 🈸/🛠️ 💪 🏃 🔛 💽(Ⓜ) 💁♂️, 📏 👫 🚫 🔄 ⚙️ 🎏 **🌀 📢 📢 & ⛴**.
-
-
-
-👈 🌌, 🤝 ❎ 🗳 💪 🍵 🇺🇸🔍 & 📄 **💗 🆔**, 💗 🈸, & ⤴️ 📶 📨 ▶️️ 🈸 🔠 💼.
-
-### 📄 🔕
-
-☝ 🔮, 🔠 📄 🔜 **🕛** (🔃 3️⃣ 🗓️ ⏮️ 🏗 ⚫️).
-
-& ⤴️, 📤 🔜 ➕1️⃣ 📋 (💼 ⚫️ ➕1️⃣ 📋, 💼 ⚫️ 💪 🎏 🤝 ❎ 🗳) 👈 🔜 💬 ➡️ 🗜, & ♻ 📄(Ⓜ).
-
-
-
-**🤝 📄** **🔗 ⏮️ 🆔 📛**, 🚫 ⏮️ 📢 📢.
-
-, ♻ 📄, 🔕 📋 💪 **🎦** 🛃 (➡️ 🗜) 👈 ⚫️ 👐 **"👍" & 🎛 👈 🆔**.
-
-👈, & 🏗 🎏 🈸 💪, 📤 📚 🌌 ⚫️ 💪 ⚫️. 🌟 🌌:
-
-* **🔀 🏓 ⏺**.
- * 👉, 🔕 📋 💪 🐕🦺 🔗 🏓 🐕🦺,, ⚓️ 🔛 🏓 🐕🦺 👆 ⚙️, 👉 5️⃣📆 ⚖️ 💪 🚫 🎛.
-* **🏃 💽** (🌘 ⏮️ 📄 🛠️ 🛠️) 🔛 📢 📢 📢 🔗 ⏮️ 🆔.
- * 👥 💬 🔛, 🕴 1️⃣ 🛠️ 💪 👂 🔛 🎯 📢 & ⛴.
- * 👉 1️⃣ 🤔 ⚫️❔ ⚫️ 📶 ⚠ 🕐❔ 🎏 🤝 ❎ 🗳 ✊ 💅 📄 🔕 🛠️.
- * ⏪, 👆 💪 ✔️ ⛔️ 🤝 ❎ 🗳 😖, ▶️ 🔕 📋 📎 📄, ⤴️ 🔗 👫 ⏮️ 🤝 ❎ 🗳, & ⤴️ ⏏ 🤝 ❎ 🗳. 👉 🚫 💯, 👆 📱(Ⓜ) 🔜 🚫 💪 ⏮️ 🕰 👈 🤝 ❎ 🗳 📆.
-
-🌐 👉 🔕 🛠️, ⏪ 🍦 📱, 1️⃣ 👑 🤔 ⚫️❔ 👆 🔜 💚 ✔️ **🎏 ⚙️ 🍵 🇺🇸🔍** ⏮️ 🤝 ❎ 🗳 ↩️ ⚙️ 🤝 📄 ⏮️ 🈸 💽 🔗 (✅ Uvicorn).
-
-## 🌃
-
-✔️ **🇺🇸🔍** 📶 ⚠, & **🎯** 🏆 💼. 🌅 🎯 👆 👩💻 ✔️ 🚮 🤭 🇺🇸🔍 🔃 **🤔 👉 🔧** & ❔ 👫 👷.
-
-✋️ 🕐 👆 💭 🔰 ℹ **🇺🇸🔍 👩💻** 👆 💪 💪 🌀 & 🔗 🎏 🧰 ℹ 👆 🛠️ 🌐 🙅 🌌.
-
-⏭ 📃, 👤 🔜 🎦 👆 📚 🧱 🖼 ❔ ⚒ 🆙 **🇺🇸🔍** **FastAPI** 🈸. 👶
diff --git a/docs/em/docs/deployment/index.md b/docs/em/docs/deployment/index.md
deleted file mode 100644
index 9bcf427b6..000000000
--- a/docs/em/docs/deployment/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# 🛠️
-
-🛠️ **FastAPI** 🈸 📶 ⏩.
-
-## ⚫️❔ 🔨 🛠️ ⛓
-
-**🛠️** 🈸 ⛓ 🎭 💪 📶 ⚒ ⚫️ **💪 👩💻**.
-
-**🕸 🛠️**, ⚫️ 🛎 🔌 🚮 ⚫️ **🛰 🎰**, ⏮️ **💽 📋** 👈 🚚 👍 🎭, ⚖, ♒️, 👈 👆 **👩💻** 💪 **🔐** 🈸 ♻ & 🍵 🔁 ⚖️ ⚠.
-
-👉 🔅 **🛠️** ▶️, 🌐❔ 👆 🕧 🔀 📟, 💔 ⚫️ & ♻ ⚫️, ⛔️ & 🔁 🛠️ 💽, ♒️.
-
-## 🛠️ 🎛
-
-📤 📚 🌌 ⚫️ ⚓️ 🔛 👆 🎯 ⚙️ 💼 & 🧰 👈 👆 ⚙️.
-
-👆 💪 **🛠️ 💽** 👆 ⚙️ 🌀 🧰, 👆 💪 ⚙️ **☁ 🐕🦺** 👈 🔨 🍕 👷 👆, ⚖️ 🎏 💪 🎛.
-
-👤 🔜 🎦 👆 👑 🔧 👆 🔜 🎲 ✔️ 🤯 🕐❔ 🛠️ **FastAPI** 🈸 (👐 🌅 ⚫️ ✔ 🙆 🎏 🆎 🕸 🈸).
-
-👆 🔜 👀 🌖 ℹ ✔️ 🤯 & ⚒ ⚫️ ⏭ 📄. 👶
diff --git a/docs/em/docs/deployment/manually.md b/docs/em/docs/deployment/manually.md
deleted file mode 100644
index 8ebe00c7c..000000000
--- a/docs/em/docs/deployment/manually.md
+++ /dev/null
@@ -1,159 +0,0 @@
-# 🏃 💽 ❎ - Uvicorn
-
-👑 👜 👆 💪 🏃 **FastAPI** 🈸 🛰 💽 🎰 🔫 💽 📋 💖 **Uvicorn**.
-
-📤 3️⃣ 👑 🎛:
-
-* Uvicorn: ↕ 🎭 🔫 💽.
-* Hypercorn: 🔫 💽 🔗 ⏮️ 🇺🇸🔍/2️⃣ & 🎻 👪 🎏 ⚒.
-* 👸: 🔫 💽 🏗 ✳ 📻.
-
-## 💽 🎰 & 💽 📋
-
-📤 🤪 ℹ 🔃 📛 ✔️ 🤯. 👶
-
-🔤 "**💽**" 🛎 ⚙️ 🔗 👯♂️ 🛰/☁ 💻 (⚛ ⚖️ 🕹 🎰) & 📋 👈 🏃♂ 🔛 👈 🎰 (✅ Uvicorn).
-
-✔️ 👈 🤯 🕐❔ 👆 ✍ "💽" 🏢, ⚫️ 💪 🔗 1️⃣ 📚 2️⃣ 👜.
-
-🕐❔ 🔗 🛰 🎰, ⚫️ ⚠ 🤙 ⚫️ **💽**, ✋️ **🎰**, **💾** (🕹 🎰), **🕸**. 👈 🌐 🔗 🆎 🛰 🎰, 🛎 🏃♂ 💾, 🌐❔ 👆 🏃 📋.
-
-## ❎ 💽 📋
-
-👆 💪 ❎ 🔫 🔗 💽 ⏮️:
-
-//// tab | Uvicorn
-
-* Uvicorn, 🌩-⏩ 🔫 💽, 🏗 🔛 uvloop & httptool.
-
-
-
-**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 e47ea0c35..000000000
--- 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 083e9ebd2..000000000
--- 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 fac4ba91a..000000000
--- a/docs/em/docs/index.md
+++ /dev/null
@@ -1,474 +0,0 @@
-# FastAPI
-
-
-
-
-
-{% endfor -%}
-{%- for sponsor in sponsors.silver -%}
-
-{% endfor %}
-{% endif %}
-
-
-
-🎏 💰
-
-## 🤔
-
-"_[...] 👤 ⚙️ **FastAPI** 📚 👫 📆. [...] 👤 🤙 📆 ⚙️ ⚫️ 🌐 👇 🏉 **⚗ 🐕🦺 🤸♂**. 👫 💆♂ 🛠️ 🔘 🐚 **🖥** 🏬 & **📠** 🏬._"
-
-
-
-🚥 👆 🏗 ✳ 📱 ⚙️ 📶 ↩️ 🕸 🛠️, ✅ 👅 **🏎**.
-
-**🏎** FastAPI 🐥 👪. & ⚫️ 🎯 **FastAPI 🇳🇨**. 👶 👶 👶
-
-## 📄
-
-🐍 3️⃣.7️⃣ ➕
-
-FastAPI 🧍 🔛 ⌚ 🐘:
-
-* 💃 🕸 🍕.
-* Pydantic 📊 🍕.
-
-## 👷♂
-
-
⚖️ ⚙️
-
-🚥 👆 📟 ⚙️ `async` / `await`, ⚙️ `async def`:
-
-```Python hl_lines="9 14"
-from typing import Union
-
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/")
-async def read_root():
- return {"Hello": "World"}
-
-
-@app.get("/items/{item_id}")
-async def read_item(item_id: int, q: Union[str, None] = None):
- return {"item_id": item_id, "q": q}
-```
-
-**🗒**:
-
-🚥 👆 🚫 💭, ✅ _"🏃 ❓" _ 📄 🔃 `async` & `await` 🩺.
-
-async def...🔃 📋
-
-📋 `uvicorn main:app` 🔗:
-
-* `main`: 📁 `main.py` (🐍 "🕹").
-* `app`: 🎚 ✍ 🔘 `main.py` ⏮️ ⏸ `app = FastAPI()`.
-* `--reload`: ⚒ 💽 ⏏ ⏮️ 📟 🔀. 🕴 👉 🛠️.
-
-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 ef6a21821..000000000
--- 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 d2af23bb9..000000000
--- 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 aed60c754..000000000
--- 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 78a321ae6..000000000
--- 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`.
-
-
-
-🎏 📁 📊 ⏮️ 🏤:
-
-```
-.
-├── app # "app" is a Python package
-│ ├── __init__.py # this file makes "app" a "Python package"
-│ ├── main.py # "main" module, e.g. import app.main
-│ ├── dependencies.py # "dependencies" module, e.g. import app.dependencies
-│ └── routers # "routers" is a "Python subpackage"
-│ │ ├── __init__.py # makes "routers" a "Python subpackage"
-│ │ ├── items.py # "items" submodule, e.g. import app.routers.items
-│ │ └── users.py # "users" submodule, e.g. import app.routers.users
-│ └── internal # "internal" is a "Python subpackage"
-│ ├── __init__.py # makes "internal" a "Python subpackage"
-│ └── admin.py # "admin" submodule, e.g. import app.internal.admin
-```
-
-## `APIRouter`
-
-➡️ 💬 📁 💡 🚚 👩💻 🔁 `/app/routers/users.py`.
-
-👆 💚 ✔️ *➡ 🛠️* 🔗 👆 👩💻 👽 ⚪️➡️ 🎂 📟, 🚧 ⚫️ 🏗.
-
-✋️ ⚫️ 🍕 🎏 **FastAPI** 🈸/🕸 🛠️ (⚫️ 🍕 🎏 "🐍 📦").
-
-👆 💪 ✍ *➡ 🛠️* 👈 🕹 ⚙️ `APIRouter`.
-
-### 🗄 `APIRouter`
-
-👆 🗄 ⚫️ & ✍ "👐" 🎏 🌌 👆 🔜 ⏮️ 🎓 `FastAPI`:
-
-```Python hl_lines="1 3" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
-
-### *➡ 🛠️* ⏮️ `APIRouter`
-
-& ⤴️ 👆 ⚙️ ⚫️ 📣 👆 *➡ 🛠️*.
-
-⚙️ ⚫️ 🎏 🌌 👆 🔜 ⚙️ `FastAPI` 🎓:
-
-```Python hl_lines="6 11 16" title="app/routers/users.py"
-{!../../docs_src/bigger_applications/app/routers/users.py!}
-```
-
-👆 💪 💭 `APIRouter` "🐩 `FastAPI`" 🎓.
-
-🌐 🎏 🎛 🐕🦺.
-
-🌐 🎏 `parameters`, `responses`, `dependencies`, `tags`, ♒️.
-
-/// tip
-
-👉 🖼, 🔢 🤙 `router`, ✋️ 👆 💪 📛 ⚫️ 👐 👆 💚.
-
-///
-
-👥 🔜 🔌 👉 `APIRouter` 👑 `FastAPI` 📱, ✋️ 🥇, ➡️ ✅ 🔗 & ➕1️⃣ `APIRouter`.
-
-## 🔗
-
-👥 👀 👈 👥 🔜 💪 🔗 ⚙️ 📚 🥉 🈸.
-
-👥 🚮 👫 👫 👍 `dependencies` 🕹 (`app/dependencies.py`).
-
-👥 🔜 🔜 ⚙️ 🙅 🔗 ✍ 🛃 `X-Token` 🎚:
-
-```Python hl_lines="1 4-6" title="app/dependencies.py"
-{!../../docs_src/bigger_applications/app/dependencies.py!}
-```
-
-/// tip
-
-👥 ⚙️ 💭 🎚 📉 👉 🖼.
-
-✋️ 🎰 💼 👆 🔜 🤚 👍 🏁 ⚙️ 🛠️ [💂♂ 🚙](security/index.md){.internal-link target=_blank}.
-
-///
-
-## ➕1️⃣ 🕹 ⏮️ `APIRouter`
-
-➡️ 💬 👆 ✔️ 🔗 💡 🚚 "🏬" ⚪️➡️ 👆 🈸 🕹 `app/routers/items.py`.
-
-👆 ✔️ *➡ 🛠️* :
-
-* `/items/`
-* `/items/{item_id}`
-
-⚫️ 🌐 🎏 📊 ⏮️ `app/routers/users.py`.
-
-✋️ 👥 💚 🙃 & 📉 📟 🍖.
-
-👥 💭 🌐 *➡ 🛠️* 👉 🕹 ✔️ 🎏:
-
-* ➡ `prefix`: `/items`.
-* `tags`: (1️⃣ 🔖: `items`).
-* ➕ `responses`.
-* `dependencies`: 👫 🌐 💪 👈 `X-Token` 🔗 👥 ✍.
-
-, ↩️ ❎ 🌐 👈 🔠 *➡ 🛠️*, 👥 💪 🚮 ⚫️ `APIRouter`.
-
-```Python hl_lines="5-10 16 21" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
-
-➡ 🔠 *➡ 🛠️* ✔️ ▶️ ⏮️ `/`, 💖:
-
-```Python hl_lines="1"
-@router.get("/{item_id}")
-async def read_item(item_id: str):
- ...
-```
-
-...🔡 🔜 🚫 🔌 🏁 `/`.
-
-, 🔡 👉 💼 `/items`.
-
-👥 💪 🚮 📇 `tags` & ➕ `responses` 👈 🔜 ✔ 🌐 *➡ 🛠️* 🔌 👉 📻.
-
-& 👥 💪 🚮 📇 `dependencies` 👈 🔜 🚮 🌐 *➡ 🛠️* 📻 & 🔜 🛠️/❎ 🔠 📨 ⚒ 👫.
-
-/// tip
-
-🗒 👈, 🌅 💖 [🔗 *➡ 🛠️ 👨🎨*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, 🙅♂ 💲 🔜 🚶♀️ 👆 *➡ 🛠️ 🔢*.
-
-///
-
-🔚 🏁 👈 🏬 ➡ 🔜:
-
-* `/items/`
-* `/items/{item_id}`
-
-...👥 🎯.
-
-* 👫 🔜 ™ ⏮️ 📇 🔖 👈 🔌 👁 🎻 `"items"`.
- * 👫 "🔖" ✴️ ⚠ 🏧 🎓 🧾 ⚙️ (⚙️ 🗄).
-* 🌐 👫 🔜 🔌 🔁 `responses`.
-* 🌐 👫 *➡ 🛠️* 🔜 ✔️ 📇 `dependencies` 🔬/🛠️ ⏭ 👫.
- * 🚥 👆 📣 🔗 🎯 *➡ 🛠️*, **👫 🔜 🛠️ 💁♂️**.
- * 📻 🔗 🛠️ 🥇, ⤴️ [`dependencies` 👨🎨](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, & ⤴️ 😐 🔢 🔗.
- * 👆 💪 🚮 [`Security` 🔗 ⏮️ `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
-
-/// tip
-
-✔️ `dependencies` `APIRouter` 💪 ⚙️, 🖼, 🚚 🤝 🎂 👪 *➡ 🛠️*. 🚥 🔗 🚫 🚮 📦 🔠 1️⃣ 👫.
-
-///
-
-/// check
-
-`prefix`, `tags`, `responses`, & `dependencies` 🔢 (📚 🎏 💼) ⚒ ⚪️➡️ **FastAPI** ℹ 👆 ❎ 📟 ❎.
-
-///
-
-### 🗄 🔗
-
-👉 📟 👨❤👨 🕹 `app.routers.items`, 📁 `app/routers/items.py`.
-
-& 👥 💪 🤚 🔗 🔢 ⚪️➡️ 🕹 `app.dependencies`, 📁 `app/dependencies.py`.
-
-👥 ⚙️ ⚖ 🗄 ⏮️ `..` 🔗:
-
-```Python hl_lines="3" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
-
-#### ❔ ⚖ 🗄 👷
-
-/// tip
-
-🚥 👆 💭 👌 ❔ 🗄 👷, 😣 ⏭ 📄 🔛.
-
-///
-
-👁 ❣ `.`, 💖:
-
-```Python
-from .dependencies import get_token_header
-```
-
-🔜 ⛓:
-
-* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/routers/items.py`) 🖖 (📁 `app/routers/`)...
-* 🔎 🕹 `dependencies` (👽 📁 `app/routers/dependencies.py`)...
-* & ⚪️➡️ ⚫️, 🗄 🔢 `get_token_header`.
-
-✋️ 👈 📁 🚫 🔀, 👆 🔗 📁 `app/dependencies.py`.
-
-💭 ❔ 👆 📱/📁 📊 👀 💖:
-
-
-
----
-
-2️⃣ ❣ `..`, 💖:
-
-```Python
-from ..dependencies import get_token_header
-```
-
-⛓:
-
-* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/routers/items.py`) 🖖 (📁 `app/routers/`)...
-* 🚶 👪 📦 (📁 `app/`)...
-* & 📤, 🔎 🕹 `dependencies` (📁 `app/dependencies.py`)...
-* & ⚪️➡️ ⚫️, 🗄 🔢 `get_token_header`.
-
-👈 👷 ☑ ❗ 👶
-
----
-
-🎏 🌌, 🚥 👥 ✔️ ⚙️ 3️⃣ ❣ `...`, 💖:
-
-```Python
-from ...dependencies import get_token_header
-```
-
-that 🔜 ⛓:
-
-* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/routers/items.py`) 🖖 (📁 `app/routers/`)...
-* 🚶 👪 📦 (📁 `app/`)...
-* ⤴️ 🚶 👪 👈 📦 (📤 🙅♂ 👪 📦, `app` 🔝 🎚 👶)...
-* & 📤, 🔎 🕹 `dependencies` (📁 `app/dependencies.py`)...
-* & ⚪️➡️ ⚫️, 🗄 🔢 `get_token_header`.
-
-👈 🔜 🔗 📦 🔛 `app/`, ⏮️ 🚮 👍 📁 `__init__.py`, ♒️. ✋️ 👥 🚫 ✔️ 👈. , 👈 🔜 🚮 ❌ 👆 🖼. 👶
-
-✋️ 🔜 👆 💭 ❔ ⚫️ 👷, 👆 💪 ⚙️ ⚖ 🗄 👆 👍 📱 🙅♂ 🤔 ❔ 🏗 👫. 👶
-
-### 🚮 🛃 `tags`, `responses`, & `dependencies`
-
-👥 🚫 ❎ 🔡 `/items` 🚫 `tags=["items"]` 🔠 *➡ 🛠️* ↩️ 👥 🚮 👫 `APIRouter`.
-
-✋️ 👥 💪 🚮 _🌅_ `tags` 👈 🔜 ✔ 🎯 *➡ 🛠️*, & ➕ `responses` 🎯 👈 *➡ 🛠️*:
-
-```Python hl_lines="30-31" title="app/routers/items.py"
-{!../../docs_src/bigger_applications/app/routers/items.py!}
-```
-
-/// tip
-
-👉 🏁 ➡ 🛠️ 🔜 ✔️ 🌀 🔖: `["items", "custom"]`.
-
- & ⚫️ 🔜 ✔️ 👯♂️ 📨 🧾, 1️⃣ `404` & 1️⃣ `403`.
-
-///
-
-## 👑 `FastAPI`
-
-🔜, ➡️ 👀 🕹 `app/main.py`.
-
-📥 🌐❔ 👆 🗄 & ⚙️ 🎓 `FastAPI`.
-
-👉 🔜 👑 📁 👆 🈸 👈 👔 🌐 👯♂️.
-
-& 🏆 👆 ⚛ 🔜 🔜 🖖 🚮 👍 🎯 🕹, 👑 📁 🔜 🙅.
-
-### 🗄 `FastAPI`
-
-👆 🗄 & ✍ `FastAPI` 🎓 🛎.
-
-& 👥 💪 📣 [🌐 🔗](dependencies/global-dependencies.md){.internal-link target=_blank} 👈 🔜 🌀 ⏮️ 🔗 🔠 `APIRouter`:
-
-```Python hl_lines="1 3 7" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
-
-### 🗄 `APIRouter`
-
-🔜 👥 🗄 🎏 🔁 👈 ✔️ `APIRouter`Ⓜ:
-
-```Python hl_lines="5" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
-
-📁 `app/routers/users.py` & `app/routers/items.py` 🔁 👈 🍕 🎏 🐍 📦 `app`, 👥 💪 ⚙️ 👁 ❣ `.` 🗄 👫 ⚙️ "⚖ 🗄".
-
-### ❔ 🏭 👷
-
-📄:
-
-```Python
-from .routers import items, users
-```
-
-⛓:
-
-* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/main.py`) 🖖 (📁 `app/`)...
-* 👀 📦 `routers` (📁 `app/routers/`)...
-* & ⚪️➡️ ⚫️, 🗄 🔁 `items` (📁 `app/routers/items.py`) & `users` (📁 `app/routers/users.py`)...
-
-🕹 `items` 🔜 ✔️ 🔢 `router` (`items.router`). 👉 🎏 1️⃣ 👥 ✍ 📁 `app/routers/items.py`, ⚫️ `APIRouter` 🎚.
-
-& ⤴️ 👥 🎏 🕹 `users`.
-
-👥 💪 🗄 👫 💖:
-
-```Python
-from app.routers import items, users
-```
-
-/// info
-
-🥇 ⏬ "⚖ 🗄":
-
-```Python
-from .routers import items, users
-```
-
-🥈 ⏬ "🎆 🗄":
-
-```Python
-from app.routers import items, users
-```
-
-💡 🌅 🔃 🐍 📦 & 🕹, ✍ 🛂 🐍 🧾 🔃 🕹.
-
-///
-
-### ❎ 📛 💥
-
-👥 🏭 🔁 `items` 🔗, ↩️ 🏭 🚮 🔢 `router`.
-
-👉 ↩️ 👥 ✔️ ➕1️⃣ 🔢 📛 `router` 🔁 `users`.
-
-🚥 👥 ✔️ 🗄 1️⃣ ⏮️ 🎏, 💖:
-
-```Python
-from .routers.items import router
-from .routers.users import router
-```
-
-`router` ⚪️➡️ `users` 🔜 📁 1️⃣ ⚪️➡️ `items` & 👥 🚫🔜 💪 ⚙️ 👫 🎏 🕰.
-
-, 💪 ⚙️ 👯♂️ 👫 🎏 📁, 👥 🗄 🔁 🔗:
-
-```Python hl_lines="5" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
-
-### 🔌 `APIRouter`Ⓜ `users` & `items`
-
-🔜, ➡️ 🔌 `router`Ⓜ ⚪️➡️ 🔁 `users` & `items`:
-
-```Python hl_lines="10-11" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
-
-/// info
-
-`users.router` 🔌 `APIRouter` 🔘 📁 `app/routers/users.py`.
-
- & `items.router` 🔌 `APIRouter` 🔘 📁 `app/routers/items.py`.
-
-///
-
-⏮️ `app.include_router()` 👥 💪 🚮 🔠 `APIRouter` 👑 `FastAPI` 🈸.
-
-⚫️ 🔜 🔌 🌐 🛣 ⚪️➡️ 👈 📻 🍕 ⚫️.
-
-/// note | 📡 ℹ
-
-⚫️ 🔜 🤙 🔘 ✍ *➡ 🛠️* 🔠 *➡ 🛠️* 👈 📣 `APIRouter`.
-
-, ⛅ 🎑, ⚫️ 🔜 🤙 👷 🚥 🌐 🎏 👁 📱.
-
-///
-
-/// check
-
-👆 🚫 ✔️ 😟 🔃 🎭 🕐❔ ✅ 📻.
-
-👉 🔜 ✊ ⏲ & 🔜 🕴 🔨 🕴.
-
-⚫️ 🏆 🚫 📉 🎭. 👶
-
-///
-
-### 🔌 `APIRouter` ⏮️ 🛃 `prefix`, `tags`, `responses`, & `dependencies`
-
-🔜, ➡️ 🌈 👆 🏢 🤝 👆 `app/internal/admin.py` 📁.
-
-⚫️ 🔌 `APIRouter` ⏮️ 📡 *➡ 🛠️* 👈 👆 🏢 💰 🖖 📚 🏗.
-
-👉 🖼 ⚫️ 🔜 💎 🙅. ✋️ ➡️ 💬 👈 ↩️ ⚫️ 💰 ⏮️ 🎏 🏗 🏢, 👥 🚫🔜 🔀 ⚫️ & 🚮 `prefix`, `dependencies`, `tags`, ♒️. 🔗 `APIRouter`:
-
-```Python hl_lines="3" title="app/internal/admin.py"
-{!../../docs_src/bigger_applications/app/internal/admin.py!}
-```
-
-✋️ 👥 💚 ⚒ 🛃 `prefix` 🕐❔ ✅ `APIRouter` 👈 🌐 🚮 *➡ 🛠️* ▶️ ⏮️ `/admin`, 👥 💚 🔐 ⚫️ ⏮️ `dependencies` 👥 ⏪ ✔️ 👉 🏗, & 👥 💚 🔌 `tags` & `responses`.
-
-👥 💪 📣 🌐 👈 🍵 ✔️ 🔀 ⏮️ `APIRouter` 🚶♀️ 👈 🔢 `app.include_router()`:
-
-```Python hl_lines="14-17" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
-
-👈 🌌, ⏮️ `APIRouter` 🔜 🚧 ⚗, 👥 💪 💰 👈 🎏 `app/internal/admin.py` 📁 ⏮️ 🎏 🏗 🏢.
-
-🏁 👈 👆 📱, 🔠 *➡ 🛠️* ⚪️➡️ `admin` 🕹 🔜 ✔️:
-
-* 🔡 `/admin`.
-* 🔖 `admin`.
-* 🔗 `get_token_header`.
-* 📨 `418`. 👶
-
-✋️ 👈 🔜 🕴 📉 👈 `APIRouter` 👆 📱, 🚫 🙆 🎏 📟 👈 ⚙️ ⚫️.
-
-, 🖼, 🎏 🏗 💪 ⚙️ 🎏 `APIRouter` ⏮️ 🎏 🤝 👩🔬.
-
-### 🔌 *➡ 🛠️*
-
-👥 💪 🚮 *➡ 🛠️* 🔗 `FastAPI` 📱.
-
-📥 👥 ⚫️... 🎦 👈 👥 💪 🤷:
-
-```Python hl_lines="21-23" title="app/main.py"
-{!../../docs_src/bigger_applications/app/main.py!}
-```
-
-& ⚫️ 🔜 👷 ☑, 👯♂️ ⏮️ 🌐 🎏 *➡ 🛠️* 🚮 ⏮️ `app.include_router()`.
-
-/// info | 📶 📡 ℹ
-
-**🗒**: 👉 📶 📡 ℹ 👈 👆 🎲 💪 **🚶**.
-
----
-
- `APIRouter`Ⓜ 🚫 "🗻", 👫 🚫 👽 ⚪️➡️ 🎂 🈸.
-
-👉 ↩️ 👥 💚 🔌 👫 *➡ 🛠️* 🗄 🔗 & 👩💻 🔢.
-
-👥 🚫🔜 ❎ 👫 & "🗻" 👫 ➡ 🎂, *➡ 🛠️* "🖖" (🏤-✍), 🚫 🔌 🔗.
-
-///
-
-## ✅ 🏧 🛠️ 🩺
-
-🔜, 🏃 `uvicorn`, ⚙️ 🕹 `app.main` & 🔢 `app`:
-
-
-
-## 🔌 🎏 📻 💗 🕰 ⏮️ 🎏 `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 f202284b5..000000000
--- 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 3a2f2bd54..000000000
--- 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 6c8d5a610..000000000
--- 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 7e2fbfaf7..000000000
--- 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 09e1d7cca..000000000
--- 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 4699fe2a5..000000000
--- 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 44ab4adc5..000000000
--- 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 97e61a763..000000000
--- 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 41938bc7b..000000000
--- 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 ab144a497..000000000
--- 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 1b37b1cf2..000000000
--- 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 5a22e5f1c..000000000
--- 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 ce87d9ee4..000000000
--- 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 6d622e952..000000000
--- 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 ad05f701e..000000000
--- 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 f15a74b4a..000000000
--- 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 19ab5b798..000000000
--- 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 a8f936b01..000000000
--- 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 d73b730e1..000000000
--- 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 fa5e3a22b..000000000
--- 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 5f7532341..000000000
--- a/docs/em/docs/tutorial/index.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# 🔰 - 👩💻 🦮
-
-👉 🔰 🎦 👆 ❔ ⚙️ **FastAPI** ⏮️ 🌅 🚮 ⚒, 🔁 🔁.
-
-🔠 📄 📉 🏗 🔛 ⏮️ 🕐, ✋️ ⚫️ 🏗 🎏 ❔, 👈 👆 💪 🚶 🔗 🙆 🎯 1️⃣ ❎ 👆 🎯 🛠️ 💪.
-
-⚫️ 🏗 👷 🔮 🔗.
-
-👆 💪 👟 🔙 & 👀 ⚫️❔ ⚫️❔ 👆 💪.
-
-## 🏃 📟
-
-🌐 📟 🍫 💪 📁 & ⚙️ 🔗 (👫 🤙 💯 🐍 📁).
-
-🏃 🙆 🖼, 📁 📟 📁 `main.py`, & ▶️ `uvicorn` ⏮️:
-
-contact 🏑🔢 🆎 📛 namestr⚖ 📛 📧 👨💼/🏢. urlstr📛 ☝ 📧 ℹ. 🔜 📁 📛. emailstr📧 📢 📧 👨💼/🏢. 🔜 📁 📧 📢. license_info 🏑🔢 🆎 📛 namestr🚚 (🚥 license_info ⚒). 🛂 📛 ⚙️ 🛠️.urlstr📛 🛂 ⚙️ 🛠️. 🔜 📁 📛.
-
-## 🗃 🔖
-
-👆 💪 🚮 🌖 🗃 🎏 🔖 ⚙️ 👪 👆 ➡ 🛠️ ⏮️ 🔢 `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 d203471e8..000000000
--- 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 c6030c089..000000000
--- 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 b45e0557b..000000000
--- 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 a914dc905..000000000
--- 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 fd077bf8f..000000000
--- 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 5c8d868a9..000000000
--- 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 c3bdeafd4..000000000
--- 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 680b1a96a..000000000
--- 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 1cc1ea5dc..000000000
--- 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 477376458..000000000
--- 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 413ceb916..000000000
--- 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 1bd314c51..000000000
--- 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 8fb459a65..000000000
--- 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 2f4a26f35..000000000
--- 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 1a47e5510..000000000
--- 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 ee7bc2d28..000000000
--- 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 1fd513d48..000000000
--- 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 6ff6e37a9..000000000
--- 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 cb4a1ca21..000000000
--- 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
-* Do not change the paths in links to images, code files, Markdown documents.
+Avoid inadvertently doing harm.
-* However, when a Markdown document is translated, the `#hash-parts` in links to its headings may change. Update these links if possible.
- * Search for such links in the translated document using the regex `#[^# ]`.
- * Search in all documents already translated into your language for `your-translated-document.md`. For example VS Code has an option "Edit" -> "Find in Files".
- * When translating a document, do not "pre-translate" `#hash-parts` that link to headings in untranslated documents.
+You have amazing tools at hand, use them wisely to help effectively.
diff --git a/docs/en/docs/css/custom.css b/docs/en/docs/css/custom.css
index a38df772f..87111ff64 100644
--- a/docs/en/docs/css/custom.css
+++ b/docs/en/docs/css/custom.css
@@ -1,3 +1,18 @@
+/* Fira Code, including characters used by Rich output, like the "heavy right-pointing angle bracket ornament", not included in Google Fonts */
+@import url(https://cdn.jsdelivr.net/npm/firacode@6.2.0/distr/fira_code.css);
+/* Noto Color Emoji for emoji support with the same font everywhere */
+@import url(https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap);
+
+/* Override default code font in Material for MkDocs to Fira Code */
+:root {
+ --md-code-font: "Fira Code", monospace, "Noto Color Emoji";
+}
+
+/* Override default regular font in Material for MkDocs to include Noto Color Emoji */
+:root {
+ --md-text-font: "Roboto", "Noto Color Emoji";
+}
+
.termynal-comment {
color: #4a968f;
font-style: italic;
diff --git a/docs/en/docs/css/termynal.css b/docs/en/docs/css/termynal.css
index 8534f9102..a2564e286 100644
--- a/docs/en/docs/css/termynal.css
+++ b/docs/en/docs/css/termynal.css
@@ -20,7 +20,7 @@
/* font-size: 18px; */
font-size: 15px;
/* font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; */
- font-family: 'Roboto Mono', 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace;
+ font-family: var(--md-code-font-family), 'Roboto Mono', 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace;
border-radius: 4px;
padding: 75px 45px 35px;
position: relative;
diff --git a/docs/en/docs/deployment/cloud.md b/docs/en/docs/deployment/cloud.md
index c88c4b51a..4f5c23e4b 100644
--- a/docs/en/docs/deployment/cloud.md
+++ b/docs/en/docs/deployment/cloud.md
@@ -4,13 +4,21 @@ You can use virtually **any cloud provider** to deploy your FastAPI application.
In most of the cases, the main cloud providers have guides to deploy FastAPI with them.
-## Cloud Providers - Sponsors { #cloud-providers-sponsors }
+## FastAPI Cloud { #fastapi-cloud }
+
+**FastAPI Cloud** is built by the same author and team behind **FastAPI**.
+
+It streamlines the process of **building**, **deploying**, and **accessing** an API with minimal effort.
-Some cloud providers ✨ [**sponsor FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, this ensures the continued and healthy **development** of FastAPI and its **ecosystem**.
+It brings the same **developer experience** of building apps with FastAPI to **deploying** them to the cloud. 🎉
+
+FastAPI Cloud is the primary sponsor and funding provider for the *FastAPI and friends* open source projects. ✨
+
+## Cloud Providers - Sponsors { #cloud-providers-sponsors }
-And it shows their true commitment to FastAPI and its **community** (you), as they not only want to provide you a **good service** but also want to make sure you have a **good and healthy framework**, FastAPI. 🙇
+Some other cloud providers ✨ [**sponsor FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ too. 🙇
-You might want to try their services and follow their guides:
+You might also want to consider them to follow their guides and try their services:
* Render
* Railway
diff --git a/docs/en/docs/deployment/fastapicloud.md b/docs/en/docs/deployment/fastapicloud.md
new file mode 100644
index 000000000..b0889974f
--- /dev/null
+++ b/docs/en/docs/deployment/fastapicloud.md
@@ -0,0 +1,65 @@
+# FastAPI Cloud { #fastapi-cloud }
+
+You can deploy your FastAPI app to FastAPI Cloud with **one command**, go and join the waiting list if you haven't. 🚀
+
+## Login { #login }
+
+Make sure you already have a **FastAPI Cloud** account (we invited you from the waiting list 😉).
+
+Then log in:
+
+power tools comes great responsibility.
+
+{% endfor -%}
+
+### Gold and Silver Sponsors { #gold-and-silver-sponsors }
+
{% for sponsor in sponsors.gold -%}
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
{% endfor %}
-{% endif %}
@@ -111,6 +117,12 @@ The key features are:
---
+## FastAPI mini documentary { #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 { #typer-the-fastapi-of-clis }
@@ -123,7 +135,7 @@ If you are building a CLI app to be
FastAPI stands on the shoulders of giants:
-* Starlette for the web parts.
+* Starlette for the web parts.
* Pydantic for the data parts.
## Installation { #installation }
@@ -229,7 +241,7 @@ INFO: Application startup complete.
About the command
-The command `fastapi dev` reads your `main.py` file, detects the **FastAPI** app in it, and starts a server using Uvicorn.
+The command `fastapi dev` reads your `main.py` file, detects the **FastAPI** app in it, and starts a server using Uvicorn.
By default, `fastapi dev` will start with auto-reload enabled for local development.
@@ -444,6 +456,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:
+
+fastapi dev main.py...uvicorn - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving.
+* uvicorn - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving.
* `fastapi-cli[standard]` - to provide the `fastapi` command.
* This includes `fastapi-cloud-cli`, which allows you to deploy your FastAPI application to FastAPI Cloud.
diff --git a/docs/en/docs/js/custom.js b/docs/en/docs/js/custom.js
index 48e95901d..be326d302 100644
--- a/docs/en/docs/js/custom.js
+++ b/docs/en/docs/js/custom.js
@@ -81,8 +81,14 @@ function setupTermynal() {
}
}
saveBuffer();
+ const inputCommands = useLines
+ .filter(line => line.type === "input")
+ .map(line => line.value)
+ .join("\n");
+ node.textContent = inputCommands;
const div = document.createElement("div");
- node.replaceWith(div);
+ node.style.display = "none";
+ node.after(div);
const termynal = new Termynal(div, {
lineData: useLines,
noInit: true,
diff --git a/docs/en/docs/management-tasks.md b/docs/en/docs/management-tasks.md
index 05cd5d27d..fc6e1eb28 100644
--- a/docs/en/docs/management-tasks.md
+++ b/docs/en/docs/management-tasks.md
@@ -74,7 +74,7 @@ Make sure you use a supported label from the `translate.yml`.
-* The title is correct following the instructions above.
-* It has the labels `lang-all` and `lang-{lang code}`.
-* The PR changes only one Markdown file adding a translation.
- * Or in some cases, at most two files, if they are small, for the same language, and people reviewed them.
- * If it's the first translation for that language, it will have additional `mkdocs.yml` files, for those cases follow the instructions below.
-* The PR doesn't add any additional or extraneous files.
-* The translation seems to have a similar structure as the original English file.
-* The translation doesn't seem to change the original content, for example with obvious additional documentation sections.
-* The translation doesn't use different Markdown structures, for example adding HTML tags when the original didn't have them.
-* The "admonition" sections, like `tip`, `info`, etc. are not changed or translated. For example:
-
-```
-/// tip
-
-This is a tip.
-
-///
-
-```
-
-looks like this:
-
-/// tip
-
-This is a tip.
-
-///
-
-...it could be translated as:
-
-```
-/// tip
-
-Esto es un consejo.
-
-///
-
-```
-
-...but needs to keep the exact `tip` keyword. If it was translated to `consejo`, like:
-
-```
-/// consejo
-
-Esto es un consejo.
-
-///
-
-```
-
-it would change the style to the default one, it would look like:
-
-/// consejo
-
-Esto es un consejo.
-
-///
-
-Those don't have to be translated, but if they are, they need to be written as:
+For these language translation PRs, confirm that:
-```
-/// tip | consejo
-
-Esto es un consejo.
-
-///
-
-```
-
-Which looks like:
-
-/// tip | consejo
-
-Esto es un consejo.
-
-///
-
-## First Translation PR
-
-When there's a first translation for a language, it will have a `docs/{lang code}/docs/index.md` translated file and a `docs/{lang code}/mkdocs.yml`.
-
-For example, for Bosnian, it would be:
-
-* `docs/bs/docs/index.md`
-* `docs/bs/mkdocs.yml`
-
-The `mkdocs.yml` file will have only the following content:
-
-```YAML
-INHERIT: ../en/mkdocs.yml
-```
-
-The language code would normally be in the ISO 639-1 list of language codes.
-
-In any case, the language code should be in the file docs/language_names.yml.
-
-There won't be yet a label for the language code, for example, if it was Bosnian, there wouldn't be a `lang-bs`. Before creating the label and adding it to the PR, create the GitHub Discussion:
-
-* Go to the Translations GitHub Discussions
-* Create a new discussion with the title `Bosnian Translations` (or the language name in English)
-* A description of:
-
-```Markdown
-## Bosnian translations
-
-This is the issue to track translations of the docs to Bosnian. 🚀
-
-Here are the [PRs to review with the label `lang-bs`](https://github.com/fastapi/fastapi/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc+label%3Alang-bs+label%3A%22awaiting-review%22). 🤓
-```
-
-Update "Bosnian" with the new language.
-
-And update the search link to point to the new language label that will be created, like `lang-bs`.
-
-Create and add the label to that new Discussion just created, like `lang-bs`.
+* The PR was automated (authored by @tiangolo), not made by another user.
+* It has the labels `lang-all` and `lang-{lang code}`.
+* If the PR is approved by at least one native speaker, you can merge it.
-Then go back to the PR, and add the label, like `lang-bs`, and `lang-all` and `awaiting-review`.
+## Review PRs
-Now the GitHub action will automatically detect the label `lang-bs` and will post in that Discussion that this PR is waiting to be reviewed.
+* If a PR doesn't explain what it does or why, if it seems like it could be useful, ask for more information. Otherwise, feel free to close it.
-## Review PRs
+* If a PR seems to be spam, meaningless, only to change statistics (to appear as "contributor") or similar, you can simply mark it as `invalid`, and it will be automatically closed.
-If a PR doesn't explain what it does or why, ask for more information.
+* If a PR seems to be AI generated, and seems like reviewing it would take more time from you than the time it took to write the prompt, mark it as `maybe-ai`, and it will be automatically closed.
-A PR should have a specific use case that it is solving.
+* A PR should have a specific use case that it is solving.
* If the PR is for a feature, it should have docs.
* Unless it's a feature we want to discourage, like support for a corner case that we don't want users to use.
* The docs should include a source example file, not write Python directly in Markdown.
-* If the source example(s) file can have different syntax for Python 3.8, 3.9, 3.10, there should be different versions of the file, and they should be shown in tabs in the docs.
+* If the source example(s) file can have different syntax for different Python versions, there should be different versions of the file, and they should be shown in tabs in the docs.
* There should be tests testing the source example.
* Before the PR is applied, the new tests should fail.
* After applying the PR, the new tests should pass.
@@ -254,27 +144,12 @@ Every month, a GitHub Action updates the FastAPI People data. Those PRs look lik
If the tests are passing, you can merge it right away.
-## External Links PRs
-
-When people add external links they edit this file external_links.yml.
-
-* Make sure the new link is in the correct category (e.g. "Podcasts") and language (e.g. "Japanese").
-* A new link should be at the top of its list.
-* The link URL should work (it should not return a 404).
-* The content of the link should be about FastAPI.
-* The new addition should have these fields:
- * `author`: The name of the author.
- * `link`: The URL with the content.
- * `title`: The title of the link (the title of the article, podcast, etc).
-
-After checking all these things and ensuring the PR has the right labels, you can merge it.
-
## Dependabot PRs
Dependabot will create PRs to update dependencies for several things, and those PRs all look similar, but some are way more delicate than others.
-* If the PR is for a direct dependency, so, Dependabot is modifying `pyproject.toml`, **don't merge it**. 😱 Let me check it first. There's a good chance that some additional tweaks or updates are needed.
-* If the PR updates one of the internal dependencies, for example it's modifying `requirements.txt` files, or GitHub Action versions, if the tests are passing, the release notes (shown in a summary in the PR) don't show any obvious potential breaking change, you can merge it. 😎
+* If the PR is for a direct dependency, so, Dependabot is modifying `pyproject.toml` in the main dependencies, **don't merge it**. 😱 Let me check it first. There's a good chance that some additional tweaks or updates are needed.
+* If the PR updates one of the internal dependencies, for example the group `dev` in `pyproject.toml`, or GitHub Action versions, if the tests are passing, the release notes (shown in a summary in the PR) don't show any obvious potential breaking change, you can merge it. 😎
## Mark GitHub Discussions Answers
diff --git a/docs/en/docs/project-generation.md b/docs/en/docs/project-generation.md
index 44f058b90..610d23ccb 100644
--- a/docs/en/docs/project-generation.md
+++ b/docs/en/docs/project-generation.md
@@ -9,18 +9,18 @@ GitHub Repository: Concatenates them with a space in the middle.
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
+{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
### Edit it { #edit-it }
@@ -78,7 +78,7 @@ That's it.
Those are the "type hints":
-{* ../../docs_src/python_types/tutorial002.py hl[1] *}
+{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
That is not the same as declaring default values like would be with:
@@ -106,7 +106,7 @@ With that, you can scroll, seeing the options, until you find the one that "ring
Check this function, it already has type hints:
-{* ../../docs_src/python_types/tutorial003.py hl[1] *}
+{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}
Because the editor knows the types of the variables, you don't only get completion, you also get error checks:
@@ -114,7 +114,7 @@ Because the editor knows the types of the variables, you don't only get completi
Now you know that you have to fix it, convert `age` to a string with `str(age)`:
-{* ../../docs_src/python_types/tutorial004.py hl[2] *}
+{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
## Declaring types { #declaring-types }
@@ -133,7 +133,7 @@ You can use, for example:
* `bool`
* `bytes`
-{* ../../docs_src/python_types/tutorial005.py hl[1] *}
+{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
### Generic types with type parameters { #generic-types-with-type-parameters }
@@ -161,56 +161,24 @@ If you can use the **latest versions of Python**, use the examples for the lates
For example, let's define a variable to be a `list` of `str`.
-//// tab | Python 3.9+
-
Declare the variable, with the same colon (`:`) syntax.
As the type, put `list`.
As the list is a type that contains some internal types, you put them in square brackets:
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-From `typing`, import `List` (with a capital `L`):
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-Declare the variable, with the same colon (`:`) syntax.
-
-As the type, put the `List` that you imported from `typing`.
-
-As the list is a type that contains some internal types, you put them in square brackets:
-
-```Python hl_lines="4"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
/// info
Those internal types in the square brackets are called "type parameters".
-In this case, `str` is the type parameter passed to `List` (or `list` in Python 3.9 and above).
+In this case, `str` is the type parameter passed to `list`.
///
That means: "the variable `items` is a `list`, and each of the items in this list is a `str`".
-/// tip
-
-If you use Python 3.9 or above, you don't have to import `List` from `typing`, you can use the same regular `list` type instead.
-
-///
-
By doing that, your editor can provide support even while processing items from the list:
@@ -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+
-
-In Python 3.9, `Annotated` is part of the standard library, so you can import it from `typing`.
+Since Python 3.9, `Annotated` is a 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 29112649c..1fefe908b 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -7,6 +7,606 @@ hide:
## Latest Changes
+### Docs
+
+* 🐛 Fix copy button in custom.js. PR [#14722](https://github.com/fastapi/fastapi/pull/14722) by [@fcharrier](https://github.com/fcharrier).
+* 📝 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 `llm-prompt.md` for Korean language. PR [#14763](https://github.com/fastapi/fastapi/pull/14763) by [@seuthootDev](https://github.com/seuthootDev).
+* 🌐 Update translations for ko (update outdated, found by fixer tool). PR [#14738](https://github.com/fastapi/fastapi/pull/14738) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🌐 Update translations for de (update-outdated). PR [#14690](https://github.com/fastapi/fastapi/pull/14690) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update LLM prompt for Russian translations. PR [#14733](https://github.com/fastapi/fastapi/pull/14733) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🌐 Update translations for ru (update-outdated). PR [#14693](https://github.com/fastapi/fastapi/pull/14693) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update translations for pt (update-outdated). PR [#14724](https://github.com/fastapi/fastapi/pull/14724) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update Korean LLM prompt. PR [#14740](https://github.com/fastapi/fastapi/pull/14740) by [@hard-coders](https://github.com/hard-coders).
+* 🌐 Improve LLM prompt for Turkish translations. PR [#14728](https://github.com/fastapi/fastapi/pull/14728) by [@Kadermiyanyedi](https://github.com/Kadermiyanyedi).
+* 🌐 Update portuguese llm-prompt.md. PR [#14702](https://github.com/fastapi/fastapi/pull/14702) by [@ceb10n](https://github.com/ceb10n).
+* 🌐 Update LLM prompt instructions file for French. PR [#14618](https://github.com/fastapi/fastapi/pull/14618) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update translations for ko (add-missing). PR [#14699](https://github.com/fastapi/fastapi/pull/14699) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 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
+
+* 🔧 Ensure that an edit to `uv.lock` gets the `internal` label. PR [#14759](https://github.com/fastapi/fastapi/pull/14759) by [@svlandeg](https://github.com/svlandeg).
+* 🔧 Update sponsors: remove Requestly. PR [#14735](https://github.com/fastapi/fastapi/pull/14735) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Update sponsors, LambdaTest changes to TestMu AI. PR [#14734](https://github.com/fastapi/fastapi/pull/14734) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ 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
+
+* ⬆ Bump ruff from 0.13.2 to 0.14.3. PR [#14276](https://github.com/fastapi/fastapi/pull/14276) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#14289](https://github.com/fastapi/fastapi/pull/14289) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+
+## 0.121.0
+
+### Features
+
+* ✨ Add support for dependencies with scopes, support `scope="request"` for dependencies with `yield` that exit before the response is sent. PR [#14262](https://github.com/fastapi/fastapi/pull/14262) by [@tiangolo](https://github.com/tiangolo).
+ * New docs: [Dependencies with `yield` - Early exit and `scope`](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#early-exit-and-scope).
+
+### Internal
+
+* 👥 Update FastAPI People - Contributors and Translators. PR [#14273](https://github.com/fastapi/fastapi/pull/14273) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI People - Sponsors. PR [#14274](https://github.com/fastapi/fastapi/pull/14274) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI GitHub topic repositories. PR [#14280](https://github.com/fastapi/fastapi/pull/14280) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Bump mkdocs-macros-plugin from 1.4.0 to 1.4.1. PR [#14277](https://github.com/fastapi/fastapi/pull/14277) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump mkdocstrings[python] from 0.26.1 to 0.30.1. PR [#14279](https://github.com/fastapi/fastapi/pull/14279) by [@dependabot[bot]](https://github.com/apps/dependabot).
+
+## 0.120.4
+
+### Fixes
+
+* 🐛 Fix security schemes in OpenAPI when added at the top level app. PR [#14266](https://github.com/fastapi/fastapi/pull/14266) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+## 0.120.3
+
+### Refactors
+
+* ♻️ Reduce internal cyclic recursion in dependencies, from 2 functions calling each other to 1 calling itself. PR [#14256](https://github.com/fastapi/fastapi/pull/14256) by [@tiangolo](https://github.com/tiangolo).
+* ♻️ Refactor internals of dependencies, simplify code and remove `get_param_sub_dependant`. PR [#14255](https://github.com/fastapi/fastapi/pull/14255) by [@tiangolo](https://github.com/tiangolo).
+* ♻️ Refactor internals of dependencies, simplify using dataclasses. PR [#14254](https://github.com/fastapi/fastapi/pull/14254) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* 📝 Update note for untranslated pages. PR [#14257](https://github.com/fastapi/fastapi/pull/14257) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+## 0.120.2
+
+### Fixes
+
+* 🐛 Fix separation of schemas with nested models introduced in 0.119.0. PR [#14246](https://github.com/fastapi/fastapi/pull/14246) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* 🔧 Add sponsor: SerpApi. PR [#14248](https://github.com/fastapi/fastapi/pull/14248) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Bump actions/download-artifact from 5 to 6. PR [#14236](https://github.com/fastapi/fastapi/pull/14236) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#14237](https://github.com/fastapi/fastapi/pull/14237) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+* ⬆ Bump actions/upload-artifact from 4 to 5. PR [#14235](https://github.com/fastapi/fastapi/pull/14235) by [@dependabot[bot]](https://github.com/apps/dependabot).
+
+## 0.120.1
+
+### Upgrades
+
+* ⬆️ Bump Starlette to <`0.50.0`. PR [#14234](https://github.com/fastapi/fastapi/pull/14234) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+### Internal
+
+* 🔧 Add `license` and `license-files` to `pyproject.toml`, remove `License` from `classifiers`. PR [#14230](https://github.com/fastapi/fastapi/pull/14230) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+## 0.120.0
+
+There are no major nor breaking changes in this release. ☕️
+
+The internal reference documentation now uses `annotated_doc.Doc` instead of `typing_extensions.Doc`, this adds a new (very small) dependency on [`annotated-doc`](https://github.com/fastapi/annotated-doc), a package made just to provide that `Doc` documentation utility class.
+
+I would expect `typing_extensions.Doc` to be deprecated and then removed at some point from `typing_extensions`, for that reason there's the new `annotated-doc` micro-package. If you are curious about this, you can read more in the repo for [`annotated-doc`](https://github.com/fastapi/annotated-doc).
+
+This new version `0.120.0` only contains that transition to the new home package for that utility class `Doc`.
+
+### Translations
+
+* 🌐 Sync German docs. PR [#14188](https://github.com/fastapi/fastapi/pull/14188) by [@nilslindemann](https://github.com/nilslindemann).
+
+### Internal
+
+* ➕ Migrate internal reference documentation from `typing_extensions.Doc` to `annotated_doc.Doc`. PR [#14222](https://github.com/fastapi/fastapi/pull/14222) by [@tiangolo](https://github.com/tiangolo).
+* 🛠️ Update German LLM prompt and test file. PR [#14189](https://github.com/fastapi/fastapi/pull/14189) by [@nilslindemann](https://github.com/nilslindemann).
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#14181](https://github.com/fastapi/fastapi/pull/14181) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+
+## 0.119.1
+
+### Fixes
+
+* 🐛 Fix internal Pydantic v1 compatibility (warnings) for Python 3.14 and Pydantic 2.12.1. PR [#14186](https://github.com/fastapi/fastapi/pull/14186) by [@svlandeg](https://github.com/svlandeg).
+
+### Docs
+
+* 📝 Replace `starlette.io` by `starlette.dev` and `uvicorn.org` by `uvicorn.dev`. PR [#14176](https://github.com/fastapi/fastapi/pull/14176) by [@Kludex](https://github.com/Kludex).
+
+### Internal
+
+* 🔧 Add sponsor Requestly. PR [#14205](https://github.com/fastapi/fastapi/pull/14205) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Configure reminder for `waiting` label in `issue-manager`. PR [#14156](https://github.com/fastapi/fastapi/pull/14156) by [@YuriiMotov](https://github.com/YuriiMotov).
+
+## 0.119.0
+
+FastAPI now (temporarily) supports both Pydantic v2 models and `pydantic.v1` models at the same time in the same app, to make it easier for any FastAPI apps still using Pydantic v1 to gradually but quickly **migrate to Pydantic v2**.
+
+```Python
+from fastapi import FastAPI
+from pydantic import BaseModel as BaseModelV2
+from pydantic.v1 import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+
+
+class ItemV2(BaseModelV2):
+ title: str
+ summary: str | None = None
+
+
+app = FastAPI()
+
+
+@app.post("/items/", response_model=ItemV2)
+def create_item(item: Item):
+ return {"title": item.name, "summary": item.description}
+```
+
+Adding this feature was a big effort with the main objective of making it easier for the few applications still stuck in Pydantic v1 to migrate to Pydantic v2.
+
+And with this, support for **Pydantic v1 is now deprecated** and will be **removed** from FastAPI in a future version soon.
+
+**Note**: have in mind that the Pydantic team already stopped supporting Pydantic v1 for recent versions of Python, starting with Python 3.14.
+
+You can read in the docs more about how to [Migrate from Pydantic v1 to Pydantic v2](https://fastapi.tiangolo.com/how-to/migrate-from-pydantic-v1-to-pydantic-v2/).
+
+### Features
+
+* ✨ Add support for `from pydantic.v1 import BaseModel`, mixed Pydantic v1 and v2 models in the same app. PR [#14168](https://github.com/fastapi/fastapi/pull/14168) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.118.3
+
+### Upgrades
+
+* ⬆️ Add support for Python 3.14. PR [#14165](https://github.com/fastapi/fastapi/pull/14165) by [@svlandeg](https://github.com/svlandeg).
+
+## 0.118.2
+
+### Fixes
+
+* 🐛 Fix tagged discriminated union not recognized as body field. PR [#12942](https://github.com/fastapi/fastapi/pull/12942) by [@frankie567](https://github.com/frankie567).
+
+### Internal
+
+* ⬆ Bump astral-sh/setup-uv from 6 to 7. PR [#14167](https://github.com/fastapi/fastapi/pull/14167) by [@dependabot[bot]](https://github.com/apps/dependabot).
+
+## 0.118.1
+
+### Upgrades
+
+* 👽️ Ensure compatibility with Pydantic 2.12.0. PR [#14036](https://github.com/fastapi/fastapi/pull/14036) by [@cjwatson](https://github.com/cjwatson).
+
+### Docs
+
+* 📝 Add External Link: Getting started with logging in FastAPI. PR [#14152](https://github.com/fastapi/fastapi/pull/14152) by [@itssimon](https://github.com/itssimon).
+
+### Translations
+
+* 🔨 Add Russian translations LLM prompt. PR [#13936](https://github.com/fastapi/fastapi/pull/13936) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Sync German docs. PR [#14149](https://github.com/fastapi/fastapi/pull/14149) by [@nilslindemann](https://github.com/nilslindemann).
+* 🌐 Add Russian translations for missing pages (LLM-generated). PR [#14135](https://github.com/fastapi/fastapi/pull/14135) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🌐 Update Russian translations for existing pages (LLM-generated). PR [#14123](https://github.com/fastapi/fastapi/pull/14123) by [@YuriiMotov](https://github.com/YuriiMotov).
+* 🌐 Remove configuration files for inactive translations. PR [#14130](https://github.com/fastapi/fastapi/pull/14130) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* 🔨 Move local coverage logic to its own script. PR [#14166](https://github.com/fastapi/fastapi/pull/14166) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#14161](https://github.com/fastapi/fastapi/pull/14161) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+* ⬆ Bump griffe-typingdoc from 0.2.8 to 0.2.9. PR [#14144](https://github.com/fastapi/fastapi/pull/14144) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump mkdocs-macros-plugin from 1.3.9 to 1.4.0. PR [#14145](https://github.com/fastapi/fastapi/pull/14145) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump markdown-include-variants from 0.0.4 to 0.0.5. PR [#14146](https://github.com/fastapi/fastapi/pull/14146) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#14126](https://github.com/fastapi/fastapi/pull/14126) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+* 👥 Update FastAPI GitHub topic repositories. PR [#14150](https://github.com/fastapi/fastapi/pull/14150) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI People - Sponsors. PR [#14139](https://github.com/fastapi/fastapi/pull/14139) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI People - Contributors and Translators. PR [#14138](https://github.com/fastapi/fastapi/pull/14138) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Bump ruff from 0.12.7 to 0.13.2. PR [#14147](https://github.com/fastapi/fastapi/pull/14147) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump sqlmodel from 0.0.24 to 0.0.25. PR [#14143](https://github.com/fastapi/fastapi/pull/14143) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump tiangolo/issue-manager from 0.5.1 to 0.6.0. PR [#14148](https://github.com/fastapi/fastapi/pull/14148) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 👷 Update docs previews comment, single comment, add failure status. PR [#14129](https://github.com/fastapi/fastapi/pull/14129) by [@tiangolo](https://github.com/tiangolo).
+* 🔨 Modify `mkdocs_hooks.py` to add `title` to page's metadata (remove permalinks in social cards). PR [#14125](https://github.com/fastapi/fastapi/pull/14125) by [@YuriiMotov](https://github.com/YuriiMotov).
+
## 0.118.0
### Fixes
@@ -5435,7 +6035,7 @@ Note: all the previous parameters are still there, so it's still possible to dec
* Upgrade the compatible version of Starlette to `0.12.0`.
* This includes support for ASGI 3 (the latest version of the standard).
- * It's now possible to use [Starlette's `StreamingResponse`](https://www.starlette.io/responses/#streamingresponse) with iterators, like [file-like](https://docs.python.org/3/glossary.html#term-file-like-object) objects (as those returned by `open()`).
+ * It's now possible to use [Starlette's `StreamingResponse`](https://www.starlette.dev/responses/#streamingresponse) with iterators, like [file-like](https://docs.python.org/3/glossary.html#term-file-like-object) objects (as those returned by `open()`).
* It's now possible to use the low level utility `iterate_in_threadpool` from `starlette.concurrency` (for advanced scenarios).
* PR [#243](https://github.com/tiangolo/fastapi/pull/243).
diff --git a/docs/en/docs/resources/index.md b/docs/en/docs/resources/index.md
index caefdf125..f7d48576f 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 6e16410a3..be7ecd587 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:
@@ -63,7 +63,7 @@ And then another background task generated at the *path operation function* will
## Technical Details { #technical-details }
-The class `BackgroundTasks` comes directly from `starlette.background`.
+The class `BackgroundTasks` comes directly from `starlette.background`.
It is imported/included directly into FastAPI so that you can import it from `fastapi` and avoid accidentally importing the alternative `BackgroundTask` (without the `s` at the end) from `starlette.background`.
@@ -71,7 +71,7 @@ By only using `BackgroundTasks` (and not `BackgroundTask`), it's then possible t
It's still possible to use `BackgroundTask` alone in FastAPI, but you have to create the object in your code and return a Starlette `Response` including it.
-You can see more details in Starlette's official docs for Background Tasks.
+You can see more details in Starlette's official docs for Background Tasks.
## Caveat { #caveat }
diff --git a/docs/en/docs/tutorial/bigger-applications.md b/docs/en/docs/tutorial/bigger-applications.md
index 74daa5483..f6cee8036 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 445235a42..5fd83a8f3 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 baeb53ec6..1b7fd7066 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 a820802f7..2d0dfcbb5 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 96dc5cf3d..016a65d7f 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 > 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
+```
+
## Dependencies with `yield`, `HTTPException`, `except` and Background Tasks { #dependencies-with-yield-httpexception-except-and-background-tasks }
Dependencies with `yield` have evolved over time to cover different use cases and fix some issues.
@@ -224,7 +269,7 @@ In Python, you can create Context Managers by 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 457cc2713..ea4307900 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 adf08a924..4b8cc9d29 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 2323b83c7..3c9c225fb 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 5090dbcdf..b287cdf50 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 a2d9757b2..638959248 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 cfa1c9073..b42e9ba2f 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 5b75d048b..8cf5a5a8a 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
@@ -37,4 +37,4 @@ All these parameters can be different than "`static`", adjust them with the need
## More info { #more-info }
-For more details and options check Starlette's docs about Static Files.
+For more details and options check Starlette's docs about Static Files.
diff --git a/docs/en/docs/tutorial/testing.md b/docs/en/docs/tutorial/testing.md
index 1e333c8f1..f61e62ac5 100644
--- a/docs/en/docs/tutorial/testing.md
+++ b/docs/en/docs/tutorial/testing.md
@@ -1,6 +1,6 @@
# Testing { #testing }
-Thanks to Starlette, testing **FastAPI** applications is easy and enjoyable.
+Thanks to Starlette, testing **FastAPI** applications is easy and enjoyable.
It is based on HTTPX, which in turn is designed based on Requests, so it's very familiar and intuitive.
@@ -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 e9b0a9fc4..c02e43ab9 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 e83400a8d..fa152c62d 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 7788bccd9..d0baa97a4 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 df7737aac..9adfa65cf 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 dd3c63a37..622a2caa2 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 f89db1533..4627e9bd1 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 8c590cbe8..f81c16ee8 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**.
+
+
Host: mysuperapp.com
Path: /items
+
+ Note over Proxy: El proxy añade headers reenviados
+
+ Proxy->>Server: HTTP Request
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Path: /items
+
+ Note over Server: El servidor interpreta los headers
(si --forwarded-allow-ips está configurado)
+
+ Server->>Proxy: HTTP Response
con URLs HTTPS correctas
+
+ Proxy->>Client: HTTPS Response
+```
+
+El **proxy** intercepta el request original del cliente y añade los *headers* especiales de reenvío (`X-Forwarded-*`) antes de pasar el request al **application server**.
+
+Estos headers preservan información sobre el request original que de otro modo se perdería:
+
+* **X-Forwarded-For**: La IP original del cliente
+* **X-Forwarded-Proto**: El protocolo original (`https`)
+* **X-Forwarded-Host**: El host original (`mysuperapp.com`)
+
+Cuando **FastAPI CLI** está configurado con `--forwarded-allow-ips`, confía en estos headers y los usa, por ejemplo para generar las URLs correctas en redirecciones.
+
+## Proxy con un prefijo de path eliminado { #proxy-with-a-stripped-path-prefix }
+
+Podrías tener un proxy que añada un prefijo de path a tu aplicación.
En estos casos, puedes usar `root_path` para configurar tu aplicación.
@@ -10,15 +109,13 @@ El `root_path` se usa para manejar estos casos específicos.
Y también se usa internamente al montar subaplicaciones.
-## Proxy con un prefijo de path eliminado
-
Tener un proxy con un prefijo de path eliminado, en este caso, significa que podrías declarar un path en `/app` en tu código, pero luego añades una capa encima (el proxy) que situaría tu aplicación **FastAPI** bajo un path como `/api/v1`.
En este caso, el path original `/app` realmente sería servido en `/api/v1/app`.
Aunque todo tu código esté escrito asumiendo que solo existe `/app`.
-{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *}
+{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *}
Y el proxy estaría **"eliminando"** el **prefijo del path** sobre la marcha antes de transmitir el request al servidor de aplicaciones (probablemente Uvicorn a través de FastAPI CLI), manteniendo a tu aplicación convencida de que está siendo servida en `/app`, así que no tienes que actualizar todo tu código para incluir el prefijo `/api/v1`.
@@ -66,14 +163,14 @@ La UI de los docs también necesitaría el esquema de OpenAPI para declarar que
En este ejemplo, el "Proxy" podría ser algo como **Traefik**. Y el servidor sería algo como FastAPI CLI con **Uvicorn**, ejecutando tu aplicación de FastAPI.
-### Proporcionando el `root_path`
+### Proporcionando el `root_path` { #providing-the-root-path }
Para lograr esto, puedes usar la opción de línea de comandos `--root-path` como:
-## 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 0ca1fd3b6..3a07482ad 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 022fb5a42..c2002a6f5 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. 🤓
@@ -154,12 +154,12 @@ Por debajo, en la especificación técnica ASGI, esto es parte del la documentación de `Lifespan` de Starlette.
+Puedes leer más sobre los manejadores `lifespan` de Starlette en la documentación de `Lifespan` de Starlette.
Incluyendo cómo manejar el estado de lifespan que puede ser usado en otras áreas de tu código.
///
-## 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 b664bceac..daf6cefed 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
+
+FastAPI genera automáticamente especificaciones **OpenAPI 3.1**, así que cualquier herramienta que uses debe soportar esta versión.
+
+///
+
+## 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.
-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**.
+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**.
-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. 🙇
+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
-
-Ahora que tenemos la aplicación con los modelos, podemos generar el código del cliente para el frontend.
-
-#### 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
-
-Ahora como el resultado final está en un archivo `openapi.json`, modificarías el `package.json` para usar ese archivo local, por ejemplo:
-
-```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"
- }
-}
+### Genera un Cliente TypeScript con el OpenAPI preprocesado { #generate-a-typescript-client-with-the-preprocessed-openapi }
+
+Como el resultado final ahora está en un archivo `openapi.json`, necesitas actualizar la ubicación de la entrada:
+
+```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 0626a1563..f3f4bb85c 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 b8fd86185..7eead8ae1 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.
@@ -93,4 +94,4 @@ Por ejemplo:
* `ProxyHeadersMiddleware` de Uvicorn
* MessagePack
-Para ver otros middlewares disponibles, revisa la documentación de Middleware de Starlette y la Lista ASGI Awesome.
+Para ver otros middlewares disponibles, revisa la documentación de Middleware de Starlette y la Lista ASGI Awesome.
diff --git a/docs/es/docs/advanced/openapi-callbacks.md b/docs/es/docs/advanced/openapi-callbacks.md
index 60d5cb3cc..caaa70fa8 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 01235b3ab..c358a1400 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 2b20819aa..ea58a300a 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 e0889c474..940f1dd3f 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 c4472eaa1..550a5d97a 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
@@ -48,4 +48,4 @@ Y como el `Response` se puede usar frecuentemente para establecer headers y cook
///
-Para ver todos los parámetros y opciones disponibles, revisa la documentación en Starlette.
+Para ver todos los parámetros y opciones disponibles, revisa la documentación en Starlette.
diff --git a/docs/es/docs/advanced/response-directly.md b/docs/es/docs/advanced/response-directly.md
index 8594011d6..2da4e84e7 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.
+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 629e6c50a..440c081e0 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 e4ccb5978..8b3e67fac 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 a8d2f20b6..4e4580fde 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 7e591cc01..a2d749103 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 - - ╭─ Python module file ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ - -INFO Importing module main -INFO Found importable FastAPI app - - ╭─ Importable FastAPI app ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ - -INFO Using import string main:app - - ╭─────────── 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 │ - │ │ - ╰─────────────────────────────────────────────────────╯ - -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. -+$ fastapi run --workers 4 main.py + + FastAPI Starting production server 🚀 + + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with the + following code: + + from main import app + + 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 0a03ff330..acd3f8d6d 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 0b479bf00..ff13196f8 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
@@ -66,7 +66,7 @@ El `dict` `scope` y la función `receive` son ambos parte de la especificación
Y esas dos cosas, `scope` y `receive`, son lo que se necesita para crear una nueva *Request instance*.
-Para aprender más sobre el `Request`, revisa la documentación de Starlette sobre Requests.
+Para aprender más sobre el `Request`, revisa la documentación de Starlette sobre Requests.
///
@@ -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 3dbdd666b..2611b6e1b 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 e10621ce5..3a3dc8294 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 52f163809..e50c1ae0a 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 152499af8..6f5988049 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 000000000..c862ace90
--- /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 b77915851..903313599 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 }
fastapi dev main.py...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`.
+* 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[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 cc6c7cc3f..4333bfcf6 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 559995151..b4aa11d0d 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 769204f8f..60b50a08f 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+
-
-En Python 3.9, `Annotated` es parte de la librería estándar, así que puedes importarlo desde `typing`.
+Desde Python 3.9, `Annotated` es parte de la standard library, 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 92898d319..324009561 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 3fe961e41..cc8a2c9cb 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,9 +59,9 @@ 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`.
+La clase `BackgroundTasks` proviene directamente de `starlette.background`.
Se importa/incluye directamente en FastAPI para que puedas importarla desde `fastapi` y evitar importar accidentalmente la alternativa `BackgroundTask` (sin la `s` al final) de `starlette.background`.
@@ -69,9 +69,9 @@ Al usar solo `BackgroundTasks` (y no `BackgroundTask`), es posible usarla como u
Todavía es posible usar `BackgroundTask` solo en FastAPI, pero debes crear el objeto en tu código y devolver una `Response` de Starlette incluyéndolo.
-Puedes ver más detalles en la documentación oficial de Starlette sobre Background Tasks.
+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 c3d8f0686..7938a1215 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:
-
-//// 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 | Consejo
-
-Preferiblemente usa la versión `Annotated` si es posible.
+Ahora utilizaremos una dependencia simple para leer un header `X-Token` personalizado:
-///
-
-```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 | 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.
+
+### Importar `FastAPI` { #import-fastapi }
-Importas y creas una clase `FastAPI` como de costumbre.
+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 d07d214ec..8902ce4e9 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 df6560b62..57cec1674 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 5b4cfc14c..0dfd6576f 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 26cd3345f..e75e29b54 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 6d0aa7c60..dde39f78c 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 ebdb59265..dab7d8c0a 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 45b113ff9..598872c0a 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 e493d4431..c1a23295e 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 2a7544e83..c31daf40f 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 fbe17c67a..60baa93a9 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 94290443a..aa645daa4 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
+```
-///
+## Dependencias con `yield`, `HTTPException`, `except` y Tareas en Background { #dependencies-with-yield-httpexception-except-and-background-tasks }
-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`.
+Las dependencias con `yield` han evolucionado con el tiempo para cubrir diferentes casos de uso y corregir algunos problemas.
-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.
+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}.
-## 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 bba532207..e74d65d7e 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 28775780f..e876921ba 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 0380b9690..d72c73e24 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
-
- ╭─ Python module file ─╮
- │ │
- │ 🐍 main.py │
- │ │
- ╰──────────────────────╯
-
-INFO Importing module main
-INFO Found importable FastAPI app
-
- ╭─ Importable FastAPI app ─╮
- │ │
- │ from main import app │
- │ │
- ╰──────────────────────────╯
-
-INFO Using import string main:app
-
- ╭────────── 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 │
- │ │
- ╰─────────────────────────────────────────────────────╯
-
-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.
+$ fastapi dev main.py
+
+ FastAPI Starting development server 🚀
+
+ Searching for package file structure from directories
+ with __init__.py files
+ Importing from /home/user/code/awesomeapp
+
+ module 🐍 main.py
+
+ code Importing the FastAPI app object from the module with
+ the following code:
+
+ from main import app
+
+ 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 9c0112bb2..12b4d1002 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 dcfc6cdfb..7804b6854 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 296374525..de636a485 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,13 +31,13 @@ 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.
+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 4ea01753b..569dd03dd 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 426280902..7ba49f3b0 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 9cb76156f..4af4782f8 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)
+q: str | None = 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 9bd47f871..2b58a2b4b 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 330523c7e..cc99deb2e 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 9d5d7495a..1f4668c84 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 51dfbb357..363553e86 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 a9d62e660..33061e6a1 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 09682f51b..8cfe69e78 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 75309ce1f..000000000
--- 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 06aa7f00e..000000000
--- 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 c428acbf7..000000000
--- 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 75b1c4c5c..000000000
--- 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 c0827a8b3..000000000
--- 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 de18856f4..000000000
--- a/docs/fa/mkdocs.yml
+++ /dev/null
@@ -1 +0,0 @@
-INHERIT: ../en/mkdocs.yml
diff --git a/docs/fr/docs/alternatives.md b/docs/fr/docs/alternatives.md
index 4d6037910..9d8d85705 100644
--- a/docs/fr/docs/alternatives.md
+++ b/docs/fr/docs/alternatives.md
@@ -452,7 +452,7 @@ Gérer toute la validation des données, leur sérialisation et la documentation
///
-### Starlette
+### Starlette
Starlette est un framework/toolkit léger ASGI, qui est idéal pour construire des services asyncio performants.
@@ -498,7 +498,7 @@ Ainsi, tout ce que vous pouvez faire avec Starlette, vous pouvez le faire direct
///
-### Uvicorn
+### Uvicorn
Uvicorn est un serveur ASGI rapide comme l'éclair, basé sur uvloop et httptools.
diff --git a/docs/fr/docs/deployment/manually.md b/docs/fr/docs/deployment/manually.md
index 7c29242a9..3f99ccd9f 100644
--- a/docs/fr/docs/deployment/manually.md
+++ b/docs/fr/docs/deployment/manually.md
@@ -4,7 +4,7 @@ La principale chose dont vous avez besoin pour exécuter une application **FastA
Il existe 3 principales alternatives :
-* Uvicorn : un serveur ASGI haute performance.
+* Uvicorn : un serveur ASGI haute performance.
* Hypercorn : un serveur
ASGI compatible avec HTTP/2 et Trio entre autres fonctionnalités.
* Daphne : le serveur ASGI
@@ -27,7 +27,7 @@ Vous pouvez installer un serveur compatible ASGI avec :
//// tab | Uvicorn
-* Uvicorn, un serveur ASGI rapide comme l'éclair, basé sur uvloop et httptools.
+* Uvicorn, un serveur ASGI rapide comme l'éclair, basé sur uvloop et httptools.
uvicorn - Pour le serveur qui charge et sert votre application.
+* uvicorn - Pour le serveur qui charge et sert votre application.
* orjson - Obligatoire si vous voulez utiliser `ORJSONResponse`.
* ujson - Obligatoire si vous souhaitez utiliser `UJSONResponse`.
diff --git a/docs/fr/docs/tutorial/background-tasks.md b/docs/fr/docs/tutorial/background-tasks.md
index 2065ca58e..6efd16e07 100644
--- a/docs/fr/docs/tutorial/background-tasks.md
+++ b/docs/fr/docs/tutorial/background-tasks.md
@@ -61,7 +61,7 @@ Et ensuite une autre tâche d'arrière-plan (générée dans les paramètres de
## Détails techniques
-La classe `BackgroundTasks` provient directement de `starlette.background`.
+La classe `BackgroundTasks` provient directement de `starlette.background`.
Elle est importée/incluse directement dans **FastAPI** pour que vous puissiez l'importer depuis `fastapi` et éviter d'importer accidentellement `BackgroundTask` (sans `s` à la fin) depuis `starlette.background`.
@@ -69,7 +69,7 @@ En utilisant seulement `BackgroundTasks` (et non `BackgroundTask`), il est possi
Il est tout de même possible d'utiliser `BackgroundTask` seul dans **FastAPI**, mais dans ce cas il faut créer l'objet dans le code et renvoyer une `Response` Starlette l'incluant.
-Plus de détails sont disponibles dans la documentation officielle de Starlette sur les tâches d'arrière-plan (via leurs classes `BackgroundTasks`et `BackgroundTask`).
+Plus de détails sont disponibles dans la documentation officielle de Starlette sur les tâches d'arrière-plan (via leurs classes `BackgroundTasks`et `BackgroundTask`).
## Avertissement
diff --git a/docs/fr/docs/tutorial/first-steps.md b/docs/fr/docs/tutorial/first-steps.md
index 758145362..96ea56e62 100644
--- a/docs/fr/docs/tutorial/first-steps.md
+++ b/docs/fr/docs/tutorial/first-steps.md
@@ -140,7 +140,7 @@ Vous pourriez aussi l'utiliser pour générer du code automatiquement, pour les
`FastAPI` est une classe héritant directement de `Starlette`.
-Vous pouvez donc aussi utiliser toutes les fonctionnalités de Starlette depuis `FastAPI`.
+Vous pouvez donc aussi utiliser toutes les fonctionnalités de Starlette depuis `FastAPI`.
///
diff --git a/docs/fr/llm-prompt.md b/docs/fr/llm-prompt.md
new file mode 100644
index 000000000..228500fe2
--- /dev/null
+++ b/docs/fr/llm-prompt.md
@@ -0,0 +1,132 @@
+### 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`).
+
+Additionally, in instructional sentences, prefer the present tense for obligations:
+
+- Prefer `vous devez …` over `vous devrez …`, unless the English source explicitly refers to a future requirement.
+
+- When translating “make sure (that) … is …”, prefer the indicative after `vous assurer que` (e.g. `Vous devez vous assurer qu'il est …`) instead of the subjunctive (e.g. `qu'il soit …`).
+
+### Quotes
+
+- Convert neutral double quotes (`"`) to French guillemets (`«` and `»`).
+
+- 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
+
+- 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 ...
+```
+
+- This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there.
+
+### Headings
+
+- Prefer translating headings using the infinitive form (as is common in the existing French docs): `Créer…`, `Utiliser…`, `Ajouter…`.
+
+- 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 …`).
+
+### French instructions about technical terms
+
+Do not try to translate everything. In particular, keep common programming terms (e.g. `framework`, `endpoint`, `plug-in`, `payload`).
+
+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 | Alertes
+- /// check: /// check | Vérifications
+- /// info: /// info
+
+- the docs: les documents
+- the documentation: la documentation
+
+- Exclude from OpenAPI: Exclusion d'OpenAPI
+
+- 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: chemin d'accès
+- path operations (plural): chemins d'accès
+- path operation function: fonction de chemin d'accès
+- path operation decorator: décorateur de chemin d'accès
+
+- 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/he/docs/index.md b/docs/he/docs/index.md
deleted file mode 100644
index 14ea82f5e..000000000
--- a/docs/he/docs/index.md
+++ /dev/null
@@ -1,469 +0,0 @@
-# FastAPI
-
-
-
-
-- תשתית FastAPI, ביצועים גבוהים, קלה ללמידה, מהירה לתכנות, מוכנה לסביבת ייצור -
- - ---- - -**תיעוד**: https://fastapi.tiangolo.com - -**קוד**: https://github.com/fastapi/fastapi - ---- - -FastAPI היא תשתית רשת מודרנית ומהירה (ביצועים גבוהים) לבניית ממשקי תכנות יישומים (API) עם פייתון 3.6+ בהתבסס על רמזי טיפוסים סטנדרטיים. - -תכונות המפתח הן: - -- **מהירה**: ביצועים גבוהים מאוד, בקנה אחד עם NodeJS ו - Go (תודות ל - Starlette ו - Pydantic). [אחת מתשתיות הפייתון המהירות ביותר](#_14). - -- **מהירה לתכנות**: הגבירו את מהירות פיתוח התכונות החדשות בכ - %200 עד %300. \* -- **פחות שגיאות**: מנעו כ - %40 משגיאות אנוש (מפתחים). \* -- **אינטואיטיבית**: תמיכת עורך מעולה. השלמה בכל מקום. פחות זמן ניפוי שגיאות. -- **קלה**: מתוכננת להיות קלה לשימוש וללמידה. פחות זמן קריאת תיעוד. -- **קצרה**: מזערו שכפול קוד. מספר תכונות מכל הכרזת פרמטר. פחות שגיאות. -- **חסונה**: קבלו קוד מוכן לסביבת ייצור. עם תיעוד אינטרקטיבי אוטומטי. -- **מבוססת סטנדרטים**: מבוססת על (ותואמת לחלוטין ל -) הסטדנרטים הפתוחים לממשקי תכנות יישומים: OpenAPI (ידועים לשעבר כ - Swagger) ו - JSON Schema. - -\* הערכה מבוססת על בדיקות של צוות פיתוח פנימי שבונה אפליקציות בסביבת ייצור. - -## נותני חסות - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} -async def...uvicorn main:app --reload...app = FastAPI().
-- --reload: גרמו לשרת להתאתחל לאחר שינויים בקוד. עשו זאת רק בסביבת פיתוח.
-
-/items/{item_id}.
-- שני ה _נתיבים_ מקבלים _בקשות_ `GET` (ידועות גם כ*מתודות* HTTP).
-- ה _נתיב_ /items/{item_id} כולל \*פרמטר נתיב\_ `item_id` שאמור להיות `int`.
-- ה _נתיב_ /items/{item_id} \*פרמטר שאילתא\_ אופציונלי `q`.
-
-### תיעוד API אינטרקטיבי
-
-כעת פנו לכתובת http://127.0.0.1:8000/docs.
-
-אתם תראו את התיעוד האוטומטי (מסופק על ידי Swagger UI):
-
-
-
-### תיעוד אלטרנטיבי
-
-כעת פנו לכתובת http://127.0.0.1:8000/redoc.
-
-אתם תראו תיעוד אלטרנטיבי (מסופק על ידי ReDoc):
-
-
-
-## שדרוג לדוגמא
-
-כעת ערכו את הקובץ `main.py` כך שיוכל לקבל גוף מבקשת `PUT`.
-
-הגדירו את הגוף בעזרת רמזי טיפוסים סטנדרטיים, הודות ל - `Pydantic`.
-
-```Python hl_lines="4 9-12 25-27"
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- price: float
- is_offer: Union[bool, None] = None
-
-
-@app.get("/")
-def read_root():
- return {"Hello": "World"}
-
-
-@app.get("/items/{item_id}")
-def read_item(item_id: int, q: Union[str, None] = None):
- return {"item_id": item_id, "q": q}
-
-
-@app.put("/items/{item_id}")
-def update_item(item_id: int, item: Item):
- return {"item_name": item.name, "item_id": item_id}
-```
-
-השרת אמול להתאתחל אוטומטית (מאחר והוספתם --reload לפקודת `uvicorn` שלמעלה).
-
-### שדרוג התיעוד האינטרקטיבי
-
-כעת פנו לכתובת http://127.0.0.1:8000/docs.
-
-- התיעוד האוטומטי יתעדכן, כולל הגוף החדש:
-
-
-
-- לחצו על הכפתור "Try it out", הוא יאפשר לכם למלא את הפרמטרים ולעבוד ישירות מול ה - API.
-
-
-
-- אחר כך לחצו על הכפתור "Execute", האתר יתקשר עם ה - API שלכם, ישלח את הפרמטרים, ישיג את התוצאות ואז יראה אותן על המסך:
-
-
-
-### שדרוג התיעוד האלטרנטיבי
-
-כעת פנו לכתובת http://127.0.0.1:8000/redoc.
-
-- התיעוד האלטרנטיבי גם יראה את פרמטר השאילתא והגוף החדשים.
-
-
-
-### סיכום
-
-לסיכום, אתם מכריזים ** פעם אחת** על טיפוסי הפרמטרים, גוף וכו' כפרמטרים לפונקציה.
-
-אתם עושים את זה עם טיפוסי פייתון מודרניים.
-
-אתם לא צריכים ללמוד תחביר חדש, מתודות או מחלקות של ספרייה ספיציפית, וכו'
-
-רק **פייתון 3.6+** סטנדרטי.
-
-לדוגמא, ל - `int`:
-
-```Python
-item_id: int
-```
-
-או למודל `Item` מורכב יותר:
-
-```Python
-item: Item
-```
-
-...ועם הכרזת הטיפוס האחת הזו אתם מקבלים:
-
-- תמיכת עורך, כולל:
- - השלמות.
- - בדיקת טיפוסים.
-- אימות מידע:
- - שגיאות ברורות ואטומטיות כאשר מוכנס מידע לא חוקי .
- - אימות אפילו לאובייקטי JSON מקוננים.
-- המרה של מידע קלט: המרה של מידע שמגיע מהרשת למידע וטיפוסים של פייתון. קורא מ:
- - JSON.
- - פרמטרי נתיב.
- - פרמטרי שאילתא.
- - עוגיות.
- - כותרות.
- - טפסים.
- - קבצים.
-- המרה של מידע פלט: המרה של מידע וטיפוסים מפייתון למידע רשת (כ - JSON):
- - המירו טיפוסי פייתון (`str`, `int`, `float`, `bool`, `list`, etc).
- - עצמי `datetime`.
- - עצמי `UUID`.
- - מודלי בסיסי נתונים.
- - ...ורבים אחרים.
-- תיעוד API אוטומטי ואינטרקטיבית כולל שתי אלטרנטיבות לממשק המשתמש:
- - Swagger UI.
- - ReDoc.
-
----
-
-בחזרה לדוגמאת הקוד הקודמת, **FastAPI** ידאג:
-
-- לאמת שיש `item_id` בנתיב בבקשות `GET` ו - `PUT`.
-- לאמת שה - `item_id` הוא מטיפוס `int` בבקשות `GET` ו - `PUT`.
- - אם הוא לא, הלקוח יראה שגיאה ברורה ושימושית.
-- לבדוק האם קיים פרמטר שאילתא בשם `q` (קרי `http://127.0.0.1:8000/items/foo?q=somequery`) לבקשות `GET`.
- - מאחר והפרמטר `q` מוגדר עם = None, הוא אופציונלי.
- - לולא ה - `None` הוא היה חובה (כמו הגוף במקרה של `PUT`).
-- לבקשות `PUT` לנתיב /items/{item_id}, לקרוא את גוף הבקשה כ - JSON:
- - לאמת שהוא כולל את מאפיין החובה `name` שאמור להיות מטיפוס `str`.
- - לאמת שהוא כולל את מאפיין החובה `price` שחייב להיות מטיפוס `float`.
- - לבדוק האם הוא כולל את מאפיין הרשות `is_offer` שאמור להיות מטיפוס `bool`, אם הוא נמצא.
- - כל זה יעבוד גם לאובייקט JSON מקונן.
-- להמיר מ - JSON ול- JSON אוטומטית.
-- לתעד הכל באמצעות OpenAPI, תיעוד שבו יוכלו להשתמש:
- - מערכות תיעוד אינטרקטיביות.
- - מערכות ייצור קוד אוטומטיות, להרבה שפות.
-- לספק ישירות שתי מערכות תיעוד רשתיות.
-
----
-
-רק גרדנו את קצה הקרחון, אבל כבר יש לכם רעיון של איך הכל עובד.
-
-נסו לשנות את השורה:
-
-```Python
- return {"item_name": item.name, "item_id": item_id}
-```
-
-...מ:
-
-```Python
- ... "item_name": item.name ...
-```
-
-...ל:
-
-```Python
- ... "item_price": item.price ...
-```
-
-...וראו איך העורך שלכם משלים את המאפיינים ויודע את הטיפוסים שלהם:
-
-
-
-לדוגמא יותר שלמה שכוללת עוד תכונות, ראו את המדריך - למשתמש.
-
-**התראת ספוילרים**: המדריך - למשתמש כולל:
-
-- הכרזה על **פרמטרים** ממקורות אחרים ושונים כגון: **כותרות**, **עוגיות**, **טפסים** ו - **קבצים**.
-- איך לקבוע **מגבלות אימות** בעזרת `maximum_length` או `regex`.
-- דרך חזקה וקלה להשתמש ב**הזרקת תלויות**.
-- אבטחה והתאמתות, כולל תמיכה ב - **OAuth2** עם **JWT** והתאמתות **HTTP Basic**.
-- טכניקות מתקדמות (אבל קלות באותה מידה) להכרזת אובייקטי JSON מקוננים (תודות ל - Pydantic).
-- אינטרקציה עם **GraphQL** דרך Strawberry וספריות אחרות.
-- תכונות נוספות רבות (תודות ל - Starlette) כגון:
- - **WebSockets**
- - בדיקות קלות במיוחד מבוססות על `requests` ו - `pytest`
- - **CORS**
- - **Cookie Sessions**
- - ...ועוד.
-
-## ביצועים
-
-בדיקות עצמאיות של TechEmpower הראו שאפליקציות **FastAPI** שרצות תחת Uvicorn הן מתשתיות הפייתון המהירות ביותר, רק מתחת ל - Starlette ו - Uvicorn עצמן (ש - FastAPI מבוססת עליהן). (\*)
-
-כדי להבין עוד על הנושא, ראו את הפרק Benchmarks.
-
-## תלויות אופציונליות
-
-בשימוש Pydantic:
-
-- email-validator - לאימות כתובות אימייל.
-
-בשימוש Starlette:
-
-- httpx - דרוש אם ברצונכם להשתמש ב - `TestClient`.
-- jinja2 - דרוש אם ברצונכם להשתמש בברירת המחדל של תצורת הטמפלייטים.
-- python-multipart - דרוש אם ברצונכם לתמוך ב "פרסור" טפסים, באצמעות request.form().
-- itsdangerous - דרוש אם ברצונכם להשתמש ב - `SessionMiddleware`.
-- pyyaml - דרוש אם ברצונכם להשתמש ב - `SchemaGenerator` של Starlette (כנראה שאתם לא צריכים את זה עם FastAPI).
-
-בשימוש FastAPI / Starlette:
-
-- uvicorn - לשרת שטוען ומגיש את האפליקציה שלכם.
-- orjson - דרוש אם ברצונכם להשתמש ב - `ORJSONResponse`.
-- ujson - דרוש אם ברצונכם להשתמש ב - `UJSONResponse`.
-
-תוכלו להתקין את כל אלו באמצעות pip install "fastapi[all]".
-
-## רשיון
-
-הפרויקט הזה הוא תחת התנאים של רשיון MIT.
diff --git a/docs/he/mkdocs.yml b/docs/he/mkdocs.yml
deleted file mode 100644
index de18856f4..000000000
--- a/docs/he/mkdocs.yml
+++ /dev/null
@@ -1 +0,0 @@
-INHERIT: ../en/mkdocs.yml
diff --git a/docs/hu/docs/index.md b/docs/hu/docs/index.md
deleted file mode 100644
index f60b6b349..000000000
--- a/docs/hu/docs/index.md
+++ /dev/null
@@ -1,467 +0,0 @@
-
-- FastAPI keretrendszer, nagy teljesítmény, könnyen tanulható, gyorsan kódolható, productionre kész -
- - ---- - -**Dokumentáció**: https://fastapi.tiangolo.com - -**Forrás kód**: https://github.com/fastapi/fastapi - ---- -A FastAPI egy modern, gyors (nagy teljesítményű), webes keretrendszer API-ok építéséhez Python -al, a Python szabványos típusjelöléseire építve. - - -Kulcs funkciók: - -* **Gyors**: Nagyon nagy teljesítmény, a **NodeJS**-el és a **Go**-val egyenrangú (a Starlettenek és a Pydantic-nek köszönhetően). [Az egyik leggyorsabb Python keretrendszer](#performance). -* **Gyorsan kódolható**: A funkciók fejlesztési sebességét 200-300 százalékkal megnöveli. * -* **Kevesebb hiba**: Körülbelül 40%-al csökkenti az emberi (fejlesztői) hibák számát. * -* **Intuitív**: Kiváló szerkesztő támogatás. Kiegészítés mindenhol. Kevesebb hibakereséssel töltött idő. -* **Egyszerű**: Egyszerű tanulásra és használatra tervezve. Kevesebb dokumentáció olvasással töltött idő. -* **Rövid**: Kód duplikáció minimalizálása. Több funkció minden paraméter deklarálásával. Kevesebb hiba. -* **Robosztus**: Production ready kód. Automatikus interaktív dokumentáció val. -* **Szabvány alapú**: Az API-ok nyílt szabványaira alapuló (és azokkal teljesen kompatibilis): OpenAPI (korábban Swagger néven ismert) és a JSON Schema. - -* Egy production alkalmazásokat építő belső fejlesztői csapat tesztjein alapuló becslés. - -## Szponzorok - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} -async def-et...uvicorn main:app --reload...email-validator - e-mail validációkra.
-* pydantic-settings - Beállítások követésére.
-* pydantic-extra-types - Extra típusok Pydantic-hoz.
-
-Starlette által használt:
-
-* httpx - Követelmény ha a `TestClient`-et akarod használni.
-* jinja2 - Követelmény ha az alap template konfigurációt akarod használni.
-* python-multipart - Követelmény ha "parsing"-ot akarsz támogatni, `request.form()`-al.
-* itsdangerous - Követelmény `SessionMiddleware` támogatáshoz.
-* pyyaml - Követelmény a Starlette `SchemaGenerator`-ának támogatásához (valószínűleg erre nincs szükség FastAPI használása esetén).
-
-FastAPI / Starlette által használt
-
-* uvicorn - Szerverekhez amíg betöltik és szolgáltatják az applikációdat.
-* orjson - Követelmény ha `ORJSONResponse`-t akarsz használni.
-* ujson - Követelmény ha `UJSONResponse`-t akarsz használni.
-
-Ezeket mind telepítheted a `pip install "fastapi[all]"` paranccsal.
-
-## Licensz
-Ez a projekt az MIT license, licensz alatt fut
diff --git a/docs/hu/mkdocs.yml b/docs/hu/mkdocs.yml
deleted file mode 100644
index de18856f4..000000000
--- a/docs/hu/mkdocs.yml
+++ /dev/null
@@ -1 +0,0 @@
-INHERIT: ../en/mkdocs.yml
diff --git a/docs/id/docs/index.md b/docs/id/docs/index.md
deleted file mode 100644
index b29d42dfd..000000000
--- a/docs/id/docs/index.md
+++ /dev/null
@@ -1,495 +0,0 @@
-# FastAPI
-
-
-
-
-- FastAPI, framework performa tinggi, mudah dipelajari, cepat untuk coding, siap untuk pengembangan -
- - ---- - -**Dokumentasi**: https://fastapi.tiangolo.com - -**Kode Sumber**: https://github.com/fastapi/fastapi - ---- - -FastAPI adalah *framework* *web* moderen, cepat (performa-tinggi) untuk membangun API dengan Python berdasarkan tipe petunjuk Python. - -Fitur utama FastAPI: - -* **Cepat**: Performa sangat tinggi, setara **NodeJS** dan **Go** (berkat Starlette dan Pydantic). [Salah satu *framework* Python tercepat yang ada](#performa). -* **Cepat untuk coding**: Meningkatkan kecepatan pengembangan fitur dari 200% sampai 300%. * -* **Sedikit bug**: Mengurangi hingga 40% kesalahan dari manusia (pemrogram). * -* **Intuitif**: Dukungan editor hebat. Penyelesaian di mana pun. Lebih sedikit *debugging*. -* **Mudah**: Dibuat mudah digunakan dan dipelajari. Sedikit waktu membaca dokumentasi. -* **Ringkas**: Mengurasi duplikasi kode. Beragam fitur dari setiap deklarasi parameter. Lebih sedikit *bug*. -* **Handal**: Dapatkan kode siap-digunakan. Dengan dokumentasi otomatis interaktif. -* **Standar-resmi**: Berdasarkan (kompatibel dengan ) standar umum untuk API: OpenAPI (sebelumnya disebut Swagger) dan JSON Schema. - -* estimasi berdasarkan pengujian tim internal pengembangan applikasi siap pakai. - -## Sponsor - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} -async def...fastapi dev main.py...email-validator - untuk validasi email.
-
-Digunakan oleh Starlette:
-
-* httpx - Dibutuhkan jika anda menggunakan `TestClient`.
-* jinja2 - Dibutuhkan jika anda menggunakan konfigurasi template bawaan.
-* python-multipart - Dibutuhkan jika anda menggunakan form dukungan "parsing", dengan `request.form()`.
-
-Digunakan oleh FastAPI / Starlette:
-
-* uvicorn - untuk server yang memuat dan melayani aplikasi anda. Termasuk `uvicorn[standard]`, yang memasukan sejumlah dependensi (misal `uvloop`) untuk needed melayani dengan performa tinggi.
-* `fastapi-cli` - untuk menyediakan perintah `fastapi`.
-
-### Tanpda dependensi `standard`
-
-Jika anda tidak ingin menambahkan dependensi opsional `standard`, anda dapat menggunakan `pip install fastapi` daripada `pip install "fastapi[standard]"`.
-
-### Dependensi Opsional Tambahan
-
-Ada beberapa dependensi opsional yang bisa anda install.
-
-Dependensi opsional tambahan Pydantic:
-
-* pydantic-settings - untuk manajemen setting.
-* pydantic-extra-types - untuk tipe tambahan yang digunakan dengan Pydantic.
-
-Dependensi tambahan opsional FastAPI:
-
-* orjson - Diperlukan jika anda akan menggunakan`ORJSONResponse`.
-* ujson - Diperlukan jika anda akan menggunakan `UJSONResponse`.
-
-## Lisensi
-
-Project terlisensi dengan lisensi MIT.
diff --git a/docs/id/docs/tutorial/first-steps.md b/docs/id/docs/tutorial/first-steps.md
deleted file mode 100644
index 9b461507d..000000000
--- a/docs/id/docs/tutorial/first-steps.md
+++ /dev/null
@@ -1,332 +0,0 @@
-# Langkah Pertama
-
-File FastAPI yang paling sederhana bisa seperti berikut:
-
-{* ../../docs_src/first_steps/tutorial001.py *}
-
-Salin file tersebut ke `main.py`.
-
-Jalankan di server:
-
-get
-
-/// info | `@decorator` Info
-
-Sintaksis `@sesuatu` di Python disebut "dekorator".
-
-Dekorator ditempatkan di atas fungsi. Seperti sebuah topi cantik (Saya pikir istilah ini berasal dari situ).
-
-"dekorator" memanggil dan bekerja dengan fungsi yang ada di bawahnya
-
-Pada kondisi ini, dekorator ini memberi tahu **FastAPI** bahwa fungsi di bawah nya berhubungan dengan **path** `/` dengan **operasi** `get`.
-
-Sehingga disebut **dekorator operasi path**.
-
-///
-
-Operasi lainnya yang bisa digunakan:
-
-* `@app.post()`
-* `@app.put()`
-* `@app.delete()`
-
-Dan operasi unik lainnya:
-
-* `@app.options()`
-* `@app.head()`
-* `@app.patch()`
-* `@app.trace()`
-
-/// tip | Tips
-
-Jika anda bisa menggunakan operasi apa saja (metode HTTP).
-
-**FastAPI** tidak mengharuskan anda menggunakan operasi tertentu.
-
-Informasi di sini hanyalah sebagai panduan, bukan keharusan.
-
-Sebagai contoh, ketika menggunakan GraphQL, semua operasi umumnya hanya menggunakan `POST`.
-
-///
-
-### Langkah 4: mendefinisikan **fungsi operasi path**
-
-Ini "**fungsi operasi path**" kita:
-
-* **path**: adalah `/`.
-* **operasi**: adalah `get`.
-* **fungsi**: adalah fungsi yang ada di bawah dekorator (di bawah `@app.get("/")`).
-
-{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
-
-Ini adalah fungsi Python.
-
-Fungsi ini dipanggil **FastAPI** setiap kali menerima request ke URL "`/`" dengan operasi `GET`.
-
-Di kondisi ini, ini adalah sebuah fungsi `async`.
-
----
-
-Anda bisa mendefinisikan fungsi ini sebagai fungsi normal daripada `async def`:
-
-{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
-
-/// note | Catatan
-
-Jika anda tidak tahu perbedaannya, kunjungi [Async: *"Panduan cepat"*](../async.md#in-a-hurry){.internal-link target=_blank}.
-
-///
-
-### Langkah 5: hasilkan konten
-
-{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
-
-Anda bisa menghasilkan `dict`, `list`, nilai singular seperti `str`, `int`, dll.
-
-Anda juga bisa menghasilkan model Pydantic (anda akan belajar mengenai ini nanti).
-
-Ada banyak objek dan model yang secara otomatis dikonversi ke JSON (termasuk ORM, dll). Anda bisa menggunakan yang anda suka, kemungkinan sudah didukung.
-
-## Ringkasan
-
-* Impor `FastAPI`.
-* Buat sebuah instance `app`.
-* Tulis **dekorator operasi path** menggunakan dekorator seperti `@app.get("/")`.
-* Definisikan **fungsi operasi path**; sebagai contoh, `def root(): ...`.
-* Jalankan server development dengan perintah `fastapi dev`.
diff --git a/docs/id/docs/tutorial/index.md b/docs/id/docs/tutorial/index.md
deleted file mode 100644
index c01ec9a89..000000000
--- a/docs/id/docs/tutorial/index.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# Tutorial - Pedoman Pengguna - Pengenalan
-
-Tutorial ini menunjukan cara menggunakan ***FastAPI*** dengan semua fitur-fiturnya, tahap demi tahap.
-
-Setiap bagian dibangun secara bertahap dari bagian sebelumnya, tetapi terstruktur untuk memisahkan banyak topik, sehingga kamu bisa secara langsung menuju ke topik spesifik untuk menyelesaikan kebutuhan API tertentu.
-
-Ini juga dibangun untuk digunakan sebagai referensi yang akan datang.
-
-Sehingga kamu dapat kembali lagi dan mencari apa yang kamu butuhkan dengan tepat.
-
-## Jalankan kode
-
-Semua blok-blok kode dapat disalin dan digunakan langsung (Mereka semua sebenarnya adalah file python yang sudah teruji).
-
-Untuk menjalankan setiap contoh, salin kode ke file `main.py`, dan jalankan `uvicorn` dengan:
-
-
-
-/// check | Periksa
-
-Dengan deklarasi tipe data Python yang sama, **FastAPI** membuat dokumentasi interaktif otomatis (terintegrasi Swagger UI).
-
-Perhatikan parameter path dideklarasikan sebagai integer.
-
-///
-
-## Keuntungan basis-standar, dokumentasi alternatif
-
-Karena skema yang dibuat berasal dari standar OpenAPI, maka banyak alat lain yang kompatibel.
-
-Sehingga **FastAPI** menyediakan dokumentasi alternatif (menggunakan ReDoc), yang bisa diakses di http://127.0.0.1:8000/redoc:
-
-
-
-Cara yang sama untuk menggunakan tools kompatibel lainnya. Termasuk alat membuat kode otomatis untuk banyak bahasa.
-
-## Pydantic
-
-Semua validasi data dikerjakan di belakang layar oleh Pydantic, sehingga anda mendapatkan banyak kemudahan. Anda juga tahu proses ini akan ditangani dengan baik.
-
-Anda bisa mendeklarasikan tipe data dengan `str`, `float`, `bool` dan banyak tipe data kompleks lainnya.
-
-Beberapa tipe di atas akan dibahas pada bab berikutnya tutorial ini.
-
-## Urutan berpengaruh
-
-Ketika membuat *operasi path*, anda bisa menghadapi kondisi dimana *path* nya sudah tetap.
-
-Seperti `/users/me`, untuk mendapatkan data user yang sedang aktif.
-
-Kemudian anda bisa memiliki path `/users/{user_id}` untuk mendapatkan data user tertentu melalui user ID.
-
-karena *operasi path* dievaluasi melalui urutan, anda harus memastikan path untuk `/users/me` dideklarasikan sebelum `/user/{user_id}`:
-
-{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
-
-Sebaliknya, path `/users/{user_id}` juga akan sesuai dengan `/users/me`, "menganggap" menerima parameter `user_id` dengan nilai `"me"`.
-
-Serupa, anda juga tidak bisa mendefinisikan operasi path:
-
-{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *}
-
-Path pertama akan selalu digunakan karena path sesuai dengan yang pertama.
-
-## Nilai terdefinisi
-
-Jika ada *operasi path* yang menerima *parameter path*, tetapi anda ingin nilai valid *parameter path* sudah terdefinisi, anda bisa menggunakan standar Python `Enum`.
-
-### Membuat class `Enum`
-
-Import `Enum` dan buat *sub-class* warisan dari `str` dan `Enum`.
-
-Dengan warisan dari `str` dokumen API mengetahui nilai nya harus berjenis `string` supaya bisa digunakan dengan benar.
-
-Kemudian buat atribut *class* dengan nilai tetap *string* yang benar:
-
-{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
-
-/// info
-
-Enumerasi (atau enum) tersedia di Python sejak versi 3.4.
-
-///
-
-/// tip | Tips
-
-"AlxexNet", "ResNet", dan "LeNet" adalah nama model *Machine Learning*.
-
-///
-
-### Mendeklarasikan *parameter path*
-
-Kemudian buat *parameter path* dengan tipe anotasi menggunakan *class* enum dari (`ModelName`)
-
-{* ../../docs_src/path_params/tutorial005.py hl[16] *}
-
-### Periksa dokumentasi
-
-Karena nilai yang tersedia untuk *parameter path* telah terdefinisi, dokumen interatik bisa memunculkan:
-
-
-
-### Bekerja dengan *enumarasi* Python
-
-Nilai *parameter path* akan menjadi *anggota enumerasi*.
-
-#### Membandingkan *anggota enumerasi*
-
-Anda bisa membandingkan parameter *path* dengan *anggota enumerasi* di enum `ModelName` yang anda buat:
-
-{* ../../docs_src/path_params/tutorial005.py hl[17] *}
-
-#### Mendapatkan *nilai enumerasi*
-
-Anda bisa mendapatkan nilai (`str` dalam kasus ini) menggunakan `model_name.value`, atau secara umum `anggota_enum_anda.value`:
-
-{* ../../docs_src/path_params/tutorial005.py hl[20] *}
-
-/// tip | Tips
-
-Anda bisa mengakses nilai `"lenet"` dnegan `ModelName.lenet.value`.
-
-///
-
-#### Menghasilkan *anggota enumerasi*
-
-Anda bisa menghasilkan *anggota enumerasi* dari *operasi path* bahkan di body JSON bersarang (contoh `dict`).
-
-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] *}
-
-Klien akan mendapatkan respon JSON seperti berikut:
-
-```JSON
-{
- "model_name": "alexnet",
- "message": "Deep Learning FTW!"
-}
-```
-
-## Parameter path berisi path
-
-Misalkan terdapat *operasi path* dengan path `/files/{file_path}`.
-
-Tetapi anda memerlukan `file_path` itu berisi *path*, seperti like `home/johndoe/myfile.txt`.
-
-Sehingga URL untuk file tersebut akan seperti: `/files/home/johndoe/myfile.txt`.
-
-### Dukungan OpenAPI
-
-OpenAPI tidak bisa mendeklarasikan *parameter path* berisi *path* di dalamnya, karena menyebabkan kondisi yang sulit di*test* dan didefinisikan.
-
-Tetapi, di **FastAPI** anda tetap bisa melakukannya dengan menggunakan *tools* internal dari Starlette.
-
-Dan dokumentasi tetap berfungsi walaupun tidak menambahkan keterangan bahwa parameter harus berisi *path*.
-
-### Konverter path
-
-Melalui Starlette anda bisa mendeklarasikan *parameter path* berisi *path* dengan URL seperti:
-
-```
-/files/{file_path:path}
-```
-
-Dikondisi ini nama parameter adalah `file_path` dan bagian terakhir `:path` menginformasikan parameter harus sesuai dengan setiap *path*.
-
-Sehingga anda bisa menggunakan:
-
-{* ../../docs_src/path_params/tutorial004.py hl[6] *}
-
-/// tip | Tips
-
-Anda mungkin perlu parameter berisi `/home/johndoe/myfile.txt` di awali garis belakang (`/`).
-
-Di kondisi ini, URL nya menjadi: `/files//home/johndoe/myfile.txt`, dengan dua garis belakang (`//`) di antara `files` dan `home`.
-
-///
-
-## Ringkasan
-
-Di **FastAPI** dengan menggunakan deklarasi tipe Python standar, pendek, intuitif, anda mendapatkan:
-
-* Dukungan editor: pemeriksaan kesalahan, autocompletion, dll.
-* "Parsing" data.
-* Validasi data.
-* Annotasi API dan dokumentasi otomatis.
-
-Semua itu anda hanya perlu mendeklarasikan sekali saja.
-
-Ini adalah salah satu keunggulan **FastAPI** dibandingkan dengan *framework* lainnya (selain dari performa Python *native*c)
diff --git a/docs/id/docs/tutorial/static-files.md b/docs/id/docs/tutorial/static-files.md
deleted file mode 100644
index b55f31394..000000000
--- a/docs/id/docs/tutorial/static-files.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Berkas Statis
-
-Anda dapat menyajikan berkas statis secara otomatis dari sebuah direktori menggunakan `StaticFiles`.
-
-## Penggunaan `StaticFiles`
-
-* Mengimpor `StaticFiles`.
-* "Mount" representatif `StaticFiles()` di jalur spesifik.
-
-{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
-
-/// note | Detail Teknis
-
-Anda dapat pula menggunakan `from starlette.staticfiles import StaticFiles`.
-
-**FastAPI** menyediakan `starlette.staticfiles` sama seperti `fastapi.staticfiles` sebagai kemudahan pada Anda, yaitu para pengembang. Tetapi ini asli berasal langsung dari Starlette.
-
-///
-
-### Apa itu "Mounting"
-
-"Mounting" dimaksud menambah aplikasi "independen" secara lengkap di jalur spesifik, kemudian menangani seluruh sub-jalur.
-
-Hal ini berbeda dari menggunakan `APIRouter` karena aplikasi yang dimount benar-benar independen. OpenAPI dan dokumentasi dari aplikasi utama Anda tak akan menyertakan apa pun dari aplikasi yang dimount, dst.
-
-Anda dapat mempelajari mengenai ini dalam [Panduan Pengguna Lanjutan](../advanced/index.md){.internal-link target=_blank}.
-
-## Detail
-
-Terhadap `"/static"` pertama mengacu pada sub-jalur yang akan menjadi tempat "sub-aplikasi" ini akan "dimount". Maka, jalur apa pun yang dimulai dengan `"/static"` akan ditangani oleh sub-jalur tersebut.
-
-Terhadap `directory="static"` mengacu pada nama direktori yang berisi berkas statis Anda.
-
-Terhadap `name="static"` ialah nama yang dapat digunakan secara internal oleh **FastAPI**.
-
-Seluruh parameter ini dapat berbeda dari sekadar "`static`", sesuaikan parameter dengan keperluan dan detail spesifik akan aplikasi Anda.
-
-## Info lanjutan
-
-Sebagai detail dan opsi tambahan lihat dokumentasi Starlette perihal Berkas Statis.
diff --git a/docs/id/mkdocs.yml b/docs/id/mkdocs.yml
deleted file mode 100644
index de18856f4..000000000
--- a/docs/id/mkdocs.yml
+++ /dev/null
@@ -1 +0,0 @@
-INHERIT: ../en/mkdocs.yml
diff --git a/docs/it/docs/index.md b/docs/it/docs/index.md
deleted file mode 100644
index 7214df8a8..000000000
--- a/docs/it/docs/index.md
+++ /dev/null
@@ -1,463 +0,0 @@
-
-- FastAPI framework, alte prestazioni, facile da imparare, rapido da implementare, pronto per il rilascio in produzione -
- - - ---- - -**Documentazione**: https://fastapi.tiangolo.com - -**Codice Sorgente**: https://github.com/fastapi/fastapi - ---- - -FastAPI è un web framework moderno e veloce (a prestazioni elevate) che serve a creare API con Python 3.6+ basato sulle annotazioni di tipo di Python. - -Le sue caratteristiche principali sono: - -* **Velocità**: Prestazioni molto elevate, alla pari di **NodeJS** e **Go** (grazie a Starlette e Pydantic). [Uno dei framework Python più veloci in circolazione](#performance). -* **Veloce da programmare**: Velocizza il lavoro consentendo il rilascio di nuove funzionalità tra il 200% e il 300% più rapidamente. * -* **Meno bug**: Riduce di circa il 40% gli errori che commettono gli sviluppatori durante la scrittura del codice. * -* **Intuitivo**: Grande supporto per gli editor di testo con autocompletamento in ogni dove. In questo modo si può dedicare meno tempo al debugging. -* **Facile**: Progettato per essere facile da usare e imparare. Si riduce il tempo da dedicare alla lettura della documentazione. -* **Sintentico**: Minimizza la duplicazione di codice. Molteplici funzionalità, ognuna con la propria dichiarazione dei parametri. Meno errori. -* **Robusto**: Crea codice pronto per la produzione con documentazione automatica interattiva. -* **Basato sugli standard**: Basato su (e completamente compatibile con) gli open standard per le API: OpenAPI (precedentemente Swagger) e JSON Schema. - -* Stima basata sull'esito di test eseguiti su codice sorgente di applicazioni rilasciate in produzione da un team interno di sviluppatori. - -## Sponsor - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} -async def...uvicorn main:app --reload...email-validator - per la validazione di email.
-
-Usate da Starlette:
-
-* requests - Richiesto se vuoi usare il `TestClient`.
-* aiofiles - Richiesto se vuoi usare `FileResponse` o `StaticFiles`.
-* jinja2 - Richiesto se vuoi usare la configurazione template di default.
-* python-multipart - Richiesto se vuoi supportare il "parsing" con `request.form()`.
-* itsdangerous - Richiesto per usare `SessionMiddleware`.
-* pyyaml - Richiesto per il supporto dello `SchemaGenerator` di Starlette (probabilmente non ti serve con FastAPI).
-* graphene - Richiesto per il supporto di `GraphQLApp`.
-
-Usate da FastAPI / Starlette:
-
-* uvicorn - per il server che carica e serve la tua applicazione.
-* orjson - ichiesto se vuoi usare `ORJSONResponse`.
-* ujson - Richiesto se vuoi usare `UJSONResponse`.
-
-Puoi installarle tutte con `pip install fastapi[all]`.
-
-## Licenza
-
-Questo progetto è concesso in licenza in base ai termini della licenza MIT.
diff --git a/docs/it/mkdocs.yml b/docs/it/mkdocs.yml
deleted file mode 100644
index de18856f4..000000000
--- a/docs/it/mkdocs.yml
+++ /dev/null
@@ -1 +0,0 @@
-INHERIT: ../en/mkdocs.yml
diff --git a/docs/ja/docs/advanced/websockets.md b/docs/ja/docs/advanced/websockets.md
index 43009eba8..2517530ab 100644
--- a/docs/ja/docs/advanced/websockets.md
+++ b/docs/ja/docs/advanced/websockets.md
@@ -184,5 +184,5 @@ Client #1596980209979 left the chat
オプションの詳細については、Starletteのドキュメントを確認してください。
-* `WebSocket` クラス
-* クラスベースのWebSocket処理
+* `WebSocket` クラス
+* クラスベースのWebSocket処理
diff --git a/docs/ja/docs/alternatives.md b/docs/ja/docs/alternatives.md
index 8129a7002..9f5152c08 100644
--- a/docs/ja/docs/alternatives.md
+++ b/docs/ja/docs/alternatives.md
@@ -419,7 +419,7 @@ Marshmallowに匹敵しますが、ベンチマークではMarshmallowよりも
///
-### Starlette
+### Starlette
Starletteは、軽量なASGIフレームワーク/ツールキットで、高性能な非同期サービスの構築に最適です。
@@ -465,7 +465,7 @@ webに関するコアな部分を全て扱います。その上に機能を追
///
-### Uvicorn
+### Uvicorn
Uvicornは非常に高速なASGIサーバーで、uvloopとhttptoolsにより構成されています。
diff --git a/docs/ja/docs/deployment/manually.md b/docs/ja/docs/deployment/manually.md
index 4ea6bd8ff..da382a9c5 100644
--- a/docs/ja/docs/deployment/manually.md
+++ b/docs/ja/docs/deployment/manually.md
@@ -6,7 +6,7 @@
//// tab | Uvicorn
-* Uvicorn, uvloopとhttptoolsを基にした高速なASGIサーバ。
+* Uvicorn, uvloopとhttptoolsを基にした高速なASGIサーバ。
lt
+* XWT
+* PSGI
+
+### abbr가 설명을 제공 { #the-abbr-gives-an-explanation }
+
+* cluster
+* Deep Learning
+
+### abbr가 전체 문구와 설명을 제공 { #the-abbr-gives-a-full-phrase-and-an-explanation }
+
+* MDN
+* I/O.
+
+////
+
+//// tab | 정보
+
+"abbr" 요소의 "title" 속성은 몇 가지 구체적인 지침에 따라 번역됩니다.
+
+번역에서는(영어 단어를 설명하기 위해) 자체 "abbr" 요소를 추가할 수 있으며, LLM은 이를 제거하면 안 됩니다.
+
+`scripts/translate.py`의 일반 프롬프트에서 `### HTML abbr elements` 섹션을 참고하세요.
+
+////
+
+## 제목 { #headings }
+
+//// tab | 테스트
+
+### 웹앱 개발하기 - 튜토리얼 { #develop-a-webapp-a-tutorial }
+
+안녕하세요.
+
+### 타입 힌트와 -애너테이션 { #type-hints-and-annotations }
+
+다시 안녕하세요.
+
+### super- 및 subclasses { #super-and-subclasses }
+
+다시 안녕하세요.
+
+////
+
+//// tab | 정보
+
+제목에 대한 유일한 강한 규칙은, LLM이 중괄호 안의 해시 부분을 변경하지 않아 링크가 깨지지 않게 하는 것입니다.
+
+`scripts/translate.py`의 일반 프롬프트에서 `### Headings` 섹션을 참고하세요.
+
+언어별 지침은 예를 들어 `docs/de/llm-prompt.md`의 `### Headings` 섹션을 참고하세요.
+
+////
+
+## 문서에서 사용되는 용어 { #terms-used-in-the-docs }
+
+//// tab | 테스트
+
+* 당신
+* 당신의
+
+* 예: (e.g.)
+* 등 (etc.)
+
+* `int`로서의 `foo`
+* `str`로서의 `bar`
+* `list`로서의 `baz`
+
+* 튜토리얼 - 사용자 가이드
+* 고급 사용자 가이드
+* SQLModel 문서
+* API 문서
+* 자동 문서
+
+* Data Science
+* Deep Learning
+* Machine Learning
+* Dependency Injection
+* HTTP Basic authentication
+* HTTP Digest
+* ISO format
+* JSON Schema 표준
+* JSON schema
+* schema definition
+* Password Flow
+* Mobile
+
+* deprecated
+* designed
+* invalid
+* on the fly
+* standard
+* default
+* case-sensitive
+* case-insensitive
+
+* 애플리케이션을 서빙하다
+* 페이지를 서빙하다
+
+* 앱
+* 애플리케이션
+
+* 요청
+* 응답
+* 오류 응답
+
+* 경로 처리
+* 경로 처리 데코레이터
+* 경로 처리 함수
+
+* body
+* 요청 body
+* 응답 body
+* JSON body
+* form body
+* file body
+* 함수 body
+
+* parameter
+* body parameter
+* path parameter
+* query parameter
+* cookie parameter
+* header parameter
+* form parameter
+* function parameter
+
+* event
+* startup event
+* 서버 startup
+* shutdown event
+* lifespan event
+
+* handler
+* event handler
+* exception handler
+* 처리하다
+
+* model
+* Pydantic model
+* data model
+* database model
+* form model
+* model object
+
+* class
+* base class
+* parent class
+* subclass
+* child class
+* sibling class
+* class method
+
+* header
+* headers
+* authorization header
+* `Authorization` header
+* forwarded header
+
+* dependency injection system
+* dependency
+* dependable
+* dependant
+
+* I/O bound
+* CPU bound
+* concurrency
+* parallelism
+* multiprocessing
+
+* env var
+* environment variable
+* `PATH`
+* `PATH` variable
+
+* authentication
+* authentication provider
+* authorization
+* authorization form
+* authorization provider
+* 사용자가 인증한다
+* 시스템이 사용자를 인증한다
+
+* CLI
+* command line interface
+
+* server
+* client
+
+* cloud provider
+* cloud service
+
+* development
+* development stages
+
+* dict
+* dictionary
+* enumeration
+* enum
+* enum member
+
+* encoder
+* decoder
+* encode하다
+* decode하다
+
+* exception
+* raise하다
+
+* expression
+* statement
+
+* frontend
+* backend
+
+* GitHub discussion
+* GitHub issue
+
+* performance
+* performance optimization
+
+* return type
+* return value
+
+* security
+* security scheme
+
+* task
+* background task
+* task function
+
+* template
+* template engine
+
+* type annotation
+* type hint
+
+* server worker
+* Uvicorn worker
+* Gunicorn Worker
+* worker process
+* worker class
+* workload
+
+* deployment
+* deploy하다
+
+* SDK
+* software development kit
+
+* `APIRouter`
+* `requirements.txt`
+* Bearer Token
+* breaking change
+* bug
+* button
+* callable
+* code
+* commit
+* context manager
+* coroutine
+* database session
+* disk
+* domain
+* engine
+* fake X
+* HTTP GET method
+* item
+* library
+* lifespan
+* lock
+* middleware
+* mobile application
+* module
+* mounting
+* network
+* origin
+* override
+* payload
+* processor
+* property
+* proxy
+* pull request
+* query
+* RAM
+* remote machine
+* status code
+* string
+* tag
+* web framework
+* wildcard
+* return하다
+* validate하다
+
+////
+
+//// tab | 정보
+
+이것은 문서에서 보이는 (대부분) 기술 용어의 불완전하고 비규범적인 목록입니다. 프롬프트 설계자가 어떤 용어에 대해 LLM에 추가적인 도움이 필요한지 파악하는 데 유용할 수 있습니다. 예를 들어, 좋은 번역을 계속 덜 좋은 번역으로 되돌릴 때, 또는 언어에서 용어의 활용/변화를 처리하는 데 문제가 있을 때 도움이 됩니다.
+
+예를 들어 `docs/de/llm-prompt.md`의 `### List of English terms and their preferred German translations` 섹션을 참고하세요.
+
+////
diff --git a/docs/ko/docs/about/index.md b/docs/ko/docs/about/index.md
index ee7804d32..dc2c72874 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-responses.md b/docs/ko/docs/advanced/additional-responses.md
new file mode 100644
index 000000000..a6f51f5b9
--- /dev/null
+++ b/docs/ko/docs/advanced/additional-responses.md
@@ -0,0 +1,247 @@
+# OpenAPI에서 추가 응답 { #additional-responses-in-openapi }
+
+/// warning | 경고
+
+이는 꽤 고급 주제입니다.
+
+**FastAPI**를 막 시작했다면, 이 내용이 필요 없을 수도 있습니다.
+
+///
+
+추가 상태 코드, 미디어 타입, 설명 등을 포함한 추가 응답을 선언할 수 있습니다.
+
+이러한 추가 응답은 OpenAPI 스키마에 포함되므로 API 문서에도 표시됩니다.
+
+하지만 이러한 추가 응답의 경우, 상태 코드와 콘텐츠를 포함하여 `JSONResponse` 같은 `Response`를 직접 반환하도록 반드시 처리해야 합니다.
+
+## `model`을 사용한 추가 응답 { #additional-response-with-model }
+
+*경로 처리 데코레이터*에 `responses` 파라미터를 전달할 수 있습니다.
+
+이는 `dict`를 받습니다. 키는 각 응답의 상태 코드(예: `200`)이고, 값은 각 응답에 대한 정보를 담은 다른 `dict`입니다.
+
+각 응답 `dict`에는 `response_model`처럼 Pydantic 모델을 담는 `model` 키가 있을 수 있습니다.
+
+**FastAPI**는 그 모델을 사용해 JSON Schema를 생성하고, OpenAPI의 올바른 위치에 포함합니다.
+
+예를 들어, 상태 코드 `404`와 Pydantic 모델 `Message`를 사용하는 다른 응답을 선언하려면 다음과 같이 작성할 수 있습니다:
+
+{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *}
+
+/// note | 참고
+
+`JSONResponse`를 직접 반환해야 한다는 점을 기억하세요.
+
+///
+
+/// info | 정보
+
+`model` 키는 OpenAPI의 일부가 아닙니다.
+
+**FastAPI**는 여기에서 Pydantic 모델을 가져와 JSON Schema를 생성하고 올바른 위치에 넣습니다.
+
+올바른 위치는 다음과 같습니다:
+
+* 값으로 또 다른 JSON 객체(`dict`)를 가지는 `content` 키 안에:
+ * 미디어 타입(예: `application/json`)을 키로 가지며, 값으로 또 다른 JSON 객체를 포함하고:
+ * `schema` 키가 있고, 그 값이 모델에서 생성된 JSON Schema입니다. 이것이 올바른 위치입니다.
+ * **FastAPI**는 이를 직접 포함하는 대신, OpenAPI의 다른 위치에 있는 전역 JSON Schemas를 참조하도록 여기에서 reference를 추가합니다. 이렇게 하면 다른 애플리케이션과 클라이언트가 그 JSON Schema를 직접 사용할 수 있고, 더 나은 코드 생성 도구 등을 제공할 수 있습니다.
+
+///
+
+이 *경로 처리*에 대해 OpenAPI에 생성되는 응답은 다음과 같습니다:
+
+```JSON hl_lines="3-12"
+{
+ "responses": {
+ "404": {
+ "description": "Additional Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Message"
+ }
+ }
+ }
+ },
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+스키마는 OpenAPI 스키마 내부의 다른 위치를 참조합니다:
+
+```JSON hl_lines="4-16"
+{
+ "components": {
+ "schemas": {
+ "Message": {
+ "title": "Message",
+ "required": [
+ "message"
+ ],
+ "type": "object",
+ "properties": {
+ "message": {
+ "title": "Message",
+ "type": "string"
+ }
+ }
+ },
+ "Item": {
+ "title": "Item",
+ "required": [
+ "id",
+ "value"
+ ],
+ "type": "object",
+ "properties": {
+ "id": {
+ "title": "Id",
+ "type": "string"
+ },
+ "value": {
+ "title": "Value",
+ "type": "string"
+ }
+ }
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": [
+ "loc",
+ "msg",
+ "type"
+ ],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "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"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+## 주요 응답에 대한 추가 미디어 타입 { #additional-media-types-for-the-main-response }
+
+같은 `responses` 파라미터를 사용해 동일한 주요 응답에 대해 다른 미디어 타입을 추가할 수도 있습니다.
+
+예를 들어, *경로 처리*가 JSON 객체(미디어 타입 `application/json`) 또는 PNG 이미지(미디어 타입 `image/png`)를 반환할 수 있다고 선언하기 위해 `image/png`라는 추가 미디어 타입을 추가할 수 있습니다:
+
+{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
+
+/// note | 참고
+
+이미지는 `FileResponse`를 사용해 직접 반환해야 한다는 점에 유의하세요.
+
+///
+
+/// info | 정보
+
+`responses` 파라미터에서 다른 미디어 타입을 명시적으로 지정하지 않는 한, FastAPI는 응답이 주요 응답 클래스와 동일한 미디어 타입(기본값 `application/json`)을 가진다고 가정합니다.
+
+하지만 커스텀 응답 클래스를 지정하면서 미디어 타입을 `None`으로 설정했다면, FastAPI는 연결된 모델이 있는 모든 추가 응답에 대해 `application/json`을 사용합니다.
+
+///
+
+## 정보 결합하기 { #combining-information }
+
+`response_model`, `status_code`, `responses` 파라미터를 포함해 여러 위치의 응답 정보를 결합할 수도 있습니다.
+
+기본 상태 코드 `200`(또는 필요하다면 커스텀 코드)을 사용하여 `response_model`을 선언하고, 그와 동일한 응답에 대한 추가 정보를 `responses`에서 OpenAPI 스키마에 직접 선언할 수 있습니다.
+
+**FastAPI**는 `responses`의 추가 정보를 유지하고, 모델의 JSON Schema와 결합합니다.
+
+예를 들어, Pydantic 모델을 사용하고 커스텀 `description`을 가진 상태 코드 `404` 응답을 선언할 수 있습니다.
+
+또한 `response_model`을 사용하는 상태 코드 `200` 응답을 선언하되, 커스텀 `example`을 포함할 수도 있습니다:
+
+{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *}
+
+이 모든 내용은 OpenAPI에 결합되어 포함되고, API 문서에 표시됩니다:
+
+
+
+## 미리 정의된 응답과 커스텀 응답 결합하기 { #combine-predefined-responses-and-custom-ones }
+
+여러 *경로 처리*에 적용되는 미리 정의된 응답이 필요할 수도 있지만, 각 *경로 처리*마다 필요한 커스텀 응답과 결합하고 싶을 수도 있습니다.
+
+그런 경우 Python의 `dict` “unpacking” 기법인 `**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_py310.py hl[11:15,24] *}
+
+## OpenAPI 응답에 대한 추가 정보 { #more-information-about-openapi-responses }
+
+응답에 정확히 무엇을 포함할 수 있는지 보려면, OpenAPI 사양의 다음 섹션을 확인하세요:
+
+* OpenAPI Responses Object: `Response Object`를 포함합니다.
+* OpenAPI Response Object: `responses` 파라미터 안의 각 응답에 이것의 어떤 항목이든 직접 포함할 수 있습니다. `description`, `headers`, `content`(여기에서 서로 다른 미디어 타입과 JSON Schema를 선언합니다), `links` 등을 포함할 수 있습니다.
diff --git a/docs/ko/docs/advanced/additional-status-codes.md b/docs/ko/docs/advanced/additional-status-codes.md
index da06cb778..64a7eabd5 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 7fa043fa3..04e557d15 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 37dfe2979..6c8593681 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 }
-아래의 명령어로 테스트 코드를 실행합니다:
+다음과 같이 평소처럼 테스트를 실행할 수 있습니다:
+
+하지만 프록시(포트 `9999`)를 사용해 "공식" URL인 `/api/v1/docs`에서 docs UI에 접근하면, 올바르게 동작합니다! 🎉
+
+http://127.0.0.1:9999/api/v1/docs에서 확인할 수 있습니다:
+
+
+
+원하던 그대로입니다. ✔️
+
+이는 FastAPI가 이 `root_path`를 사용해, OpenAPI에서 기본 `server`를 `root_path`가 제공한 URL로 생성하기 때문입니다.
+
+## 추가 서버 { #additional-servers }
+
+/// warning | 경고
+
+이는 더 고급 사용 사례입니다. 건너뛰어도 괜찮습니다.
+
+///
+
+기본적으로 **FastAPI**는 OpenAPI 스키마에서 `root_path`의 URL로 `server`를 생성합니다.
+
+하지만 예를 들어 동일한 docs UI가 스테이징과 프로덕션 환경 모두와 상호작용하도록 하려면, 다른 대안 `servers`를 제공할 수도 있습니다.
+
+사용자 정의 `servers` 리스트를 전달했고 `root_path`(API가 프록시 뒤에 있기 때문)가 있다면, **FastAPI**는 리스트의 맨 앞에 이 `root_path`를 가진 "server"를 삽입합니다.
+
+예:
+
+{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *}
+
+다음과 같은 OpenAPI 스키마를 생성합니다:
+
+```JSON hl_lines="5-7"
+{
+ "openapi": "3.1.0",
+ // 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 | 팁
+
+`root_path`에서 가져온 값인 `/api/v1`의 `url` 값을 가진, 자동 생성된 server에 주목하세요.
+
+///
+
+http://127.0.0.1:9999/api/v1/docs의 docs UI에서는 다음처럼 보입니다:
+
+
+
+/// tip | 팁
+
+docs UI는 선택한 server와 상호작용합니다.
+
+///
+
+/// note | 기술 세부사항
+
+OpenAPI 사양에서 `servers` 속성은 선택 사항입니다.
+
+`servers` 파라미터를 지정하지 않고 `root_path`가 `/`와 같다면, 생성된 OpenAPI 스키마의 `servers` 속성은 기본적으로 완전히 생략되며, 이는 `url` 값이 `/`인 단일 server와 동등합니다.
+
+///
+
+### `root_path`에서 자동 server 비활성화하기 { #disable-automatic-server-from-root-path }
+
+**FastAPI**가 `root_path`를 사용한 자동 server를 포함하지 않게 하려면, `root_path_in_servers=False` 파라미터를 사용할 수 있습니다:
+
+{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *}
+
+그러면 OpenAPI 스키마에 포함되지 않습니다.
+
+## 서브 애플리케이션 마운트하기 { #mounting-a-sub-application }
+
+프록시에서 `root_path`를 사용하면서도, [서브 애플리케이션 - 마운트](sub-applications.md){.internal-link target=_blank}에 설명된 것처럼 서브 애플리케이션을 마운트해야 한다면, 기대하는 대로 일반적으로 수행할 수 있습니다.
+
+FastAPI가 내부적으로 `root_path`를 똑똑하게 사용하므로, 그냥 동작합니다. ✨
diff --git a/docs/ko/docs/advanced/custom-response.md b/docs/ko/docs/advanced/custom-response.md
index 2001956fa..55dc2a4be 100644
--- a/docs/ko/docs/advanced/custom-response.md
+++ b/docs/ko/docs/advanced/custom-response.md
@@ -1,4 +1,4 @@
-# 사용자 정의 응답 - HTML, Stream, 파일, 기타
+# 사용자 정의 응답 - HTML, Stream, 파일, 기타 { #custom-response-html-stream-file-others }
기본적으로, **FastAPI** 응답을 `JSONResponse`를 사용하여 반환합니다.
@@ -6,11 +6,11 @@
그러나 `Response` (또는 `JSONResponse`와 같은 하위 클래스)를 직접 반환하면, 데이터가 자동으로 변환되지 않으며 (심지어 `response_model`을 선언했더라도), 문서화가 자동으로 생성되지 않습니다(예를 들어, 생성된 OpenAPI의 일부로 HTTP 헤더 `Content-Type`에 특정 "미디어 타입"을 포함하는 경우).
-하지만 *경로 작업 데코레이터*에서 `response_class` 매개변수를 사용하여 원하는 `Response`(예: 모든 `Response` 하위 클래스)를 선언할 수도 있습니다.
+하지만 *경로 처리 데코레이터*에서 `response_class` 매개변수를 사용하여 원하는 `Response`(예: 모든 `Response` 하위 클래스)를 선언할 수도 있습니다.
-*경로 작업 함수*에서 반환하는 내용은 해당 `Response`안에 포함됩니다.
+*경로 처리 함수*에서 반환하는 내용은 해당 `Response`안에 포함됩니다.
-그리고 만약 그 `Response`가 `JSONResponse`와 `UJSONResponse`의 경우 처럼 JSON 미디어 타입(`application/json`)을 가지고 있다면, *경로 작업 데코레이터*에서 선언한 Pydantic의 `response_model`을 사용해 자동으로 변환(및 필터링) 됩니다.
+그리고 만약 그 `Response`가 `JSONResponse`와 `UJSONResponse`의 경우 처럼 JSON 미디어 타입(`application/json`)을 가지고 있다면, *경로 처리 데코레이터*에서 선언한 Pydantic의 `response_model`을 사용해 자동으로 변환(및 필터링) 됩니다.
/// note | 참고
@@ -18,11 +18,11 @@
///
-## `ORJSONResponse` 사용하기
+## `ORJSONResponse` 사용하기 { #use-orjsonresponse }
-예를 들어, 성능을 극대화하려는 경우, orjson을 설치하여 사용하고 응답을 `ORJSONResponse`로 설정할 수 있습니다.
+예를 들어, 성능을 극대화하려는 경우, `orjson`을 설치하여 사용하고 응답을 `ORJSONResponse`로 설정할 수 있습니다.
-사용하고자 하는 `Response` 클래스(하위 클래스)를 임포트한 후, **경로 작업 데코레이터*에서 선언하세요.
+사용하고자 하는 `Response` 클래스(하위 클래스)를 임포트한 후, *경로 처리 데코레이터*에서 선언하세요.
대규모 응답의 경우, 딕셔너리를 반환하는 것보다 `Response`를 반환하는 것이 훨씬 빠릅니다.
@@ -30,7 +30,7 @@
하지만 반환하는 내용이 **JSON으로 직렬화 가능**하다고 확신하는 경우, 해당 내용을 응답 클래스에 직접 전달할 수 있으며, FastAPI가 반환 내용을 `jsonable_encoder`를 통해 처리한 뒤 응답 클래스에 전달하는 오버헤드를 피할 수 있습니다.
-{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *}
+{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *}
/// info | 정보
@@ -48,14 +48,14 @@
///
-## HTML 응답
+## HTML 응답 { #html-response }
**FastAPI**에서 HTML 응답을 직접 반환하려면 `HTMLResponse`를 사용하세요.
* `HTMLResponse`를 임포트 합니다.
-* *경로 작업 데코레이터*의 `response_class` 매개변수로 `HTMLResponse`를 전달합니다.
+* *경로 처리 데코레이터*의 `response_class` 매개변수로 `HTMLResponse`를 전달합니다.
-{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *}
+{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *}
/// info | 정보
@@ -67,17 +67,17 @@
///
-### `Response` 반환하기
+### `Response` 반환하기 { #return-a-response }
-[응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 본 것 처럼, *경로 작업*에서 응답을 직접 반환하여 재정의할 수도 있습니다.
+[응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 본 것 처럼, *경로 처리*에서 응답을 직접 반환하여 재정의할 수도 있습니다.
위의 예제와 동일하게 `HTMLResponse`를 반환하는 코드는 다음과 같을 수 있습니다:
-{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *}
+{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *}
/// warning | 경고
-*경로 작업 함수*에서 직접 반환된 `Response`는 OpenAPI에 문서화되지 않습니다(예를들어, `Content-Type`이 문서화되지 않음) 자동 대화형 문서에서도 표시되지 않습니다.
+*경로 처리 함수*에서 직접 반환된 `Response`는 OpenAPI에 문서화되지 않습니다(예를들어, `Content-Type`이 문서화되지 않음) 자동 대화형 문서에서도 표시되지 않습니다.
///
@@ -87,27 +87,27 @@
///
-### OpenAPI에 문서화하고 `Response` 재정의 하기
+### OpenAPI에 문서화하고 `Response` 재정의 하기 { #document-in-openapi-and-override-response }
함수 내부에서 응답을 재정의하면서 동시에 OpenAPI에서 "미디어 타입"을 문서화하고 싶다면, `response_class` 매게변수를 사용하면서 `Response` 객체를 반환할 수 있습니다.
-이 경우 `response_class`는 OpenAPI *경로 작업*을 문서화하는 데만 사용되고, 실제로는 여러분이 반환한 `Response`가 그대로 사용됩니다.
+이 경우 `response_class`는 OpenAPI *경로 처리*를 문서화하는 데만 사용되고, 실제로는 여러분이 반환한 `Response`가 그대로 사용됩니다.
-### `HTMLResponse`직접 반환하기
+#### `HTMLResponse`직접 반환하기 { #return-an-htmlresponse-directly }
예를 들어, 다음과 같이 작성할 수 있습니다:
-{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *}
+{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *}
이 예제에서, `generate_html_response()` 함수는 HTML을 `str`로 반환하는 대신 이미 `Response`를 생성하고 반환합니다.
`generate_html_response()`를 호출한 결과를 반환함으로써, 기본적인 **FastAPI** 기본 동작을 재정의 하는 `Response`를 이미 반환하고 있습니다.
-하지만 `response_class`에 `HTMLResponse`를 함께 전달했기 때문에, FastAPI는 이를 OpenAPI 및 대화형 문서에서 `text/html`로 HTML을 문서화 하는 방법을 알 수 있습니다.
+하지만 `response_class`에 `HTMLResponse`를 함께 전달했기 때문에, **FastAPI**는 이를 OpenAPI 및 대화형 문서에서 `text/html`로 HTML을 문서화 하는 방법을 알 수 있습니다.
-## 사용 가능한 응답들
+## 사용 가능한 응답들 { #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/dataclasses.md b/docs/ko/docs/advanced/dataclasses.md
new file mode 100644
index 000000000..92ad5545b
--- /dev/null
+++ b/docs/ko/docs/advanced/dataclasses.md
@@ -0,0 +1,95 @@
+# Dataclasses 사용하기 { #using-dataclasses }
+
+FastAPI는 **Pydantic** 위에 구축되어 있으며, 지금까지는 Pydantic 모델을 사용해 요청과 응답을 선언하는 방법을 보여드렸습니다.
+
+하지만 FastAPI는 `dataclasses`도 같은 방식으로 사용하는 것을 지원합니다:
+
+{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
+
+이는 **Pydantic** 덕분에 여전히 지원되는데, Pydantic이 `dataclasses`에 대한 내부 지원을 제공하기 때문입니다.
+
+따라서 위 코드처럼 Pydantic을 명시적으로 사용하지 않더라도, FastAPI는 Pydantic을 사용해 표준 dataclasses를 Pydantic의 dataclasses 변형으로 변환합니다.
+
+그리고 물론 다음과 같은 기능도 동일하게 지원합니다:
+
+* 데이터 검증
+* 데이터 직렬화
+* 데이터 문서화 등
+
+이는 Pydantic 모델을 사용할 때와 같은 방식으로 동작합니다. 그리고 실제로도 내부적으로는 Pydantic을 사용해 같은 방식으로 구현됩니다.
+
+/// info | 정보
+
+dataclasses는 Pydantic 모델이 할 수 있는 모든 것을 할 수는 없다는 점을 기억하세요.
+
+그래서 여전히 Pydantic 모델을 사용해야 할 수도 있습니다.
+
+하지만 이미 여러 dataclasses를 가지고 있다면, 이것은 FastAPI로 웹 API를 구동하는 데 그것들을 활용할 수 있는 좋은 방법입니다. 🤓
+
+///
+
+## `response_model`에서 Dataclasses 사용하기 { #dataclasses-in-response-model }
+
+`response_model` 매개변수에서도 `dataclasses`를 사용할 수 있습니다:
+
+{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
+
+dataclass는 자동으로 Pydantic dataclass로 변환됩니다.
+
+이렇게 하면 해당 스키마가 API docs 사용자 인터페이스에 표시됩니다:
+
+
+
+## 중첩 데이터 구조에서 Dataclasses 사용하기 { #dataclasses-in-nested-data-structures }
+
+`dataclasses`를 다른 타입 애너테이션과 조합해 중첩 데이터 구조를 만들 수도 있습니다.
+
+일부 경우에는 Pydantic 버전의 `dataclasses`를 사용해야 할 수도 있습니다. 예를 들어 자동 생성된 API 문서에서 오류가 발생하는 경우입니다.
+
+그런 경우 표준 `dataclasses`를 드롭인 대체재인 `pydantic.dataclasses`로 간단히 바꾸면 됩니다:
+
+{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
+
+1. 표준 `dataclasses`에서 `field`를 계속 임포트합니다.
+
+2. `pydantic.dataclasses`는 `dataclasses`의 드롭인 대체재입니다.
+
+3. `Author` dataclass에는 `Item` dataclasses의 리스트가 포함됩니다.
+
+4. `Author` dataclass가 `response_model` 매개변수로 사용됩니다.
+
+5. 요청 본문으로 dataclasses와 함께 다른 표준 타입 애너테이션을 사용할 수 있습니다.
+
+ 이 경우에는 `Item` dataclasses의 리스트입니다.
+
+6. 여기서는 dataclasses 리스트인 `items`를 포함하는 딕셔너리를 반환합니다.
+
+ FastAPI는 여전히 데이터를 JSON으로 serializing할 수 있습니다.
+
+7. 여기서 `response_model`은 `Author` dataclasses 리스트에 대한 타입 애너테이션을 사용합니다.
+
+ 다시 말해, `dataclasses`를 표준 타입 애너테이션과 조합할 수 있습니다.
+
+8. 이 *경로 처리 함수*는 `async def` 대신 일반 `def`를 사용하고 있다는 점에 주목하세요.
+
+ 언제나처럼 FastAPI에서는 필요에 따라 `def`와 `async def`를 조합해 사용할 수 있습니다.
+
+ 어떤 것을 언제 사용해야 하는지 다시 확인하고 싶다면, [`async`와 `await`](../async.md#in-a-hurry){.internal-link target=_blank} 문서의 _"급하신가요?"_ 섹션을 확인하세요.
+
+9. 이 *경로 처리 함수*는 dataclasses를(물론 반환할 수도 있지만) 반환하지 않고, 내부 데이터를 담은 딕셔너리들의 리스트를 반환합니다.
+
+ FastAPI는 `response_model` 매개변수(dataclasses 포함)를 사용해 응답을 변환합니다.
+
+`dataclasses`는 다른 타입 애너테이션과 매우 다양한 조합으로 결합해 복잡한 데이터 구조를 구성할 수 있습니다.
+
+더 구체적인 내용은 위 코드 내 애너테이션 팁을 확인하세요.
+
+## 더 알아보기 { #learn-more }
+
+`dataclasses`를 다른 Pydantic 모델과 조합하거나, 이를 상속하거나, 여러분의 모델에 포함하는 등의 작업도 할 수 있습니다.
+
+자세한 내용은 dataclasses에 관한 Pydantic 문서를 참고하세요.
+
+## 버전 { #version }
+
+이 기능은 FastAPI `0.67.0` 버전부터 사용할 수 있습니다. 🔖
diff --git a/docs/ko/docs/advanced/events.md b/docs/ko/docs/advanced/events.md
index 5f8fe0f1e..35223eaf3 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/generate-clients.md b/docs/ko/docs/advanced/generate-clients.md
new file mode 100644
index 000000000..1def3efe1
--- /dev/null
+++ b/docs/ko/docs/advanced/generate-clients.md
@@ -0,0 +1,208 @@
+# SDK 생성하기 { #generating-sdks }
+
+**FastAPI**는 **OpenAPI** 사양을 기반으로 하므로, FastAPI의 API는 많은 도구가 이해할 수 있는 표준 형식으로 설명할 수 있습니다.
+
+덕분에 여러 언어용 클라이언트 라이브러리(**SDKs**), 최신 **문서**, 그리고 코드와 동기화된 **테스트** 또는 **자동화 워크플로**를 쉽게 생성할 수 있습니다.
+
+이 가이드에서는 FastAPI 백엔드용 **TypeScript SDK**를 생성하는 방법을 배웁니다.
+
+## 오픈 소스 SDK 생성기 { #open-source-sdk-generators }
+
+다양하게 활용할 수 있는 옵션으로 OpenAPI Generator가 있으며, **다양한 프로그래밍 언어**를 지원하고 OpenAPI 사양으로부터 SDK를 생성할 수 있습니다.
+
+**TypeScript 클라이언트**의 경우 Hey API는 TypeScript 생태계에 최적화된 경험을 제공하는 목적에 맞게 설계된 솔루션입니다.
+
+더 많은 SDK 생성기는 OpenAPI.Tools에서 확인할 수 있습니다.
+
+/// tip | 팁
+
+FastAPI는 **OpenAPI 3.1** 사양을 자동으로 생성하므로, 사용하는 도구는 이 버전을 지원해야 합니다.
+
+///
+
+## FastAPI 스폰서의 SDK 생성기 { #sdk-generators-from-fastapi-sponsors }
+
+이 섹션에서는 FastAPI를 후원하는 회사들이 제공하는 **벤처 투자 기반** 및 **기업 지원** 솔루션을 소개합니다. 이 제품들은 고품질로 생성된 SDK에 더해 **추가 기능**과 **통합**을 제공합니다.
+
+✨ [**FastAPI 후원하기**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨를 통해, 이 회사들은 프레임워크와 그 **생태계**가 건강하고 **지속 가능**하게 유지되도록 돕습니다.
+
+또한 이들의 후원은 FastAPI **커뮤니티**(여러분)에 대한 강한 헌신을 보여주며, **좋은 서비스**를 제공하는 것뿐 아니라, 견고하고 활발한 프레임워크인 FastAPI를 지원하는 데에도 관심이 있음을 나타냅니다. 🙇
+
+예를 들어 다음을 사용해 볼 수 있습니다:
+
+* Speakeasy
+* Stainless
+* liblab
+
+이 중 일부는 오픈 소스이거나 무료 티어를 제공하므로, 비용 부담 없이 사용해 볼 수 있습니다. 다른 상용 SDK 생성기도 있으며 온라인에서 찾을 수 있습니다. 🤓
+
+## TypeScript SDK 만들기 { #create-a-typescript-sdk }
+
+간단한 FastAPI 애플리케이션으로 시작해 보겠습니다:
+
+{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
+
+*path operation*에서 요청 페이로드와 응답 페이로드에 사용하는 모델을 `Item`, `ResponseMessage` 모델로 정의하고 있다는 점에 주목하세요.
+
+### API 문서 { #api-docs }
+
+`/docs`로 이동하면, 요청으로 보낼 데이터와 응답으로 받을 데이터에 대한 **스키마(schemas)**가 있는 것을 볼 수 있습니다:
+
+
+
+이 스키마는 앱에서 모델로 선언되었기 때문에 볼 수 있습니다.
+
+그 정보는 앱의 **OpenAPI 스키마**에서 사용할 수 있고, 이후 API 문서에 표시됩니다.
+
+OpenAPI에 포함된 모델의 동일한 정보가 **클라이언트 코드 생성**에 사용될 수 있습니다.
+
+### Hey API { #hey-api }
+
+모델이 포함된 FastAPI 앱이 준비되면, Hey API를 사용해 TypeScript 클라이언트를 생성할 수 있습니다. 가장 빠른 방법은 npx를 사용하는 것입니다.
+
+```sh
+npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client
+```
+
+이 명령은 `./src/client`에 TypeScript SDK를 생성합니다.
+
+`@hey-api/openapi-ts` 설치 방법과 생성된 결과물은 해당 웹사이트에서 확인할 수 있습니다.
+
+### SDK 사용하기 { #using-the-sdk }
+
+이제 클라이언트 코드를 import해서 사용할 수 있습니다. 아래처럼 사용할 수 있으며, 메서드에 대한 자동 완성이 제공되는 것을 확인할 수 있습니다:
+
+
+
+보낼 페이로드에 대해서도 자동 완성이 제공됩니다:
+
+
+
+/// tip | 팁
+
+`name`과 `price`에 대한 자동 완성은 FastAPI 애플리케이션에서 `Item` 모델에 정의된 내용입니다.
+
+///
+
+전송하는 데이터에 대해 인라인 오류도 표시됩니다:
+
+
+
+응답 객체도 자동 완성을 제공합니다:
+
+
+
+## 태그가 있는 FastAPI 앱 { #fastapi-app-with-tags }
+
+대부분의 경우 FastAPI 앱은 더 커지고, 서로 다른 *path operations* 그룹을 분리하기 위해 태그를 사용하게 될 가능성이 큽니다.
+
+예를 들어 **items** 섹션과 **users** 섹션이 있고, 이를 태그로 분리할 수 있습니다:
+
+{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *}
+
+### 태그로 TypeScript 클라이언트 생성하기 { #generate-a-typescript-client-with-tags }
+
+태그를 사용하는 FastAPI 앱에 대해 클라이언트를 생성하면, 일반적으로 생성된 클라이언트 코드도 태그를 기준으로 분리됩니다.
+
+이렇게 하면 클라이언트 코드에서 항목들이 올바르게 정렬되고 그룹화됩니다:
+
+
+
+이 경우 다음이 있습니다:
+
+* `ItemsService`
+* `UsersService`
+
+### 클라이언트 메서드 이름 { #client-method-names }
+
+현재 `createItemItemsPost` 같은 생성된 메서드 이름은 그다지 깔끔하지 않습니다:
+
+```TypeScript
+ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
+```
+
+...이는 클라이언트 생성기가 각 *path operation*에 대해 OpenAPI 내부의 **operation ID**를 사용하기 때문입니다.
+
+OpenAPI는 모든 *path operations* 전체에서 operation ID가 각각 유일해야 한다고 요구합니다. 그래서 FastAPI는 operation ID가 유일하도록 **함수 이름**, **경로**, **HTTP method/operation**을 조합해 operation ID를 생성합니다.
+
+하지만 다음에서 이를 개선하는 방법을 보여드리겠습니다. 🤓
+
+## 커스텀 Operation ID와 더 나은 메서드 이름 { #custom-operation-ids-and-better-method-names }
+
+클라이언트에서 **더 단순한 메서드 이름**을 갖도록, operation ID가 **생성되는 방식**을 **수정**할 수 있습니다.
+
+이 경우 operation ID가 다른 방식으로도 **유일**하도록 보장해야 합니다.
+
+예를 들어 각 *path operation*이 태그를 갖도록 한 다음, **태그**와 *path operation* **이름**(함수 이름)을 기반으로 operation ID를 생성할 수 있습니다.
+
+### 유일 ID 생성 함수 커스터마이징 { #custom-generate-unique-id-function }
+
+FastAPI는 각 *path operation*에 대해 **유일 ID**를 사용하며, 이는 **operation ID** 및 요청/응답에 필요한 커스텀 모델 이름에도 사용됩니다.
+
+이 함수를 커스터마이징할 수 있습니다. 이 함수는 `APIRoute`를 받아 문자열을 반환합니다.
+
+예를 들어 아래에서는 첫 번째 태그(대부분 태그는 하나만 있을 것입니다)와 *path operation* 이름(함수 이름)을 사용합니다.
+
+그 다음 이 커스텀 함수를 `generate_unique_id_function` 매개변수로 **FastAPI**에 전달할 수 있습니다:
+
+{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *}
+
+### 커스텀 Operation ID로 TypeScript 클라이언트 생성하기 { #generate-a-typescript-client-with-custom-operation-ids }
+
+이제 클라이언트를 다시 생성하면, 개선된 메서드 이름을 확인할 수 있습니다:
+
+
+
+보시다시피, 이제 메서드 이름은 태그 다음에 함수 이름이 오며, URL 경로와 HTTP operation의 정보는 포함하지 않습니다.
+
+### 클라이언트 생성기를 위한 OpenAPI 사양 전처리 { #preprocess-the-openapi-specification-for-the-client-generator }
+
+생성된 코드에는 여전히 일부 **중복 정보**가 있습니다.
+
+`ItemsService`(태그에서 가져옴)에 이미 **items**가 포함되어 있어 이 메서드가 items와 관련되어 있음을 알 수 있지만, 메서드 이름에도 태그 이름이 접두사로 붙어 있습니다. 😕
+
+OpenAPI 전반에서는 operation ID가 **유일**하다는 것을 보장하기 위해 이 방식을 유지하고 싶을 수 있습니다.
+
+하지만 생성된 클라이언트에서는, 클라이언트를 생성하기 직전에 OpenAPI operation ID를 **수정**해서 메서드 이름을 더 보기 좋고 **깔끔하게** 만들 수 있습니다.
+
+OpenAPI JSON을 `openapi.json` 파일로 다운로드한 뒤, 아래와 같은 스크립트로 **접두사 태그를 제거**할 수 있습니다:
+
+{* ../../docs_src/generate_clients/tutorial004_py39.py *}
+
+//// tab | Node.js
+
+```Javascript
+{!> ../../docs_src/generate_clients/tutorial004.js!}
+```
+
+////
+
+이렇게 하면 operation ID가 `items-get_items` 같은 형태에서 `get_items`로 변경되어, 클라이언트 생성기가 더 단순한 메서드 이름을 생성할 수 있습니다.
+
+### 전처리된 OpenAPI로 TypeScript 클라이언트 생성하기 { #generate-a-typescript-client-with-the-preprocessed-openapi }
+
+이제 최종 결과가 `openapi.json` 파일에 있으므로, 입력 위치를 업데이트해야 합니다:
+
+```sh
+npx @hey-api/openapi-ts -i ./openapi.json -o src/client
+```
+
+새 클라이언트를 생성한 후에는 **깔끔한 메서드 이름**을 가지면서도, **자동 완성**, **인라인 오류** 등은 그대로 제공됩니다:
+
+
+
+## 장점 { #benefits }
+
+자동으로 생성된 클라이언트를 사용하면 다음에 대해 **자동 완성**을 받을 수 있습니다:
+
+* 메서드
+* 본문(body)의 요청 페이로드, 쿼리 파라미터 등
+* 응답 페이로드
+
+또한 모든 것에 대해 **인라인 오류**도 확인할 수 있습니다.
+
+그리고 백엔드 코드를 업데이트한 뒤 프론트엔드를 **재생성(regenerate)**하면, 새 *path operations*가 메서드로 추가되고 기존 것은 제거되며, 그 밖의 변경 사항도 생성된 코드에 반영됩니다. 🤓
+
+이는 무언가 변경되면 그 변경이 클라이언트 코드에도 자동으로 **반영**된다는 뜻입니다. 또한 클라이언트를 **빌드(build)**하면 사용된 데이터가 **불일치(mismatch)**할 경우 오류가 발생합니다.
+
+따라서 운영 환경에서 최종 사용자에게 오류가 노출된 뒤 문제를 추적하는 대신, 개발 사이클 초기에 **많은 오류를 매우 빨리 감지**할 수 있습니다. ✨
diff --git a/docs/ko/docs/advanced/index.md b/docs/ko/docs/advanced/index.md
index 31704727c..78ef5ffec 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/middleware.md b/docs/ko/docs/advanced/middleware.md
new file mode 100644
index 000000000..be2c972a6
--- /dev/null
+++ b/docs/ko/docs/advanced/middleware.md
@@ -0,0 +1,97 @@
+# 고급 Middleware { #advanced-middleware }
+
+메인 튜토리얼에서 애플리케이션에 [커스텀 Middleware](../tutorial/middleware.md){.internal-link target=_blank}를 추가하는 방법을 읽었습니다.
+
+그리고 [`CORSMiddleware`로 CORS 처리하기](../tutorial/cors.md){.internal-link target=_blank}도 읽었습니다.
+
+이 섹션에서는 다른 middleware들을 사용하는 방법을 살펴보겠습니다.
+
+## ASGI middleware 추가하기 { #adding-asgi-middlewares }
+
+**FastAPI**는 Starlette를 기반으로 하고 ASGI 사양을 구현하므로, 어떤 ASGI middleware든 사용할 수 있습니다.
+
+ASGI 사양을 따르기만 하면, FastAPI나 Starlette를 위해 만들어진 middleware가 아니어도 동작합니다.
+
+일반적으로 ASGI middleware는 첫 번째 인자로 ASGI 앱을 받도록 기대하는 클래스입니다.
+
+그래서 서드파티 ASGI middleware 문서에서는 아마 다음과 같이 하라고 안내할 것입니다:
+
+```Python
+from unicorn import UnicornMiddleware
+
+app = SomeASGIApp()
+
+new_app = UnicornMiddleware(app, some_config="rainbow")
+```
+
+하지만 FastAPI(정확히는 Starlette)는 더 간단한 방법을 제공하며, 이를 통해 내부 middleware가 서버 오류를 처리하고 커스텀 예외 핸들러가 올바르게 동작하도록 보장합니다.
+
+이를 위해(그리고 CORS 예제에서처럼) `app.add_middleware()`를 사용합니다.
+
+```Python
+from fastapi import FastAPI
+from unicorn import UnicornMiddleware
+
+app = FastAPI()
+
+app.add_middleware(UnicornMiddleware, some_config="rainbow")
+```
+
+`app.add_middleware()`는 첫 번째 인자로 middleware 클래스를 받고, 그 뒤에는 middleware에 전달할 추가 인자들을 받습니다.
+
+## 통합 middleware { #integrated-middlewares }
+
+**FastAPI**에는 일반적인 사용 사례를 위한 여러 middleware가 포함되어 있습니다. 다음에서 이를 사용하는 방법을 살펴보겠습니다.
+
+/// note | 기술 세부사항
+
+다음 예제에서는 `from starlette.middleware.something import SomethingMiddleware`를 사용해도 됩니다.
+
+**FastAPI**는 개발자 편의를 위해 `fastapi.middleware`에 여러 middleware를 제공하지만, 사용 가능한 대부분의 middleware는 Starlette에서 직접 제공됩니다.
+
+///
+
+## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware }
+
+들어오는 모든 요청이 `https` 또는 `wss`여야 하도록 강제합니다.
+
+`http` 또는 `ws`로 들어오는 모든 요청은 대신 보안 스킴으로 리디렉션됩니다.
+
+{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *}
+
+## `TrustedHostMiddleware` { #trustedhostmiddleware }
+
+HTTP Host Header 공격을 방어하기 위해, 들어오는 모든 요청에 올바르게 설정된 `Host` 헤더가 있어야 하도록 강제합니다.
+
+{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *}
+
+다음 인자들을 지원합니다:
+
+* `allowed_hosts` - 호스트명으로 허용할 도메인 이름 목록입니다. `*.example.com` 같은 와일드카드 도메인으로 서브도메인을 매칭하는 것도 지원합니다. 어떤 호스트명이든 허용하려면 `allowed_hosts=["*"]`를 사용하거나 middleware를 생략하세요.
+* `www_redirect` - True로 설정하면, 허용된 호스트의 non-www 버전으로 들어오는 요청을 www 버전으로 리디렉션합니다. 기본값은 `True`입니다.
+
+들어오는 요청이 올바르게 검증되지 않으면 `400` 응답이 전송됩니다.
+
+## `GZipMiddleware` { #gzipmiddleware }
+
+`Accept-Encoding` 헤더에 `"gzip"`이 포함된 어떤 요청이든 GZip 응답을 처리합니다.
+
+이 middleware는 일반 응답과 스트리밍 응답을 모두 처리합니다.
+
+{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *}
+
+다음 인자들을 지원합니다:
+
+* `minimum_size` - 바이트 단위로 지정한 최소 크기보다 작은 응답은 GZip으로 압축하지 않습니다. 기본값은 `500`입니다.
+* `compresslevel` - GZip 압축 중에 사용됩니다. 1부터 9까지의 정수입니다. 기본값은 `9`입니다. 값이 낮을수록 압축은 더 빠르지만 파일 크기는 더 커지고, 값이 높을수록 압축은 더 느리지만 파일 크기는 더 작아집니다.
+
+## 다른 middleware { #other-middlewares }
+
+다른 ASGI middleware도 많이 있습니다.
+
+예를 들어:
+
+* Uvicorn의 `ProxyHeadersMiddleware`
+* MessagePack
+
+사용 가능한 다른 middleware를 보려면 Starlette의 Middleware 문서와 ASGI Awesome List를 확인하세요.
diff --git a/docs/ko/docs/advanced/middlewares.md b/docs/ko/docs/advanced/middlewares.md
deleted file mode 100644
index c00aedeaf..000000000
--- 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/openapi-callbacks.md b/docs/ko/docs/advanced/openapi-callbacks.md
new file mode 100644
index 000000000..e4bdea9d6
--- /dev/null
+++ b/docs/ko/docs/advanced/openapi-callbacks.md
@@ -0,0 +1,186 @@
+# OpenAPI 콜백 { #openapi-callbacks }
+
+다른 사람이 만든 *external API*(아마도 당신의 API를 *사용*할 동일한 개발자)가 요청을 트리거하도록 만드는 *경로 처리*를 가진 API를 만들 수 있습니다.
+
+당신의 API 앱이 *external API*를 호출할 때 일어나는 과정을 "callback"이라고 합니다. 외부 개발자가 작성한 소프트웨어가 당신의 API로 요청을 보낸 다음, 당신의 API가 다시 *external API*로 요청을 보내 *되돌려 호출*하기 때문입니다(아마도 같은 개발자가 만든 API일 것입니다).
+
+이 경우, 그 *external API*가 어떤 형태여야 하는지 문서화하고 싶을 수 있습니다. 어떤 *경로 처리*를 가져야 하는지, 어떤 body를 기대하는지, 어떤 응답을 반환해야 하는지 등입니다.
+
+## 콜백이 있는 앱 { #an-app-with-callbacks }
+
+예시로 확인해 보겠습니다.
+
+청구서를 생성할 수 있는 앱을 개발한다고 가정해 보세요.
+
+이 청구서는 `id`, `title`(선택 사항), `customer`, `total`을 갖습니다.
+
+당신의 API 사용자(외부 개발자)는 POST 요청으로 당신의 API에서 청구서를 생성합니다.
+
+그 다음 당신의 API는(가정해 보면):
+
+* 청구서를 외부 개발자의 고객에게 전송합니다.
+* 돈을 수금합니다.
+* API 사용자(외부 개발자)의 API로 다시 알림을 보냅니다.
+ * 이는 (당신의 API에서) 그 외부 개발자가 제공하는 어떤 *external API*로 POST 요청을 보내는 방식으로 수행됩니다(이것이 "callback"입니다).
+
+## 일반적인 **FastAPI** 앱 { #the-normal-fastapi-app }
+
+먼저 콜백을 추가하기 전, 일반적인 API 앱이 어떻게 생겼는지 보겠습니다.
+
+`Invoice` body를 받는 *경로 처리*와, 콜백을 위한 URL을 담는 쿼리 파라미터 `callback_url`이 있을 것입니다.
+
+이 부분은 꽤 일반적이며, 대부분의 코드는 이미 익숙할 것입니다:
+
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
+
+/// tip | 팁
+
+`callback_url` 쿼리 파라미터는 Pydantic의 Url 타입을 사용합니다.
+
+///
+
+유일하게 새로운 것은 *경로 처리 데코레이터*의 인자로 `callbacks=invoices_callback_router.routes`가 들어간다는 점입니다. 이것이 무엇인지 다음에서 보겠습니다.
+
+## 콜백 문서화하기 { #documenting-the-callback }
+
+실제 콜백 코드는 당신의 API 앱에 크게 의존합니다.
+
+그리고 앱마다 많이 달라질 수 있습니다.
+
+다음처럼 한두 줄의 코드일 수도 있습니다:
+
+```Python
+callback_url = "https://example.com/api/v1/invoices/events/"
+httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
+```
+
+하지만 콜백에서 가장 중요한 부분은, 당신의 API 사용자(외부 개발자)가 콜백 요청 body로 *당신의 API*가 보낼 데이터 등에 맞춰 *external API*를 올바르게 구현하도록 보장하는 것입니다.
+
+그래서 다음으로 할 일은, *당신의 API*에서 보내는 콜백을 받기 위해 그 *external API*가 어떤 형태여야 하는지 문서화하는 코드를 추가하는 것입니다.
+
+그 문서는 당신의 API에서 `/docs`의 Swagger UI에 표시되며, 외부 개발자들이 *external API*를 어떻게 만들어야 하는지 알 수 있게 해줍니다.
+
+이 예시는 콜백 자체(한 줄 코드로도 될 수 있음)를 구현하지 않고, 문서화 부분만 구현합니다.
+
+/// tip | 팁
+
+실제 콜백은 단지 HTTP 요청입니다.
+
+콜백을 직접 구현할 때는 HTTPX나 Requests 같은 것을 사용할 수 있습니다.
+
+///
+
+## 콜백 문서화 코드 작성하기 { #write-the-callback-documentation-code }
+
+이 코드는 앱에서 실행되지 않습니다. 그 *external API*가 어떤 형태여야 하는지 *문서화*하는 데만 필요합니다.
+
+하지만 **FastAPI**로 API의 자동 문서를 쉽게 생성하는 방법은 이미 알고 있습니다.
+
+따라서 그와 같은 지식을 사용해 *external API*가 어떻게 생겨야 하는지 문서화할 것입니다... 즉 외부 API가 구현해야 하는 *경로 처리(들)*(당신의 API가 호출할 것들)을 만들어서 말입니다.
+
+/// tip | 팁
+
+콜백을 문서화하는 코드를 작성할 때는, 자신이 그 *외부 개발자*라고 상상하는 것이 유용할 수 있습니다. 그리고 지금은 *당신의 API*가 아니라 *external API*를 구현하고 있다고 생각해 보세요.
+
+이 관점(외부 개발자의 관점)을 잠시 채택하면, 그 *external API*를 위해 파라미터, body용 Pydantic 모델, 응답 등을 어디에 두어야 하는지가 더 명확하게 느껴질 수 있습니다.
+
+///
+
+### 콜백 `APIRouter` 생성하기 { #create-a-callback-apirouter }
+
+먼저 하나 이상의 콜백을 담을 새 `APIRouter`를 만듭니다.
+
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
+
+### 콜백 *경로 처리* 생성하기 { #create-the-callback-path-operation }
+
+콜백 *경로 처리*를 만들려면 위에서 만든 동일한 `APIRouter`를 사용합니다.
+
+일반적인 FastAPI *경로 처리*처럼 보일 것입니다:
+
+* 아마도 받아야 할 body 선언이 있을 것입니다(예: `body: InvoiceEvent`).
+* 그리고 반환해야 할 응답 선언도 있을 수 있습니다(예: `response_model=InvoiceEventReceived`).
+
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *}
+
+일반적인 *경로 처리*와의 주요 차이점은 2가지입니다:
+
+* 실제 코드를 가질 필요가 없습니다. 당신의 앱은 이 코드를 절대 호출하지 않기 때문입니다. 이는 *external API*를 문서화하는 데만 사용됩니다. 따라서 함수는 그냥 `pass`만 있어도 됩니다.
+* *path*에는 OpenAPI 3 expression(자세한 내용은 아래 참고)이 포함될 수 있으며, 이를 통해 *당신의 API*로 보내진 원래 요청의 파라미터와 일부 값을 변수로 사용할 수 있습니다.
+
+### 콜백 경로 표현식 { #the-callback-path-expression }
+
+콜백 *path*는 *당신의 API*로 보내진 원래 요청의 일부를 포함할 수 있는 OpenAPI 3 expression을 가질 수 있습니다.
+
+이 경우, 다음 `str`입니다:
+
+```Python
+"{$callback_url}/invoices/{$request.body.id}"
+```
+
+따라서 당신의 API 사용자(외부 개발자)가 *당신의 API*로 다음 요청을 보내고:
+
+```
+https://yourapi.com/invoices/?callback_url=https://www.external.org/events
+```
+
+JSON body가 다음과 같다면:
+
+```JSON
+{
+ "id": "2expen51ve",
+ "customer": "Mr. Richie Rich",
+ "total": "9999"
+}
+```
+
+그러면 *당신의 API*는 청구서를 처리하고, 나중에 어느 시점에서 `callback_url`(즉 *external API*)로 콜백 요청을 보냅니다:
+
+```
+https://www.external.org/events/invoices/2expen51ve
+```
+
+그리고 다음과 같은 JSON body를 포함할 것입니다:
+
+```JSON
+{
+ "description": "Payment celebration",
+ "paid": true
+}
+```
+
+또한 그 *external API*로부터 다음과 같은 JSON body 응답을 기대합니다:
+
+```JSON
+{
+ "ok": true
+}
+```
+
+/// tip | 팁
+
+콜백 URL에는 `callback_url` 쿼리 파라미터로 받은 URL(`https://www.external.org/events`)뿐 아니라, JSON body 안의 청구서 `id`(`2expen51ve`)도 함께 사용된다는 점에 주목하세요.
+
+///
+
+### 콜백 라우터 추가하기 { #add-the-callback-router }
+
+이 시점에서, 위에서 만든 콜백 라우터 안에 *콜백 경로 처리(들)*(즉 *external developer*가 *external API*에 구현해야 하는 것들)을 준비했습니다.
+
+이제 *당신의 API 경로 처리 데코레이터*에서 `callbacks` 파라미터를 사용해, 그 콜백 라우터의 `.routes` 속성(실제로는 routes/*경로 처리*의 `list`)을 전달합니다:
+
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
+
+/// tip | 팁
+
+`callback=`에 라우터 자체(`invoices_callback_router`)를 넘기는 것이 아니라, `invoices_callback_router.routes`처럼 `.routes` 속성을 넘긴다는 점에 주목하세요.
+
+///
+
+### 문서 확인하기 { #check-the-docs }
+
+이제 앱을 실행하고 http://127.0.0.1:8000/docs로 이동하세요.
+
+*경로 처리*에 대해 "Callbacks" 섹션을 포함한 문서가 표시되며, *external API*가 어떤 형태여야 하는지 확인할 수 있습니다:
+
+
diff --git a/docs/ko/docs/advanced/openapi-webhooks.md b/docs/ko/docs/advanced/openapi-webhooks.md
new file mode 100644
index 000000000..89cacf7b7
--- /dev/null
+++ b/docs/ko/docs/advanced/openapi-webhooks.md
@@ -0,0 +1,55 @@
+# OpenAPI Webhooks { #openapi-webhooks }
+
+앱이 어떤 데이터와 함께 (요청을 보내서) *사용자의* 앱을 호출할 수 있고, 보통 어떤 **이벤트**를 **알리기** 위해 그렇게 할 수 있다는 것을 API **사용자**에게 알려야 하는 경우가 있습니다.
+
+이는 사용자가 여러분의 API로 요청을 보내는 일반적인 과정 대신, **여러분의 API**(또는 앱)가 **사용자의 시스템**(사용자의 API, 사용자의 앱)으로 **요청을 보낼 수 있다**는 의미입니다.
+
+이를 보통 **webhook**이라고 합니다.
+
+## Webhooks 단계 { #webhooks-steps }
+
+일반적인 과정은, 여러분이 코드에서 보낼 메시지, 즉 **요청 본문(body)**이 무엇인지 **정의**하는 것입니다.
+
+또한 여러분의 앱이 어떤 **시점**에 그 요청(또는 이벤트)을 보낼지도 어떤 방식으로든 정의합니다.
+
+그리고 **사용자**는 (예: 어딘가의 웹 대시보드에서) 여러분의 앱이 그 요청을 보내야 할 **URL**을 어떤 방식으로든 정의합니다.
+
+webhook의 URL을 등록하는 방법과 실제로 그 요청을 보내는 코드에 대한 모든 **로직**은 여러분에게 달려 있습니다. **여러분의 코드**에서 원하는 방식으로 작성하면 됩니다.
+
+## **FastAPI**와 OpenAPI로 webhooks 문서화하기 { #documenting-webhooks-with-fastapi-and-openapi }
+
+**FastAPI**에서는 OpenAPI를 사용해, 이러한 webhook의 이름, 여러분의 앱이 보낼 수 있는 HTTP 작업 타입(예: `POST`, `PUT` 등), 그리고 여러분의 앱이 보낼 요청 **본문(body)**을 정의할 수 있습니다.
+
+이렇게 하면 사용자가 여러분의 **webhook** 요청을 받기 위해 **자신들의 API를 구현**하기가 훨씬 쉬워지고, 경우에 따라서는 자신의 API 코드 일부를 자동 생성할 수도 있습니다.
+
+/// info | 정보
+
+Webhooks는 OpenAPI 3.1.0 이상에서 사용할 수 있으며, FastAPI `0.99.0` 이상에서 지원됩니다.
+
+///
+
+## webhooks가 있는 앱 { #an-app-with-webhooks }
+
+**FastAPI** 애플리케이션을 만들면, *경로 처리*를 정의하는 것과 같은 방식으로(예: `@app.webhooks.post()`), *webhooks*를 정의하는 데 사용할 수 있는 `webhooks` 속성이 있습니다.
+
+{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *}
+
+여러분이 정의한 webhook은 **OpenAPI** 스키마와 자동 **docs UI**에 포함됩니다.
+
+/// info | 정보
+
+`app.webhooks` 객체는 실제로 `APIRouter`일 뿐이며, 여러 파일로 앱을 구조화할 때 사용하는 것과 동일한 타입입니다.
+
+///
+
+webhook에서는 실제로(`/items/` 같은) *경로(path)*를 선언하지 않는다는 점에 유의하세요. 그곳에 전달하는 텍스트는 webhook의 **식별자**(이벤트 이름)일 뿐입니다. 예를 들어 `@app.webhooks.post("new-subscription")`에서 webhook 이름은 `new-subscription`입니다.
+
+이는 **사용자**가 webhook 요청을 받고 싶은 실제 **URL 경로**를 다른 방식(예: 웹 대시보드)으로 정의할 것이라고 기대하기 때문입니다.
+
+### 문서 확인하기 { #check-the-docs }
+
+이제 앱을 실행하고 http://127.0.0.1:8000/docs로 이동하세요.
+
+문서에 일반적인 *경로 처리*가 보이고, 이제는 일부 **webhooks**도 함께 보일 것입니다:
+
+
diff --git a/docs/ko/docs/advanced/path-operation-advanced-configuration.md b/docs/ko/docs/advanced/path-operation-advanced-configuration.md
new file mode 100644
index 000000000..f20fa6d26
--- /dev/null
+++ b/docs/ko/docs/advanced/path-operation-advanced-configuration.md
@@ -0,0 +1,172 @@
+# 경로 처리 고급 구성 { #path-operation-advanced-configuration }
+
+## OpenAPI operationId { #openapi-operationid }
+
+/// warning | 경고
+
+OpenAPI “전문가”가 아니라면, 아마 이 내용은 필요하지 않을 것입니다.
+
+///
+
+매개변수 `operation_id`를 사용해 *경로 처리*에 사용할 OpenAPI `operationId`를 설정할 수 있습니다.
+
+각 작업마다 고유하도록 보장해야 합니다.
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
+
+### *경로 처리 함수* 이름을 operationId로 사용하기 { #using-the-path-operation-function-name-as-the-operationid }
+
+API의 함수 이름을 `operationId`로 사용하고 싶다면, 모든 API를 순회하면서 `APIRoute.name`을 사용해 각 *경로 처리*의 `operation_id`를 덮어쓸 수 있습니다.
+
+모든 *경로 처리*를 추가한 뒤에 수행해야 합니다.
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *}
+
+/// tip | 팁
+
+`app.openapi()`를 수동으로 호출한다면, 그 전에 `operationId`들을 업데이트해야 합니다.
+
+///
+
+/// warning | 경고
+
+이렇게 할 경우, 각 *경로 처리 함수*의 이름이 고유하도록 보장해야 합니다.
+
+서로 다른 모듈(파이썬 파일)에 있어도 마찬가지입니다.
+
+///
+
+## OpenAPI에서 제외하기 { #exclude-from-openapi }
+
+생성된 OpenAPI 스키마(따라서 자동 문서화 시스템)에서 특정 *경로 처리*를 제외하려면, `include_in_schema` 매개변수를 `False`로 설정하세요:
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *}
+
+## docstring에서 고급 설명 가져오기 { #advanced-description-from-docstring }
+
+OpenAPI에 사용할 *경로 처리 함수*의 docstring 줄 수를 제한할 수 있습니다.
+
+`\f`(이스케이프된 "form feed" 문자)를 추가하면 **FastAPI**는 이 지점에서 OpenAPI에 사용할 출력 내용을 잘라냅니다.
+
+문서에는 표시되지 않지만, Sphinx 같은 다른 도구는 나머지 부분을 사용할 수 있습니다.
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
+
+## 추가 응답 { #additional-responses }
+
+*경로 처리*에 대해 `response_model`과 `status_code`를 선언하는 방법을 이미 보셨을 것입니다.
+
+이는 *경로 처리*의 기본 응답에 대한 메타데이터를 정의합니다.
+
+모델, 상태 코드 등과 함께 추가 응답도 선언할 수 있습니다.
+
+이에 대한 문서의 전체 장이 있으니, [OpenAPI의 추가 응답](additional-responses.md){.internal-link target=_blank}에서 읽어보세요.
+
+## OpenAPI Extra { #openapi-extra }
+
+애플리케이션에서 *경로 처리*를 선언하면, **FastAPI**는 OpenAPI 스키마에 포함될 해당 *경로 처리*의 관련 메타데이터를 자동으로 생성합니다.
+
+/// note | 기술 세부사항
+
+OpenAPI 명세에서는 이를 Operation Object라고 부릅니다.
+
+///
+
+여기에는 *경로 처리*에 대한 모든 정보가 있으며, 자동 문서를 생성하는 데 사용됩니다.
+
+`tags`, `parameters`, `requestBody`, `responses` 등이 포함됩니다.
+
+이 *경로 처리* 전용 OpenAPI 스키마는 보통 **FastAPI**가 자동으로 생성하지만, 확장할 수도 있습니다.
+
+/// tip | 팁
+
+이는 저수준 확장 지점입니다.
+
+추가 응답만 선언하면 된다면, 더 편리한 방법은 [OpenAPI의 추가 응답](additional-responses.md){.internal-link target=_blank}을 사용하는 것입니다.
+
+///
+
+`openapi_extra` 매개변수를 사용해 *경로 처리*의 OpenAPI 스키마를 확장할 수 있습니다.
+
+### OpenAPI 확장 { #openapi-extensions }
+
+예를 들어 `openapi_extra`는 [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions)를 선언하는 데 도움이 될 수 있습니다:
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *}
+
+자동 API 문서를 열면, 해당 특정 *경로 처리*의 하단에 확장이 표시됩니다.
+
+
+
+또한 API의 `/openapi.json`에서 결과 OpenAPI를 보면, 특정 *경로 처리*의 일부로 확장이 포함된 것도 확인할 수 있습니다:
+
+```JSON hl_lines="22"
+{
+ "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": {}
+ }
+ }
+ }
+ },
+ "x-aperture-labs-portal": "blue"
+ }
+ }
+ }
+}
+```
+
+### 사용자 정의 OpenAPI *경로 처리* 스키마 { #custom-openapi-path-operation-schema }
+
+`openapi_extra`의 딕셔너리는 *경로 처리*에 대해 자동으로 생성된 OpenAPI 스키마와 깊게 병합됩니다.
+
+따라서 자동 생성된 스키마에 추가 데이터를 더할 수 있습니다.
+
+예를 들어 Pydantic과 함께 FastAPI의 자동 기능을 사용하지 않고, 자체 코드로 요청을 읽고 검증하기로 결정할 수도 있지만, OpenAPI 스키마에는 여전히 그 요청을 정의하고 싶을 수 있습니다.
+
+그럴 때 `openapi_extra`를 사용할 수 있습니다:
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *}
+
+이 예시에서는 어떤 Pydantic 모델도 선언하지 않았습니다. 사실 요청 바디는 JSON으로 parsed되지도 않고, `bytes`로 직접 읽습니다. 그리고 함수 `magic_data_reader()`가 어떤 방식으로든 이를 파싱하는 역할을 담당합니다.
+
+그럼에도 불구하고, 요청 바디에 대해 기대하는 스키마를 선언할 수 있습니다.
+
+### 사용자 정의 OpenAPI 콘텐츠 타입 { #custom-openapi-content-type }
+
+같은 트릭을 사용하면, Pydantic 모델을 이용해 JSON Schema를 정의하고 이를 *경로 처리*의 사용자 정의 OpenAPI 스키마 섹션에 포함시킬 수 있습니다.
+
+요청의 데이터 타입이 JSON이 아니더라도 이렇게 할 수 있습니다.
+
+예를 들어 이 애플리케이션에서는 Pydantic 모델에서 JSON Schema를 추출하는 FastAPI의 통합 기능도, JSON에 대한 자동 검증도 사용하지 않습니다. 실제로 요청 콘텐츠 타입을 JSON이 아니라 YAML로 선언합니다:
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
+
+그럼에도 기본 통합 기능을 사용하지 않더라도, YAML로 받고자 하는 데이터에 대한 JSON Schema를 수동으로 생성하기 위해 Pydantic 모델을 여전히 사용합니다.
+
+그 다음 요청을 직접 사용하고, 바디를 `bytes`로 추출합니다. 이는 FastAPI가 요청 페이로드를 JSON으로 파싱하려고 시도조차 하지 않는다는 뜻입니다.
+
+그리고 코드에서 YAML 콘텐츠를 직접 파싱한 뒤, 다시 같은 Pydantic 모델을 사용해 YAML 콘텐츠를 검증합니다:
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
+
+/// tip | 팁
+
+여기서는 같은 Pydantic 모델을 재사용합니다.
+
+하지만 마찬가지로, 다른 방식으로 검증할 수도 있습니다.
+
+///
diff --git a/docs/ko/docs/advanced/response-change-status-code.md b/docs/ko/docs/advanced/response-change-status-code.md
index 1ba9aa3cc..4dfadde9d 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 327f20afe..eef74276f 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 08d63c43c..abf06bb18 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 e8abe0be2..1c36db9b9 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/security/http-basic-auth.md b/docs/ko/docs/advanced/security/http-basic-auth.md
new file mode 100644
index 000000000..611aad795
--- /dev/null
+++ b/docs/ko/docs/advanced/security/http-basic-auth.md
@@ -0,0 +1,107 @@
+# HTTP Basic Auth { #http-basic-auth }
+
+가장 단순한 경우에는 HTTP Basic Auth를 사용할 수 있습니다.
+
+HTTP Basic Auth에서는 애플리케이션이 사용자명과 비밀번호가 들어 있는 헤더를 기대합니다.
+
+이를 받지 못하면 HTTP 401 "Unauthorized" 오류를 반환합니다.
+
+그리고 값이 `Basic`이고 선택적으로 `realm` 파라미터를 포함하는 `WWW-Authenticate` 헤더를 반환합니다.
+
+이는 브라우저가 사용자명과 비밀번호를 입력하는 통합 프롬프트를 표시하도록 알려줍니다.
+
+그다음 사용자명과 비밀번호를 입력하면, 브라우저가 자동으로 해당 값을 헤더에 담아 전송합니다.
+
+## 간단한 HTTP Basic Auth { #simple-http-basic-auth }
+
+* `HTTPBasic`과 `HTTPBasicCredentials`를 임포트합니다.
+* `HTTPBasic`을 사용해 "`security` scheme"을 생성합니다.
+* *경로 처리*에서 dependency로 해당 `security`를 사용합니다.
+* `HTTPBasicCredentials` 타입의 객체를 반환합니다:
+ * 전송된 `username`과 `password`를 포함합니다.
+
+{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *}
+
+처음으로 URL을 열어보면(또는 문서에서 "Execute" 버튼을 클릭하면) 브라우저가 사용자명과 비밀번호를 물어봅니다:
+
+
+
+## 사용자명 확인하기 { #check-the-username }
+
+더 완전한 예시입니다.
+
+dependency를 사용해 사용자명과 비밀번호가 올바른지 확인하세요.
+
+이를 위해 Python 표준 모듈 `secrets`를 사용해 사용자명과 비밀번호를 확인합니다.
+
+`secrets.compare_digest()`는 `bytes` 또는 ASCII 문자(영어에서 사용하는 문자)만 포함한 `str`을 받아야 합니다. 즉, `Sebastián`의 `á` 같은 문자가 있으면 동작하지 않습니다.
+
+이를 처리하기 위해 먼저 `username`과 `password`를 UTF-8로 인코딩해서 `bytes`로 변환합니다.
+
+그런 다음 `secrets.compare_digest()`를 사용해 `credentials.username`이 `"stanleyjobson"`이고 `credentials.password`가 `"swordfish"`인지 확실히 확인할 수 있습니다.
+
+{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *}
+
+이는 다음과 비슷합니다:
+
+```Python
+if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
+ # Return some error
+ ...
+```
+
+하지만 `secrets.compare_digest()`를 사용하면 "timing attacks"라고 불리는 한 유형의 공격에 대해 안전해집니다.
+
+### Timing Attacks { #timing-attacks }
+
+그렇다면 "timing attack"이란 무엇일까요?
+
+공격자들이 사용자명과 비밀번호를 추측하려고 한다고 가정해봅시다.
+
+그리고 사용자명 `johndoe`, 비밀번호 `love123`으로 요청을 보냅니다.
+
+그러면 애플리케이션의 Python 코드는 대략 다음과 같을 것입니다:
+
+```Python
+if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
+ ...
+```
+
+하지만 Python이 `johndoe`의 첫 글자 `j`를 `stanleyjobson`의 첫 글자 `s`와 비교하는 순간, 두 문자열이 같지 않다는 것을 이미 알게 되어 `False`를 반환합니다. 이는 “나머지 글자들을 비교하느라 계산을 더 낭비할 필요가 없다”고 판단하기 때문입니다. 그리고 애플리케이션은 "Incorrect username or password"라고 말합니다.
+
+그런데 공격자들이 사용자명을 `stanleyjobsox`, 비밀번호를 `love123`으로 다시 시도합니다.
+
+그러면 애플리케이션 코드는 다음과 비슷하게 동작합니다:
+
+```Python
+if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
+ ...
+```
+
+Python은 두 문자열이 같지 않다는 것을 알아차리기 전까지 `stanleyjobsox`와 `stanleyjobson` 양쪽의 `stanleyjobso` 전체를 비교해야 합니다. 그래서 "Incorrect username or password"라고 응답하기까지 추가로 몇 마이크로초가 더 걸릴 것입니다.
+
+#### 응답 시간은 공격자에게 도움이 됩니다 { #the-time-to-answer-helps-the-attackers }
+
+이 시점에서 서버가 "Incorrect username or password" 응답을 보내는 데 몇 마이크로초 더 걸렸다는 것을 알아채면, 공격자들은 _무언가_ 맞았다는 것(초기 몇 글자가 맞았다는 것)을 알게 됩니다.
+
+그리고 `johndoe`보다는 `stanleyjobsox`에 더 가까운 값을 시도해야 한다는 것을 알고 다시 시도할 수 있습니다.
+
+#### "전문적인" 공격 { #a-professional-attack }
+
+물론 공격자들은 이런 작업을 손으로 하지 않습니다. 보통 초당 수천~수백만 번 테스트할 수 있는 프로그램을 작성할 것이고, 한 번에 정답 글자 하나씩 추가로 얻어낼 수 있습니다.
+
+그렇게 하면 몇 분 또는 몇 시간 만에, 응답에 걸린 시간만을 이용해(우리 애플리케이션의 “도움”을 받아) 올바른 사용자명과 비밀번호를 추측할 수 있게 됩니다.
+
+#### `secrets.compare_digest()`로 해결하기 { #fix-it-with-secrets-compare-digest }
+
+하지만 우리 코드는 실제로 `secrets.compare_digest()`를 사용하고 있습니다.
+
+요약하면, `stanleyjobsox`와 `stanleyjobson`을 비교하는 데 걸리는 시간은 `johndoe`와 `stanleyjobson`을 비교하는 데 걸리는 시간과 같아집니다. 비밀번호도 마찬가지입니다.
+
+이렇게 애플리케이션 코드에서 `secrets.compare_digest()`를 사용하면, 이러한 범위의 보안 공격 전반에 대해 안전해집니다.
+
+### 오류 반환하기 { #return-the-error }
+
+자격 증명이 올바르지 않다고 판단되면, 상태 코드 401(자격 증명이 제공되지 않았을 때와 동일)을 사용하는 `HTTPException`을 반환하고 브라우저가 로그인 프롬프트를 다시 표시하도록 `WWW-Authenticate` 헤더를 추가하세요:
+
+{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *}
diff --git a/docs/ko/docs/advanced/security/index.md b/docs/ko/docs/advanced/security/index.md
new file mode 100644
index 000000000..4c7abfacc
--- /dev/null
+++ b/docs/ko/docs/advanced/security/index.md
@@ -0,0 +1,19 @@
+# 고급 보안 { #advanced-security }
+
+## 추가 기능 { #additional-features }
+
+[튜토리얼 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank}에서 다룬 내용 외에도, 보안을 처리하기 위한 몇 가지 추가 기능이 있습니다.
+
+/// tip | 팁
+
+다음 섹션들은 **반드시 "고급"이라고 할 수는 없습니다**.
+
+그리고 사용 사례에 따라, 그중 하나에 해결책이 있을 수도 있습니다.
+
+///
+
+## 먼저 튜토리얼 읽기 { #read-the-tutorial-first }
+
+다음 섹션은 주요 [튜토리얼 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank}을 이미 읽었다고 가정합니다.
+
+모두 동일한 개념을 기반으로 하지만, 몇 가지 추가 기능을 사용할 수 있게 해줍니다.
diff --git a/docs/ko/docs/advanced/security/oauth2-scopes.md b/docs/ko/docs/advanced/security/oauth2-scopes.md
new file mode 100644
index 000000000..0f90f92ae
--- /dev/null
+++ b/docs/ko/docs/advanced/security/oauth2-scopes.md
@@ -0,0 +1,274 @@
+# OAuth2 스코프 { #oauth2-scopes }
+
+**FastAPI**에서 OAuth2 스코프를 직접 사용할 수 있으며, 자연스럽게 동작하도록 통합되어 있습니다.
+
+이를 통해 OAuth2 표준을 따르는 더 세밀한 권한 시스템을 OpenAPI 애플리케이션(및 API 문서)에 통합할 수 있습니다.
+
+스코프를 사용하는 OAuth2는 Facebook, Google, GitHub, Microsoft, X(Twitter) 등 많은 대형 인증 제공자가 사용하는 메커니즘입니다. 이들은 이를 통해 사용자와 애플리케이션에 특정 권한을 제공합니다.
+
+Facebook, Google, GitHub, Microsoft, X(Twitter)로 “로그인”할 때마다, 해당 애플리케이션은 스코프가 있는 OAuth2를 사용하고 있습니다.
+
+이 섹션에서는 **FastAPI** 애플리케이션에서 동일한 “스코프가 있는 OAuth2”로 인증(Authentication)과 인가(Authorization)를 관리하는 방법을 확인합니다.
+
+/// warning | 경고
+
+이 섹션은 다소 고급 내용입니다. 이제 막 시작했다면 건너뛰어도 됩니다.
+
+OAuth2 스코프가 반드시 필요한 것은 아니며, 인증과 인가는 원하는 방식으로 처리할 수 있습니다.
+
+하지만 스코프가 있는 OAuth2는 (OpenAPI와 함께) API 및 API 문서에 깔끔하게 통합될 수 있습니다.
+
+그럼에도 불구하고, 해당 스코프(또는 그 밖의 어떤 보안/인가 요구사항이든)는 코드에서 필요에 맞게 직접 강제해야 합니다.
+
+많은 경우 스코프가 있는 OAuth2는 과한 선택일 수 있습니다.
+
+하지만 필요하다고 알고 있거나 궁금하다면 계속 읽어보세요.
+
+///
+
+## OAuth2 스코프와 OpenAPI { #oauth2-scopes-and-openapi }
+
+OAuth2 사양은 “스코프(scopes)”를 공백으로 구분된 문자열 목록으로 정의합니다.
+
+각 문자열의 내용은 어떤 형식이든 될 수 있지만, 공백을 포함하면 안 됩니다.
+
+이 스코프들은 “권한”을 나타냅니다.
+
+OpenAPI(예: API 문서)에서는 “security schemes”를 정의할 수 있습니다.
+
+이 security scheme 중 하나가 OAuth2를 사용한다면, 스코프도 선언하고 사용할 수 있습니다.
+
+각 “스코프”는 (공백 없는) 문자열일 뿐입니다.
+
+보통 다음과 같이 특정 보안 권한을 선언하는 데 사용합니다:
+
+* `users:read` 또는 `users:write` 는 흔한 예시입니다.
+* `instagram_basic` 는 Facebook/Instagram에서 사용합니다.
+* `https://www.googleapis.com/auth/drive` 는 Google에서 사용합니다.
+
+/// info | 정보
+
+OAuth2에서 “스코프”는 필요한 특정 권한을 선언하는 문자열일 뿐입니다.
+
+`:` 같은 다른 문자가 있거나 URL이어도 상관없습니다.
+
+그런 세부사항은 구현에 따라 달라집니다.
+
+OAuth2 입장에서는 그저 문자열입니다.
+
+///
+
+## 전체 개요 { #global-view }
+
+먼저, 메인 **튜토리얼 - 사용자 가이드**의 [비밀번호(및 해싱)를 사용하는 OAuth2, JWT 토큰을 사용하는 Bearer](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank} 예제에서 어떤 부분이 바뀌는지 빠르게 살펴보겠습니다. 이제 OAuth2 스코프를 사용합니다:
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *}
+
+이제 변경 사항을 단계별로 살펴보겠습니다.
+
+## OAuth2 보안 스킴 { #oauth2-security-scheme }
+
+첫 번째 변경 사항은 이제 사용 가능한 스코프 2개(`me`, `items`)로 OAuth2 보안 스킴을 선언한다는 점입니다.
+
+`scopes` 매개변수는 각 스코프를 키로 하고, 설명을 값으로 하는 `dict`를 받습니다:
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *}
+
+이제 스코프를 선언했기 때문에, 로그인/인가할 때 API 문서에 스코프가 표시됩니다.
+
+그리고 접근을 허용할 스코프를 선택할 수 있게 됩니다: `me`와 `items`.
+
+이는 Facebook, Google, GitHub 등으로 로그인하면서 권한을 부여할 때 사용되는 것과 동일한 메커니즘입니다:
+
+
+
+## 스코프를 포함한 JWT 토큰 { #jwt-token-with-scopes }
+
+이제 토큰 *경로 처리*를 수정해, 요청된 스코프를 반환하도록 합니다.
+
+여전히 동일한 `OAuth2PasswordRequestForm`을 사용합니다. 여기에는 요청에서 받은 각 스코프를 담는 `scopes` 속성이 있으며, 타입은 `str`의 `list`입니다.
+
+그리고 JWT 토큰의 일부로 스코프를 반환합니다.
+
+/// danger | 위험
+
+단순화를 위해, 여기서는 요청으로 받은 스코프를 그대로 토큰에 추가하고 있습니다.
+
+하지만 실제 애플리케이션에서는 보안을 위해, 사용자가 실제로 가질 수 있는 스코프만(또는 미리 정의한 것만) 추가하도록 반드시 확인해야 합니다.
+
+///
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}
+
+## *경로 처리*와 의존성에서 스코프 선언하기 { #declare-scopes-in-path-operations-and-dependencies }
+
+이제 `/users/me/items/`에 대한 *경로 처리*가 스코프 `items`를 요구한다고 선언합니다.
+
+이를 위해 `fastapi`에서 `Security`를 import하여 사용합니다.
+
+`Security`는 (`Depends`처럼) 의존성을 선언하는 데 사용할 수 있지만, `Security`는 스코프(문자열) 목록을 받는 `scopes` 매개변수도 받습니다.
+
+이 경우, 의존성 함수 `get_current_active_user`를 `Security`에 전달합니다(`Depends`로 할 때와 같은 방식).
+
+하지만 스코프 `list`도 함께 전달합니다. 여기서는 스코프 하나만: `items`(더 많을 수도 있습니다).
+
+또한 의존성 함수 `get_current_active_user`는 `Depends`뿐 아니라 `Security`로도 하위 의존성을 선언할 수 있습니다. 자체 하위 의존성 함수(`get_current_user`)와 추가 스코프 요구사항을 선언합니다.
+
+이 경우에는 스코프 `me`를 요구합니다(여러 스코프를 요구할 수도 있습니다).
+
+/// note | 참고
+
+반드시 서로 다른 곳에 서로 다른 스코프를 추가해야 하는 것은 아닙니다.
+
+여기서는 **FastAPI**가 서로 다른 레벨에서 선언된 스코프를 어떻게 처리하는지 보여주기 위해 이렇게 합니다.
+
+///
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}
+
+/// info | 기술 세부사항
+
+`Security`는 실제로 `Depends`의 서브클래스이며, 나중에 보게 될 추가 매개변수 하나만 더 있습니다.
+
+하지만 `Depends` 대신 `Security`를 사용하면, **FastAPI**는 보안 스코프를 선언할 수 있음을 알고 내부적으로 이를 사용하며, OpenAPI로 API를 문서화할 수 있습니다.
+
+하지만 `fastapi`에서 `Query`, `Path`, `Depends`, `Security` 등을 import할 때, 이것들은 실제로 특수한 클래스를 반환하는 함수입니다.
+
+///
+
+## `SecurityScopes` 사용하기 { #use-securityscopes }
+
+이제 의존성 `get_current_user`를 업데이트합니다.
+
+이는 위의 의존성들이 사용하는 것입니다.
+
+여기에서 앞서 만든 동일한 OAuth2 스킴을 의존성으로 선언하여 사용합니다: `oauth2_scheme`.
+
+이 의존성 함수 자체에는 스코프 요구사항이 없기 때문에, `oauth2_scheme`와 함께 `Depends`를 사용할 수 있습니다. 보안 스코프를 지정할 필요가 없을 때는 `Security`를 쓸 필요가 없습니다.
+
+또한 `fastapi.security`에서 import한 `SecurityScopes` 타입의 특별한 매개변수를 선언합니다.
+
+이 `SecurityScopes` 클래스는 `Request`와 비슷합니다(`Request`는 요청 객체를 직접 얻기 위해 사용했습니다).
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}
+
+## `scopes` 사용하기 { #use-the-scopes }
+
+매개변수 `security_scopes`의 타입은 `SecurityScopes`입니다.
+
+여기에는 `scopes` 속성이 있으며, 자기 자신과 이 함수를 하위 의존성으로 사용하는 모든 의존성이 요구하는 스코프 전체를 담은 `list`를 가집니다. 즉, 모든 “dependants”... 다소 헷갈릴 수 있는데, 아래에서 다시 설명합니다.
+
+`security_scopes` 객체(`SecurityScopes` 클래스)에는 또한 `scope_str` 속성이 있는데, 공백으로 구분된 단일 문자열로 스코프들을 담고 있습니다(이를 사용할 것입니다).
+
+나중에 여러 지점에서 재사용(`raise`)할 수 있는 `HTTPException`을 생성합니다.
+
+이 예외에는 필요한 스코프(있다면)를 공백으로 구분된 문자열(`scope_str`)로 포함합니다. 그리고 그 스코프 문자열을 `WWW-Authenticate` 헤더에 넣습니다(이는 사양의 일부입니다).
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}
+
+## `username`과 데이터 형태 검증하기 { #verify-the-username-and-data-shape }
+
+`username`을 얻었는지 확인하고, 스코프를 추출합니다.
+
+그런 다음 Pydantic 모델로 데이터를 검증합니다(`ValidationError` 예외를 잡습니다). JWT 토큰을 읽거나 Pydantic으로 데이터를 검증하는 과정에서 오류가 나면, 앞에서 만든 `HTTPException`을 raise합니다.
+
+이를 위해 Pydantic 모델 `TokenData`에 새 속성 `scopes`를 추가합니다.
+
+Pydantic으로 데이터를 검증하면, 예를 들어 스코프가 정확히 `str`의 `list`이고 `username`이 `str`인지 등을 보장할 수 있습니다.
+
+예를 들어 `dict`나 다른 형태라면, 나중에 애플리케이션이 어느 시점에 깨지면서 보안 위험이 될 수 있습니다.
+
+또한 해당 username을 가진 사용자가 있는지 확인하고, 없다면 앞에서 만든 동일한 예외를 raise합니다.
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}
+
+## `scopes` 검증하기 { #verify-the-scopes }
+
+이제 이 의존성과 모든 dependant( *경로 처리* 포함)가 요구하는 모든 스코프가, 받은 토큰의 스코프에 포함되어 있는지 확인합니다. 그렇지 않으면 `HTTPException`을 raise합니다.
+
+이를 위해, 모든 스코프를 `str`로 담고 있는 `security_scopes.scopes`를 사용합니다.
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}
+
+## 의존성 트리와 스코프 { #dependency-tree-and-scopes }
+
+이 의존성 트리와 스코프를 다시 살펴보겠습니다.
+
+`get_current_active_user` 의존성은 `get_current_user`를 하위 의존성으로 가지므로, `get_current_active_user`에서 선언된 스코프 `"me"`는 `get_current_user`에 전달되는 `security_scopes.scopes`의 요구 스코프 목록에 포함됩니다.
+
+*경로 처리* 자체도 스코프 `"items"`를 선언하므로, 이것 또한 `get_current_user`에 전달되는 `security_scopes.scopes` 목록에 포함됩니다.
+
+의존성과 스코프의 계층 구조는 다음과 같습니다:
+
+* *경로 처리* `read_own_items`는:
+ * 의존성과 함께 요구 스코프 `["items"]`를 가집니다:
+ * `get_current_active_user`:
+ * 의존성 함수 `get_current_active_user`는:
+ * 의존성과 함께 요구 스코프 `["me"]`를 가집니다:
+ * `get_current_user`:
+ * 의존성 함수 `get_current_user`는:
+ * 자체적으로는 요구 스코프가 없습니다.
+ * `oauth2_scheme`를 사용하는 의존성이 있습니다.
+ * `SecurityScopes` 타입의 `security_scopes` 매개변수가 있습니다:
+ * 이 `security_scopes` 매개변수는 위에서 선언된 모든 스코프를 담은 `list`인 `scopes` 속성을 가지므로:
+ * *경로 처리* `read_own_items`의 경우 `security_scopes.scopes`에는 `["me", "items"]`가 들어갑니다.
+ * *경로 처리* `read_users_me`의 경우 `security_scopes.scopes`에는 `["me"]`가 들어갑니다. 이는 의존성 `get_current_active_user`에서 선언되기 때문입니다.
+ * *경로 처리* `read_system_status`의 경우 `security_scopes.scopes`에는 `[]`(없음)가 들어갑니다. `scopes`가 있는 `Security`를 선언하지 않았고, 그 의존성인 `get_current_user`도 `scopes`를 선언하지 않았기 때문입니다.
+
+/// tip | 팁
+
+여기서 중요한 “마법 같은” 점은 `get_current_user`가 각 *경로 처리*마다 검사해야 하는 `scopes` 목록이 달라진다는 것입니다.
+
+이는 특정 *경로 처리*에 대한 의존성 트리에서, 각 *경로 처리*와 각 의존성에 선언된 `scopes`에 따라 달라집니다.
+
+///
+
+## `SecurityScopes`에 대한 추가 설명 { #more-details-about-securityscopes }
+
+`SecurityScopes`는 어느 지점에서든, 그리고 여러 곳에서 사용할 수 있으며, “루트” 의존성에만 있어야 하는 것은 아닙니다.
+
+`SecurityScopes`는 **해당 특정** *경로 처리*와 **해당 특정** 의존성 트리에 대해, 현재 `Security` 의존성과 모든 dependant에 선언된 보안 스코프를 항상 갖고 있습니다.
+
+`SecurityScopes`에는 dependant가 선언한 모든 스코프가 포함되므로, 중앙의 의존성 함수에서 토큰이 필요한 스코프를 가지고 있는지 검증한 다음, 서로 다른 *경로 처리*에서 서로 다른 스코프 요구사항을 선언할 수 있습니다.
+
+이들은 각 *경로 처리*마다 독립적으로 검사됩니다.
+
+## 확인하기 { #check-it }
+
+API 문서를 열면, 인증하고 인가할 스코프를 지정할 수 있습니다.
+
+
+
+어떤 스코프도 선택하지 않으면 “인증”은 되지만, `/users/me/` 또는 `/users/me/items/`에 접근하려고 하면 권한이 충분하지 않다는 오류가 발생합니다. `/status/`에는 여전히 접근할 수 있습니다.
+
+그리고 스코프 `me`는 선택했지만 스코프 `items`는 선택하지 않았다면, `/users/me/`에는 접근할 수 있지만 `/users/me/items/`에는 접근할 수 없습니다.
+
+이는 사용자가 애플리케이션에 얼마나 많은 권한을 부여했는지에 따라, 제3자 애플리케이션이 사용자로부터 제공받은 토큰으로 이 *경로 처리*들 중 하나에 접근하려고 할 때 발생하는 상황과 같습니다.
+
+## 제3자 통합에 대해 { #about-third-party-integrations }
+
+이 예제에서는 OAuth2 “password” 플로우를 사용하고 있습니다.
+
+이는 보통 자체 프론트엔드가 있는 우리 애플리케이션에 로그인할 때 적합합니다.
+
+우리가 이를 통제하므로 `username`과 `password`를 받는 것을 신뢰할 수 있기 때문입니다.
+
+하지만 다른 사람들이 연결할 OAuth2 애플리케이션(즉, Facebook, Google, GitHub 등과 동등한 인증 제공자를 만들고 있다면)을 구축한다면, 다른 플로우 중 하나를 사용해야 합니다.
+
+가장 흔한 것은 implicit 플로우입니다.
+
+가장 안전한 것은 code 플로우이지만, 더 많은 단계가 필요해 구현이 더 복잡합니다. 복잡하기 때문에 많은 제공자는 implicit 플로우를 권장하게 됩니다.
+
+/// note | 참고
+
+인증 제공자마다 자신들의 브랜드의 일부로 만들기 위해, 각 플로우를 서로 다른 방식으로 이름 붙이는 경우가 흔합니다.
+
+하지만 결국, 동일한 OAuth2 표준을 구현하고 있는 것입니다.
+
+///
+
+**FastAPI**는 `fastapi.security.oauth2`에 이러한 모든 OAuth2 인증 플로우를 위한 유틸리티를 포함합니다.
+
+## 데코레이터 `dependencies`에서의 `Security` { #security-in-decorator-dependencies }
+
+[경로 처리 데코레이터의 의존성](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}에서 설명한 것처럼 데코레이터의 `dependencies` 매개변수에 `Depends`의 `list`를 정의할 수 있는 것과 같은 방식으로, 거기에서 `scopes`와 함께 `Security`를 사용할 수도 있습니다.
diff --git a/docs/ko/docs/advanced/settings.md b/docs/ko/docs/advanced/settings.md
new file mode 100644
index 000000000..6fa7c644c
--- /dev/null
+++ b/docs/ko/docs/advanced/settings.md
@@ -0,0 +1,302 @@
+# 설정과 환경 변수 { #settings-and-environment-variables }
+
+많은 경우 애플리케이션에는 외부 설정이나 구성(예: secret key, 데이터베이스 자격 증명, 이메일 서비스 자격 증명 등)이 필요할 수 있습니다.
+
+이러한 설정 대부분은 데이터베이스 URL처럼 변동 가능(변경될 수 있음)합니다. 그리고 많은 설정은 secret처럼 민감할 수 있습니다.
+
+이 때문에 보통 애플리케이션이 읽어들이는 환경 변수로 이를 제공하는 것이 일반적입니다.
+
+/// tip | 팁
+
+환경 변수를 이해하려면 [환경 변수](../environment-variables.md){.internal-link target=_blank}를 읽어보세요.
+
+///
+
+## 타입과 검증 { #types-and-validation }
+
+이 환경 변수들은 Python 외부에 있으며 다른 프로그램 및 시스템의 나머지 부분(그리고 Linux, Windows, macOS 같은 서로 다른 운영체제와도)과 호환되어야 하므로, 텍스트 문자열만 다룰 수 있습니다.
+
+즉, Python에서 환경 변수로부터 읽어온 어떤 값이든 `str`이 되며, 다른 타입으로의 변환이나 검증은 코드에서 수행해야 합니다.
+
+## Pydantic `Settings` { #pydantic-settings }
+
+다행히 Pydantic은 Pydantic: Settings management를 통해 환경 변수에서 오는 이러한 설정을 처리할 수 있는 훌륭한 유틸리티를 제공합니다.
+
+### `pydantic-settings` 설치하기 { #install-pydantic-settings }
+
+먼저 [가상 환경](../virtual-environments.md){.internal-link target=_blank}을 만들고 활성화한 다음, `pydantic-settings` 패키지를 설치하세요:
+
+
+
-다음으로, 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 4cb4cbe0d..fffffa6a5 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 처리.
+* `WebSocket` 클래스.
+* 클래스 기반 WebSocket 처리.
diff --git a/docs/ko/docs/advanced/wsgi.md b/docs/ko/docs/advanced/wsgi.md
index 3e9de3e6c..89cf57cfe 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/alternatives.md b/docs/ko/docs/alternatives.md
new file mode 100644
index 000000000..d8c2df2d8
--- /dev/null
+++ b/docs/ko/docs/alternatives.md
@@ -0,0 +1,485 @@
+# 대안, 영감, 비교 { #alternatives-inspiration-and-comparisons }
+
+**FastAPI**에 영감을 준 것들, 대안과의 비교, 그리고 그로부터 무엇을 배웠는지에 대한 내용입니다.
+
+## 소개 { #intro }
+
+다른 사람들의 이전 작업이 없었다면 **FastAPI**는 존재하지 않았을 것입니다.
+
+그 전에 만들어진 많은 도구들이 **FastAPI**의 탄생에 영감을 주었습니다.
+
+저는 여러 해 동안 새로운 framework를 만드는 것을 피하고 있었습니다. 먼저 **FastAPI**가 다루는 모든 기능을 여러 서로 다른 framework, plug-in, 도구를 사용해 해결해 보려고 했습니다.
+
+하지만 어느 시점에는, 이전 도구들의 가장 좋은 아이디어를 가져와 가능한 최선의 방식으로 조합하고, 이전에는 존재하지 않았던 언어 기능(Python 3.6+ type hints)을 활용해 이 모든 기능을 제공하는 무언가를 만드는 것 외에는 다른 선택지가 없었습니다.
+
+## 이전 도구들 { #previous-tools }
+
+### Django { #django }
+
+가장 인기 있는 Python framework이며 널리 신뢰받고 있습니다. Instagram 같은 시스템을 만드는 데 사용됩니다.
+
+상대적으로 관계형 데이터베이스(예: MySQL 또는 PostgreSQL)와 강하게 결합되어 있어서, NoSQL 데이터베이스(예: Couchbase, MongoDB, Cassandra 등)를 주 저장 엔진으로 사용하는 것은 그리 쉽지 않습니다.
+
+백엔드에서 HTML을 생성하기 위해 만들어졌지, 현대적인 프런트엔드(예: React, Vue.js, Angular)나 다른 시스템(예: IoT 기기)에서 사용되는 API를 만들기 위해 설계된 것은 아닙니다.
+
+### Django REST Framework { #django-rest-framework }
+
+Django REST framework는 Django를 기반으로 Web API를 구축하기 위한 유연한 toolkit으로 만들어졌고, Django의 API 기능을 개선하기 위한 목적이었습니다.
+
+Mozilla, Red Hat, Eventbrite를 포함해 많은 회사에서 사용합니다.
+
+**자동 API 문서화**의 초기 사례 중 하나였고, 이것이 특히 **FastAPI**를 "찾게 된" 첫 아이디어 중 하나였습니다.
+
+/// note | 참고
+
+Django REST Framework는 Tom Christie가 만들었습니다. **FastAPI**의 기반이 되는 Starlette와 Uvicorn을 만든 사람과 동일합니다.
+
+///
+
+/// check | **FastAPI**에 영감을 준 것
+
+자동 API 문서화 웹 사용자 인터페이스를 제공하기.
+
+///
+
+### Flask { #flask }
+
+Flask는 "microframework"로, Django에 기본으로 포함된 데이터베이스 통합이나 여러 기능들을 포함하지 않습니다.
+
+이 단순함과 유연성 덕분에 NoSQL 데이터베이스를 주 데이터 저장 시스템으로 사용하는 같은 작업이 가능합니다.
+
+매우 단순하기 때문에 비교적 직관적으로 배울 수 있지만, 문서가 어떤 지점에서는 다소 기술적으로 깊어지기도 합니다.
+
+또한 데이터베이스, 사용자 관리, 혹은 Django에 미리 구축되어 있는 다양한 기능들이 꼭 필요하지 않은 다른 애플리케이션에도 흔히 사용됩니다. 물론 이런 기능들 중 다수는 plug-in으로 추가할 수 있습니다.
+
+이런 구성요소의 분리와, 필요한 것만 정확히 덧붙여 확장할 수 있는 "microframework"라는 점은 제가 유지하고 싶었던 핵심 특성이었습니다.
+
+Flask의 단순함을 고려하면 API를 구축하는 데 잘 맞는 것처럼 보였습니다. 다음으로 찾고자 했던 것은 Flask용 "Django REST Framework"였습니다.
+
+/// check | **FastAPI**에 영감을 준 것
+
+micro-framework가 되기. 필요한 도구와 구성요소를 쉽게 조합할 수 있도록 하기.
+
+단순하고 사용하기 쉬운 routing 시스템을 갖기.
+
+///
+
+### Requests { #requests }
+
+**FastAPI**는 실제로 **Requests**의 대안이 아닙니다. 둘의 범위는 매우 다릅니다.
+
+실제로 FastAPI 애플리케이션 *내부에서* Requests를 사용하는 경우도 흔합니다.
+
+그럼에도 FastAPI는 Requests로부터 꽤 많은 영감을 얻었습니다.
+
+**Requests**는 (클라이언트로서) API와 *상호작용*하기 위한 라이브러리이고, **FastAPI**는 (서버로서) API를 *구축*하기 위한 라이브러리입니다.
+
+대략 말하면 서로 반대편에 있으며, 서로를 보완합니다.
+
+Requests는 매우 단순하고 직관적인 설계를 가졌고, 합리적인 기본값을 바탕으로 사용하기가 아주 쉽습니다. 동시에 매우 강력하고 커스터마이징도 가능합니다.
+
+그래서 공식 웹사이트에서 말하듯이:
+
+> Requests is one of the most downloaded Python packages of all time
+
+사용 방법은 매우 간단합니다. 예를 들어 `GET` 요청을 하려면 다음처럼 작성합니다:
+
+```Python
+response = requests.get("http://example.com/some/url")
+```
+
+이에 대응하는 FastAPI의 API *경로 처리*는 다음과 같이 보일 수 있습니다:
+
+```Python hl_lines="1"
+@app.get("/some/url")
+def read_url():
+ return {"message": "Hello World"}
+```
+
+`requests.get(...)`와 `@app.get(...)`의 유사성을 확인해 보세요.
+
+/// check | **FastAPI**에 영감을 준 것
+
+* 단순하고 직관적인 API를 갖기.
+* HTTP method 이름(operations)을 직접, 직관적이고 명확한 방식으로 사용하기.
+* 합리적인 기본값을 제공하되, 강력한 커스터마이징을 가능하게 하기.
+
+///
+
+### Swagger / OpenAPI { #swagger-openapi }
+
+제가 Django REST Framework에서 가장 원했던 주요 기능은 자동 API 문서화였습니다.
+
+그 후 JSON(또는 JSON의 확장인 YAML)을 사용해 API를 문서화하는 표준인 Swagger가 있다는 것을 알게 되었습니다.
+
+그리고 Swagger API를 위한 웹 사용자 인터페이스도 이미 만들어져 있었습니다. 그래서 API에 대한 Swagger 문서를 생성할 수 있다면, 이 웹 사용자 인터페이스를 자동으로 사용할 수 있게 됩니다.
+
+어느 시점에 Swagger는 Linux Foundation으로 넘어가 OpenAPI로 이름이 바뀌었습니다.
+
+그래서 2.0 버전을 이야기할 때는 "Swagger"라고 말하는 것이 일반적이고, 3+ 버전은 "OpenAPI"라고 말하는 것이 일반적입니다.
+
+/// check | **FastAPI**에 영감을 준 것
+
+커스텀 schema 대신, API 사양을 위한 열린 표준을 채택하고 사용하기.
+
+또한 표준 기반의 사용자 인터페이스 도구를 통합하기:
+
+* Swagger UI
+* ReDoc
+
+이 두 가지는 꽤 대중적이고 안정적이기 때문에 선택되었습니다. 하지만 간단히 검색해보면 OpenAPI를 위한 대안 UI가 수십 가지나 있다는 것을 알 수 있습니다(**FastAPI**와 함께 사용할 수 있습니다).
+
+///
+
+### Flask REST framework들 { #flask-rest-frameworks }
+
+Flask REST framework는 여러 개가 있지만, 시간을 들여 조사해 본 결과, 상당수가 중단되었거나 방치되어 있었고, 해결되지 않은 여러 이슈 때문에 적합하지 않은 경우가 많았습니다.
+
+### Marshmallow { #marshmallow }
+
+API 시스템에 필요한 주요 기능 중 하나는 데이터 "serialization"입니다. 이는 코드(Python)에서 데이터를 가져와 네트워크로 전송할 수 있는 형태로 변환하는 것을 의미합니다. 예를 들어 데이터베이스의 데이터를 담은 객체를 JSON 객체로 변환하거나, `datetime` 객체를 문자열로 변환하는 등의 작업입니다.
+
+API에 또 하나 크게 필요한 기능은 데이터 검증입니다. 특정 파라미터를 기준으로 데이터가 유효한지 확인하는 것입니다. 예를 들어 어떤 필드가 `int`인지, 임의의 문자열이 아닌지 확인하는 식입니다. 이는 특히 들어오는 데이터에 유용합니다.
+
+데이터 검증 시스템이 없다면, 모든 검사를 코드에서 수동으로 해야 합니다.
+
+이런 기능들을 제공하기 위해 Marshmallow가 만들어졌습니다. 훌륭한 라이브러리이며, 저도 이전에 많이 사용했습니다.
+
+하지만 Python type hints가 존재하기 전에 만들어졌습니다. 그래서 각 schema를 정의하려면 Marshmallow가 제공하는 특정 유틸리티와 클래스를 사용해야 합니다.
+
+/// check | **FastAPI**에 영감을 준 것
+
+데이터 타입과 검증을 제공하는 "schema"를 코드로 정의하고, 이를 자동으로 활용하기.
+
+///
+
+### Webargs { #webargs }
+
+API에 필요한 또 다른 큰 기능은 들어오는 요청에서 데이터를 parsing하는 것입니다.
+
+Webargs는 Flask를 포함한 여러 framework 위에서 이를 제공하기 위해 만들어진 도구입니다.
+
+내부적으로 Marshmallow를 사용해 데이터 검증을 수행합니다. 그리고 같은 개발자들이 만들었습니다.
+
+아주 훌륭한 도구이며, 저도 **FastAPI**를 만들기 전에 많이 사용했습니다.
+
+/// info | 정보
+
+Webargs는 Marshmallow와 같은 개발자들이 만들었습니다.
+
+///
+
+/// check | **FastAPI**에 영감을 준 것
+
+들어오는 요청 데이터의 자동 검증을 갖기.
+
+///
+
+### APISpec { #apispec }
+
+Marshmallow와 Webargs는 plug-in 형태로 검증, parsing, serialization을 제공합니다.
+
+하지만 문서화는 여전히 부족했습니다. 그래서 APISpec이 만들어졌습니다.
+
+이는 여러 framework를 위한 plug-in이며(Starlette용 plug-in도 있습니다).
+
+작동 방식은, 각 route를 처리하는 함수의 docstring 안에 YAML 형식으로 schema 정의를 작성하고,
+
+그로부터 OpenAPI schema를 생성합니다.
+
+Flask, Starlette, Responder 등에서 이런 방식으로 동작합니다.
+
+하지만 다시, Python 문자열 내부(큰 YAML)에서 micro-syntax를 다루어야 한다는 문제가 있습니다.
+
+에디터가 이를 크게 도와주지 못합니다. 또한 파라미터나 Marshmallow schema를 수정해놓고 YAML docstring도 같이 수정하는 것을 잊어버리면, 생성된 schema는 오래된 상태가 됩니다.
+
+/// info | 정보
+
+APISpec은 Marshmallow와 같은 개발자들이 만들었습니다.
+
+///
+
+/// check | **FastAPI**에 영감을 준 것
+
+API를 위한 열린 표준인 OpenAPI를 지원하기.
+
+///
+
+### Flask-apispec { #flask-apispec }
+
+Flask plug-in으로, Webargs, Marshmallow, APISpec을 묶어줍니다.
+
+Webargs와 Marshmallow의 정보를 사용해 APISpec으로 OpenAPI schema를 자동 생성합니다.
+
+훌륭한 도구인데도 과소평가되어 있습니다. 다른 많은 Flask plug-in보다 훨씬 더 유명해져야 합니다. 문서가 너무 간결하고 추상적이라서 그럴 수도 있습니다.
+
+이 도구는 Python docstring 내부에 YAML(또 다른 문법)을 작성해야 하는 문제를 해결했습니다.
+
+Flask + Flask-apispec + Marshmallow + Webargs 조합은 **FastAPI**를 만들기 전까지 제가 가장 좋아하던 백엔드 stack이었습니다.
+
+이를 사용하면서 여러 Flask full-stack generator가 만들어졌습니다. 이것들이 지금까지 저(그리고 여러 외부 팀)가 사용해 온 주요 stack입니다:
+
+* https://github.com/tiangolo/full-stack
+* https://github.com/tiangolo/full-stack-flask-couchbase
+* https://github.com/tiangolo/full-stack-flask-couchdb
+
+그리고 이 동일한 full-stack generator들이 [**FastAPI** Project Generators](project-generation.md){.internal-link target=_blank}의 기반이 되었습니다.
+
+/// info | 정보
+
+Flask-apispec은 Marshmallow와 같은 개발자들이 만들었습니다.
+
+///
+
+/// check | **FastAPI**에 영감을 준 것
+
+serialization과 validation을 정의하는 동일한 코드로부터 OpenAPI schema를 자동 생성하기.
+
+///
+
+### NestJS (그리고 Angular) { #nestjs-and-angular }
+
+이건 Python도 아닙니다. NestJS는 Angular에서 영감을 받은 JavaScript(TypeScript) NodeJS framework입니다.
+
+Flask-apispec으로 할 수 있는 것과 어느 정도 비슷한 것을 달성합니다.
+
+Angular 2에서 영감을 받은 의존성 주입 시스템이 통합되어 있습니다. 제가 아는 다른 의존성 주입 시스템들처럼 "injectable"을 사전에 등록해야 하므로, 장황함과 코드 반복이 늘어납니다.
+
+파라미터가 TypeScript 타입(Python type hints와 유사함)으로 설명되기 때문에 에디터 지원은 꽤 좋습니다.
+
+하지만 TypeScript 데이터는 JavaScript로 컴파일된 뒤에는 보존되지 않기 때문에, 타입에 의존해 검증, serialization, 문서화를 동시에 정의할 수 없습니다. 이 점과 일부 설계 결정 때문에, 검증/serialization/자동 schema 생성을 하려면 여러 곳에 decorator를 추가해야 하며, 결과적으로 매우 장황해집니다.
+
+중첩 모델을 잘 처리하지 못합니다. 즉, 요청의 JSON body가 내부 필드를 가진 JSON 객체이고 그 내부 필드들이 다시 중첩된 JSON 객체인 경우, 제대로 문서화하고 검증할 수 없습니다.
+
+/// check | **FastAPI**에 영감을 준 것
+
+Python 타입을 사용해 뛰어난 에디터 지원을 제공하기.
+
+강력한 의존성 주입 시스템을 갖추기. 코드 반복을 최소화하는 방법을 찾기.
+
+///
+
+### Sanic { #sanic }
+
+`asyncio` 기반의 매우 빠른 Python framework 중 초기 사례였습니다. Flask와 매우 유사하게 만들어졌습니다.
+
+/// note | 기술 세부사항
+
+기본 Python `asyncio` 루프 대신 `uvloop`를 사용했습니다. 이것이 매우 빠르게 만든 요인입니다.
+
+이는 Uvicorn과 Starlette에 명확히 영감을 주었고, 현재 공개 benchmark에서는 이 둘이 Sanic보다 더 빠릅니다.
+
+///
+
+/// check | **FastAPI**에 영감을 준 것
+
+미친 성능을 낼 수 있는 방법을 찾기.
+
+그래서 **FastAPI**는 Starlette를 기반으로 합니다. Starlette는 사용 가능한 framework 중 가장 빠르기 때문입니다(서드파티 benchmark로 테스트됨).
+
+///
+
+### Falcon { #falcon }
+
+Falcon은 또 다른 고성능 Python framework로, 최소한으로 설계되었고 Hug 같은 다른 framework의 기반으로 동작하도록 만들어졌습니다.
+
+함수가 두 개의 파라미터(하나는 "request", 하나는 "response")를 받도록 설계되어 있습니다. 그런 다음 request에서 일부를 "읽고", response에 일부를 "작성"합니다. 이 설계 때문에, 표준 Python type hints를 함수 파라미터로 사용해 요청 파라미터와 body를 선언하는 것이 불가능합니다.
+
+따라서 데이터 검증, serialization, 문서화는 자동으로 되지 않고 코드로 해야 합니다. 또는 Hug처럼 Falcon 위에 framework를 얹어 구현해야 합니다. request 객체 하나와 response 객체 하나를 파라미터로 받는 Falcon의 설계에서 영감을 받은 다른 framework에서도 같은 구분이 나타납니다.
+
+/// check | **FastAPI**에 영감을 준 것
+
+훌륭한 성능을 얻는 방법을 찾기.
+
+Hug(= Falcon 기반)과 함께, 함수에서 `response` 파라미터를 선언하도록 **FastAPI**에 영감을 주었습니다.
+
+다만 FastAPI에서는 선택 사항이며, 주로 헤더, 쿠키, 그리고 대체 status code를 설정하는 데 사용됩니다.
+
+///
+
+### Molten { #molten }
+
+**FastAPI**를 만들기 시작한 초기 단계에서 Molten을 알게 되었고, 꽤 비슷한 아이디어를 갖고 있었습니다:
+
+* Python type hints 기반
+* 이 타입으로부터 검증과 문서화 생성
+* 의존성 주입 시스템
+
+Pydantic 같은 서드파티 라이브러리를 사용해 데이터 검증/serialization/문서화를 하지 않고 자체 구현을 사용합니다. 그래서 이런 데이터 타입 정의를 쉽게 재사용하기는 어렵습니다.
+
+조금 더 장황한 설정이 필요합니다. 또한 WSGI(ASGI가 아니라) 기반이므로, Uvicorn, Starlette, Sanic 같은 도구가 제공하는 고성능을 활용하도록 설계되지 않았습니다.
+
+의존성 주입 시스템은 의존성을 사전에 등록해야 하고, 선언된 타입을 기반으로 의존성을 해결합니다. 따라서 특정 타입을 제공하는 "component"를 두 개 이상 선언할 수 없습니다.
+
+Route는 한 곳에서 선언하고, 다른 곳에 선언된 함수를 사용합니다(엔드포인트를 처리하는 함수 바로 위에 둘 수 있는 decorator를 사용하는 대신). 이는 Flask(및 Starlette)보다는 Django 방식에 가깝습니다. 코드에서 상대적으로 강하게 결합된 것들을 분리해 놓습니다.
+
+/// check | **FastAPI**에 영감을 준 것
+
+모델 속성의 "default" 값으로 데이터 타입에 대한 추가 검증을 정의하기. 이는 에디터 지원을 개선하며, 이전에는 Pydantic에 없었습니다.
+
+이것은 실제로 Pydantic의 일부를 업데이트하여 같은 검증 선언 스타일을 지원하도록 하는 데 영감을 주었습니다(이 기능은 이제 Pydantic에 이미 포함되어 있습니다).
+
+///
+
+### Hug { #hug }
+
+Hug는 Python type hints를 사용해 API 파라미터 타입을 선언하는 기능을 구현한 초기 framework 중 하나였습니다. 이는 다른 도구들도 같은 방식을 하도록 영감을 준 훌륭한 아이디어였습니다.
+
+표준 Python 타입 대신 커스텀 타입을 선언에 사용했지만, 여전히 큰 진전이었습니다.
+
+또한 전체 API를 JSON으로 선언하는 커스텀 schema를 생성한 초기 framework 중 하나였습니다.
+
+OpenAPI나 JSON Schema 같은 표준을 기반으로 하지 않았기 때문에 Swagger UI 같은 다른 도구와 통합하는 것은 직관적이지 않았습니다. 하지만 역시 매우 혁신적인 아이디어였습니다.
+
+흥미롭고 흔치 않은 기능이 하나 있습니다. 같은 framework로 API뿐 아니라 CLI도 만들 수 있습니다.
+
+동기식 Python 웹 framework의 이전 표준(WSGI) 기반이어서 Websockets와 다른 것들을 처리할 수는 없지만, 성능은 여전히 높습니다.
+
+/// info | 정보
+
+Hug는 Timothy Crosley가 만들었습니다. Python 파일에서 import를 자동으로 정렬하는 훌륭한 도구인 `isort`의 제작자이기도 합니다.
+
+///
+
+/// check | **FastAPI**에 영감을 준 아이디어들
+
+Hug는 APIStar의 일부에 영감을 주었고, 저는 APIStar와 함께 Hug를 가장 유망한 도구 중 하나로 보았습니다.
+
+Hug는 Python type hints로 파라미터를 선언하고, API를 정의하는 schema를 자동으로 생성하도록 **FastAPI**에 영감을 주었습니다.
+
+Hug는 헤더와 쿠키를 설정하기 위해 함수에 `response` 파라미터를 선언하도록 **FastAPI**에 영감을 주었습니다.
+
+///
+
+### APIStar (<= 0.5) { #apistar-0-5 }
+
+**FastAPI**를 만들기로 결정하기 직전에 **APIStar** 서버를 발견했습니다. 찾고 있던 거의 모든 것을 갖추고 있었고 설계도 훌륭했습니다.
+
+NestJS와 Molten보다 앞서, Python type hints를 사용해 파라미터와 요청을 선언하는 framework 구현을 제가 처음 본 사례들 중 하나였습니다. Hug와 거의 같은 시기에 발견했습니다. 하지만 APIStar는 OpenAPI 표준을 사용했습니다.
+
+여러 위치에서 동일한 type hints를 기반으로 자동 데이터 검증, 데이터 serialization, OpenAPI schema 생성을 제공했습니다.
+
+Body schema 정의는 Pydantic처럼 동일한 Python type hints를 사용하지는 않았고 Marshmallow와 조금 더 비슷해서 에디터 지원은 그만큼 좋지 않았지만, 그래도 APIStar는 당시 사용할 수 있는 최선의 선택지였습니다.
+
+당시 최고의 성능 benchmark를 가졌습니다(Starlette에 의해서만 추월됨).
+
+처음에는 자동 API 문서화 웹 UI가 없었지만, Swagger UI를 추가할 수 있다는 것을 알고 있었습니다.
+
+의존성 주입 시스템도 있었습니다. 위에서 언급한 다른 도구들처럼 component의 사전 등록이 필요했지만, 여전히 훌륭한 기능이었습니다.
+
+보안 통합이 없어서 전체 프로젝트에서 사용해 볼 수는 없었습니다. 그래서 Flask-apispec 기반 full-stack generator로 갖추고 있던 모든 기능을 대체할 수 없었습니다. 그 기능을 추가하는 pull request를 만드는 것이 제 백로그에 있었습니다.
+
+하지만 이후 프로젝트의 초점이 바뀌었습니다.
+
+더 이상 API web framework가 아니게 되었는데, 제작자가 Starlette에 집중해야 했기 때문입니다.
+
+이제 APIStar는 web framework가 아니라 OpenAPI 사양을 검증하기 위한 도구 모음입니다.
+
+/// info | 정보
+
+APIStar는 Tom Christie가 만들었습니다. 다음을 만든 사람과 동일합니다:
+
+* Django REST Framework
+* Starlette(**FastAPI**의 기반)
+* Uvicorn(Starlette와 **FastAPI**에서 사용)
+
+///
+
+/// check | **FastAPI**에 영감을 준 것
+
+존재하게 만들기.
+
+동일한 Python 타입으로 여러 가지(데이터 검증, serialization, 문서화)를 선언하면서 동시에 뛰어난 에디터 지원을 제공한다는 아이디어는 제가 매우 훌륭하다고 생각했습니다.
+
+그리고 오랫동안 비슷한 framework를 찾아 여러 대안을 테스트한 끝에, APIStar가 그때 이용 가능한 최선의 선택지였습니다.
+
+그 후 APIStar 서버가 더는 존재하지 않게 되고 Starlette가 만들어졌는데, 이는 그런 시스템을 위한 더 새롭고 더 나은 기반이었습니다. 이것이 **FastAPI**를 만들게 된 최종 영감이었습니다.
+
+저는 **FastAPI**를 APIStar의 "정신적 후계자"로 생각합니다. 동시에, 이 모든 이전 도구들에서 배운 것들을 바탕으로 기능, typing 시스템, 그리고 다른 부분들을 개선하고 확장했습니다.
+
+///
+
+## **FastAPI**가 사용하는 것 { #used-by-fastapi }
+
+### Pydantic { #pydantic }
+
+Pydantic은 Python type hints를 기반으로 데이터 검증, serialization, 문서화(JSON Schema 사용)를 정의하는 라이브러리입니다.
+
+그 덕분에 매우 직관적입니다.
+
+Marshmallow와 비교할 수 있습니다. 다만 benchmark에서 Marshmallow보다 빠릅니다. 그리고 동일한 Python type hints를 기반으로 하므로 에디터 지원도 훌륭합니다.
+
+/// check | **FastAPI**가 이를 사용하는 목적
+
+모든 데이터 검증, 데이터 serialization, 자동 모델 문서화(JSON Schema 기반)를 처리하기.
+
+그 다음 **FastAPI**는 그 JSON Schema 데이터를 가져와 OpenAPI에 포함시키며, 그 외에도 여러 작업을 수행합니다.
+
+///
+
+### Starlette { #starlette }
+
+Starlette는 경량 ASGI framework/toolkit으로, 고성능 asyncio 서비스를 만들기에 이상적입니다.
+
+매우 단순하고 직관적입니다. 쉽게 확장할 수 있도록 설계되었고, 모듈식 component를 갖습니다.
+
+다음이 포함됩니다:
+
+* 정말 인상적인 성능.
+* WebSocket 지원.
+* 프로세스 내 백그라운드 작업.
+* 시작 및 종료 이벤트.
+* HTTPX 기반의 테스트 클라이언트.
+* CORS, GZip, Static Files, Streaming responses.
+* 세션 및 쿠키 지원.
+* 100% 테스트 커버리지.
+* 100% 타입 주석이 달린 코드베이스.
+* 소수의 필수 의존성.
+
+Starlette는 현재 테스트된 Python framework 중 가장 빠릅니다. 단, framework가 아니라 서버인 Uvicorn이 더 빠릅니다.
+
+Starlette는 웹 microframework의 기본 기능을 모두 제공합니다.
+
+하지만 자동 데이터 검증, serialization, 문서화는 제공하지 않습니다.
+
+그것이 **FastAPI**가 위에 추가하는 핵심 중 하나이며, 모두 Python type hints(Pydantic 사용)를 기반으로 합니다. 여기에 더해 의존성 주입 시스템, 보안 유틸리티, OpenAPI schema 생성 등도 포함됩니다.
+
+/// note | 기술 세부사항
+
+ASGI는 Django 코어 팀 멤버들이 개발 중인 새로운 "표준"입니다. 아직 "Python 표준"(PEP)은 아니지만, 그 방향으로 진행 중입니다.
+
+그럼에도 이미 여러 도구에서 "표준"으로 사용되고 있습니다. 이는 상호운용성을 크게 개선합니다. 예를 들어 Uvicorn을 다른 ASGI 서버(예: Daphne 또는 Hypercorn)로 교체할 수도 있고, `python-socketio` 같은 ASGI 호환 도구를 추가할 수도 있습니다.
+
+///
+
+/// check | **FastAPI**가 이를 사용하는 목적
+
+핵심 웹 부분을 모두 처리하기. 그 위에 기능을 추가하기.
+
+`FastAPI` 클래스 자체는 `Starlette` 클래스를 직접 상속합니다.
+
+따라서 Starlette로 할 수 있는 모든 것은 기본적으로 **FastAPI**로도 직접 할 수 있습니다. 즉, **FastAPI**는 사실상 Starlette에 강력한 기능을 더한 것입니다.
+
+///
+
+### Uvicorn { #uvicorn }
+
+Uvicorn은 uvloop과 httptools로 구축된 초고속 ASGI 서버입니다.
+
+web framework가 아니라 서버입니다. 예를 들어 경로 기반 routing을 위한 도구는 제공하지 않습니다. 그런 것은 Starlette(또는 **FastAPI**) 같은 framework가 위에서 제공합니다.
+
+Starlette와 **FastAPI**에서 권장하는 서버입니다.
+
+/// check | **FastAPI**가 이를 권장하는 방식
+
+**FastAPI** 애플리케이션을 실행하기 위한 주요 웹 서버.
+
+또한 `--workers` 커맨드라인 옵션을 사용하면 비동기 멀티프로세스 서버로 실행할 수도 있습니다.
+
+자세한 내용은 [배포](deployment/index.md){.internal-link target=_blank} 섹션을 확인하세요.
+
+///
+
+## 벤치마크와 속도 { #benchmarks-and-speed }
+
+Uvicorn, Starlette, FastAPI 사이의 차이를 이해하고 비교하려면 [벤치마크](benchmarks.md){.internal-link target=_blank} 섹션을 확인하세요.
diff --git a/docs/ko/docs/async.md b/docs/ko/docs/async.md
index ec503d540..36f1ca6bf 100644
--- a/docs/ko/docs/async.md
+++ b/docs/ko/docs/async.md
@@ -1,18 +1,18 @@
-# 동시성과 async / await
+# 동시성과 async / await { #concurrency-and-async-await }
-*경로 작동 함수*에서의 `async def` 문법에 대한 세부사항과 비동기 코드, 동시성 및 병렬성에 대한 배경
+*경로 처리 함수*에서의 `async def` 문법에 대한 세부사항과 비동기 코드, 동시성 및 병렬성에 대한 배경
-## 바쁘신 경우
+## 바쁘신가요? { #in-a-hurry }
-요약
+TL;DR:
-다음과 같이 `await`를 사용해 호출하는 제3의 라이브러리를 사용하는 경우:
+다음과 같이 `await`를 사용해 호출하라고 안내하는 제3자 라이브러리를 사용하는 경우:
```Python
results = await some_library()
```
-다음처럼 *경로 작동 함수*를 `async def`를 사용해 선언하십시오:
+다음처럼 *경로 처리 함수*를 `async def`를 사용해 선언하십시오:
```Python hl_lines="2"
@app.get('/')
@@ -29,7 +29,7 @@ async def read_results():
---
-데이터베이스, API, 파일시스템 등과 의사소통하는 제3의 라이브러리를 사용하고, 그것이 `await`를 지원하지 않는 경우(현재 거의 모든 데이터베이스 라이브러리가 그러합니다), *경로 작동 함수*를 일반적인 `def`를 사용해 선언하십시오:
+데이터베이스, API, 파일시스템 등과 의사소통하는 제3자 라이브러리를 사용하고, 그것이 `await` 사용을 지원하지 않는 경우(현재 대부분의 데이터베이스 라이브러리가 그러합니다), *경로 처리 함수*를 일반적인 `def`를 사용해 선언하십시오:
```Python hl_lines="2"
@app.get('/')
@@ -40,23 +40,23 @@ def results():
---
-만약 당신의 응용프로그램이 (어째서인지) 다른 무엇과 의사소통하고 그것이 응답하기를 기다릴 필요가 없다면 `async def`를 사용하십시오.
+만약 여러분의 애플리케이션이 (어째서인지) 다른 어떤 것과도 통신하고 그 응답을 기다릴 필요가 없다면, 내부에서 `await`를 사용할 필요가 없더라도 `async def`를 사용하세요.
---
-모르겠다면, 그냥 `def`를 사용하십시오.
+잘 모르겠다면, 일반적인 `def`를 사용하세요.
---
-**참고**: *경로 작동 함수*에서 필요한만큼 `def`와 `async def`를 혼용할 수 있고, 가장 알맞은 것을 선택해서 정의할 수 있습니다. FastAPI가 자체적으로 알맞은 작업을 수행할 것입니다.
+**참고**: *경로 처리 함수*에서 필요한 만큼 `def`와 `async def`를 혼용할 수 있으며, 각각에 대해 가장 알맞은 옵션을 선택해 정의하면 됩니다. FastAPI가 올바르게 처리합니다.
-어찌되었든, 상기 어떠한 경우라도, FastAPI는 여전히 비동기적으로 작동하고 매우 빠릅니다.
+어쨌든 위의 어떤 경우에서도 FastAPI는 여전히 비동기적으로 동작하며 매우 빠릅니다.
-그러나 상기 작업을 수행함으로써 어느 정도의 성능 최적화가 가능합니다.
+하지만 위의 단계를 따르면, 몇 가지 성능 최적화를 할 수 있습니다.
-## 기술적 세부사항
+## 기술적 세부사항 { #technical-details }
-최신 파이썬 버전은 `async`와 `await` 문법과 함께 **“코루틴”**이라고 하는 것을 사용하는 **“비동기 코드”**를 지원합니다.
+최신 파이썬 버전은 **“코루틴”**이라고 하는 것을 사용하는 **“비동기 코드”**를 **`async` 및 `await`** 문법과 함께 지원합니다.
아래 섹션들에서 해당 문장을 부분별로 살펴보겠습니다:
@@ -64,251 +64,283 @@ def results():
* **`async`와 `await`**
* **코루틴**
-## 비동기 코드
+## 비동기 코드 { #asynchronous-code }
-비동기 코드란 언어 💬 가 코드의 어느 한 부분에서, 컴퓨터 / 프로그램🤖에게 *다른 무언가*가 어딘가에서 끝날 때까지 기다려야한다고 말하는 방식입니다. *다른 무언가*가 “느린-파일" 📝 이라고 불린다고 가정해봅시다.
+비동기 코드는 언어 💬 가 코드의 어느 한 부분에서 컴퓨터/프로그램 🤖 에게, 어느 시점에는 어딘가에서 *다른 무언가*가 끝날 때까지 기다려야 한다고 말할 수 있는 방법이 있다는 의미입니다. 그 *다른 무언가*를 "slow-file" 📝 이라고 해보겠습니다.
-따라서 “느린-파일” 📝이 끝날때까지 컴퓨터는 다른 작업을 수행할 수 있습니다.
+따라서 그 시간 동안 컴퓨터는 "slow-file" 📝 이 끝나는 동안 다른 작업을 하러 갈 수 있습니다.
-그 다음 컴퓨터 / 프로그램 🤖 은 다시 기다리고 있기 때문에 기회가 있을 때마다 다시 돌아오거나, 혹은 당시에 수행해야하는 작업들이 완료될 때마다 다시 돌아옵니다. 그리고 그것 🤖 은 기다리고 있던 작업 중 어느 것이 이미 완료되었는지, 그것 🤖 이 해야하는 모든 작업을 수행하면서 확인합니다.
+그 다음 컴퓨터/프로그램 🤖 은 다시 기다리는 중이기 때문에 기회가 있을 때마다 돌아오거나, 혹은 그 시점에 해야 할 작업을 모두 끝낼 때마다 돌아옵니다. 그리고 기다리던 작업 중 이미 끝난 것이 있는지 확인하면서, 해야 했던 작업을 수행합니다.
-다음으로, 그것 🤖 은 완료할 첫번째 작업에 착수하고(우리의 "느린-파일" 📝 이라고 가정합시다) 그에 대해 수행해야하는 작업을 계속합니다.
+다음으로, 완료된 첫 번째 작업(우리의 "slow-file" 📝 이라고 해보겠습니다)을 가져와서, 그에 대해 해야 했던 작업을 계속합니다.
-"다른 무언가를 기다리는 것"은 일반적으로 비교적 "느린" (프로세서와 RAM 메모리 속도에 비해) I/O 작업을 의미합니다. 예를 들면 다음의 것들을 기다리는 것입니다:
+이 "다른 무언가를 기다리는 것"은 일반적으로 프로세서와 RAM 메모리 속도에 비해 상대적으로 "느린" I/O 작업을 의미합니다. 예를 들어 다음을 기다리는 것입니다:
-* 네트워크를 통해 클라이언트로부터 전송되는 데이터
-* 네트워크를 통해 클라이언트가 수신할, 당신의 프로그램으로부터 전송되는 데이터
-* 시스템이 읽고 프로그램에 전달할 디스크 내의 파일 내용
-* 당신의 프로그램이 시스템에 전달하는, 디스크에 작성될 내용
+* 네트워크를 통해 클라이언트가 데이터를 보내는 것
+* 네트워크를 통해 클라이언트가 여러분의 프로그램이 보낸 데이터를 받는 것
+* 시스템이 디스크의 파일 내용을 읽어서 프로그램에 전달하는 것
+* 프로그램이 시스템에 전달한 내용을 디스크에 쓰는 것
* 원격 API 작업
-* 완료될 데이터베이스 작업
-* 결과를 반환하는 데이터베이스 쿼리
-* 기타
+* 데이터베이스 작업이 완료되는 것
+* 데이터베이스 쿼리가 결과를 반환하는 것
+* 기타 등등
-수행 시간의 대부분이 I/O 작업을 기다리는데에 소요되기 때문에, "I/O에 묶인" 작업이라고 불립니다.
+실행 시간의 대부분이 I/O 작업을 기다리는 데 소비되기 때문에, 이를 "I/O bound" 작업이라고 부릅니다.
-이것은 "비동기"라고 불리는데 컴퓨터 / 프로그램이 작업 결과를 가지고 일을 수행할 수 있도록, 느린 작업에 "동기화"되어 아무것도 하지 않으면서 작업이 완료될 정확한 시점만을 기다릴 필요가 없기 때문입니다.
+이것은 컴퓨터/프로그램이 느린 작업과 "동기화"되어, 아무것도 하지 않은 채 그 작업이 끝나는 정확한 시점만 기다렸다가 결과를 가져와 일을 계속할 필요가 없기 때문에 "비동기"라고 불립니다.
-이 대신에, "비동기" 시스템에서는, 작업은 일단 완료되면, 컴퓨터 / 프로그램이 수행하고 있는 일을 완료하고 이후 다시 돌아와서 그것의 결과를 받아 이를 사용해 작업을 지속할 때까지 잠시 (몇 마이크로초) 대기할 수 있습니다.
+대신 "비동기" 시스템에서는, 작업이 끝나면 컴퓨터/프로그램이 하러 갔던 일을 마칠 때까지 잠시(몇 마이크로초) 줄에서 기다렸다가, 다시 돌아와 결과를 받아 이를 사용해 작업을 계속할 수 있습니다.
-"동기"("비동기"의 반대)는 컴퓨터 / 프로그램이 상이한 작업들간 전환을 하기 전에 그것이 대기를 동반하게 될지라도 모든 순서를 따르기 때문에 "순차"라는 용어로도 흔히 불립니다.
+"동기"(“비동기”의 반대)는 보통 "순차"라는 용어로도 불리는데, 컴퓨터/프로그램이 다른 작업으로 전환하기 전에 모든 단계를 순서대로 따르기 때문이며, 그 단계들에 기다림이 포함되어 있더라도 마찬가지입니다.
-### 동시성과 버거
+### 동시성과 햄버거 { #concurrency-and-burgers }
-위에서 설명한 **비동기** 코드에 대한 개념은 종종 **"동시성"**이라고도 불립니다. 이것은 **"병렬성"**과는 다릅니다.
+위에서 설명한 **비동기** 코드에 대한 개념은 때때로 **"동시성"**이라고도 불립니다. 이는 **"병렬성"**과는 다릅니다.
-**동시성**과 **병렬성**은 모두 "동시에 일어나는 서로 다른 일들"과 관련이 있습니다.
+**동시성**과 **병렬성**은 모두 "대략 같은 시간에 일어나는 서로 다른 일들"과 관련이 있습니다.
하지만 *동시성*과 *병렬성*의 세부적인 개념에는 꽤 차이가 있습니다.
-차이를 확인하기 위해, 다음의 버거에 대한 이야기를 상상해보십시오:
+차이를 보기 위해, 다음의 햄버거 이야기를 상상해보세요:
-### 동시 버거
+### 동시 햄버거 { #concurrent-burgers }
-당신은 짝사랑 상대 😍 와 패스트푸드 🍔 를 먹으러 갔습니다. 당신은 점원 💁 이 당신 앞에 있는 사람들의 주문을 받을 동안 줄을 서서 기다리고 있습니다.
+여러분은 짝사랑 상대와 패스트푸드를 먹으러 갔고, 점원이 여러분 앞 사람들의 주문을 받는 동안 줄을 서서 기다립니다. 😍
-이제 당신의 순서가 되어서, 당신은 당신과 짝사랑 상대 😍 를 위한 두 개의 고급스러운 버거 🍔 를 주문합니다.
+
-당신이 돈을 냅니다 💸.
+이제 여러분 차례가 되어, 여러분과 짝사랑 상대를 위해 매우 고급스러운 햄버거 2개를 주문합니다. 🍔🍔
-점원 💁 은 주방 👨🍳 에 요리를 하라고 전달하고, 따라서 그들은 당신의 버거 🍔 를 준비해야한다는 사실을 알게됩니다(그들이 지금은 당신 앞 고객들의 주문을 준비하고 있을지라도 말입니다).
+
-점원 💁 은 당신의 순서가 적힌 번호표를 줍니다.
+점원은 주방의 요리사에게 무언가를 말해, (지금은 앞선 손님들의 주문을 준비하고 있더라도) 여러분의 햄버거를 준비해야 한다는 것을 알게 합니다.
-기다리는 동안, 당신은 짝사랑 상대 😍 와 함께 테이블을 고르고, 자리에 앉아 오랫동안 (당신이 주문한 버거는 꽤나 고급스럽기 때문에 준비하는데 시간이 조금 걸립니다 ✨🍔✨) 대화를 나눕니다.
+
-짝사랑 상대 😍 와 테이블에 앉아서 버거 🍔 를 기다리는 동안, 그 사람 😍 이 얼마나 멋지고, 사랑스럽고, 똑똑한지 감탄하며 시간을 보냅니다 ✨😍✨.
+여러분이 돈을 냅니다. 💸
-짝사랑 상대 😍 와 기다리면서 얘기하는 동안, 때때로, 당신은 당신의 차례가 되었는지 보기 위해 카운터의 번호를 확인합니다.
+점원은 여러분 차례 번호를 줍니다.
-그러다 어느 순간, 당신의 차례가 됩니다. 카운터에 가서, 버거 🍔 를 받고, 테이블로 다시 돌아옵니다.
+
-당신과 짝사랑 상대 😍 는 버거 🍔 를 먹으며 좋은 시간을 보냅니다 ✨.
+기다리는 동안, 여러분은 짝사랑 상대와 함께 자리를 고르고 앉아 오랫동안 대화를 나눕니다(여러분의 햄버거는 매우 고급스럽기 때문에 준비하는 데 시간이 좀 걸립니다).
+
+짝사랑 상대와 테이블에 앉아 햄버거를 기다리는 동안, 그 사람이 얼마나 멋지고 귀엽고 똑똑한지 감탄하며 시간을 보낼 수 있습니다 ✨😍✨.
+
+
+
+기다리며 대화하는 동안, 때때로 여러분은 카운터에 표시되는 번호를 확인해 여러분 차례인지 봅니다.
+
+그러다 어느 순간 마침내 여러분 차례가 됩니다. 여러분은 카운터에 가서 햄버거를 받고, 테이블로 돌아옵니다.
+
+
+
+여러분과 짝사랑 상대는 햄버거를 먹으며 좋은 시간을 보냅니다. ✨
+
+
+
+/// info | 정보
+
+아름다운 일러스트: Ketrina Thompson. 🎨
+
+///
---
-당신이 이 이야기에서 컴퓨터 / 프로그램 🤖 이라고 상상해보십시오.
+이 이야기에서 여러분이 컴퓨터/프로그램 🤖 이라고 상상해보세요.
-줄을 서서 기다리는 동안, 당신은 아무것도 하지 않고 😴 당신의 차례를 기다리며, 어떠한 "생산적인" 일도 하지 않습니다. 하지만 점원 💁 이 (음식을 준비하지는 않고) 주문을 받기만 하기 때문에 줄이 빨리 줄어들어서 괜찮습니다.
+줄을 서 있는 동안, 여러분은 그냥 쉬고 😴, 차례를 기다리며, 그다지 "생산적인" 일을 하지 않습니다. 하지만 점원은 주문만 받지(음식을 준비하진 않기) 때문에 줄이 빠르게 줄어들어 괜찮습니다.
-그다음, 당신이 차례가 오면, 당신은 실제로 "생산적인" 일 🤓 을 합니다. 당신은 메뉴를 보고, 무엇을 먹을지 결정하고, 짝사랑 상대 😍 의 선택을 묻고, 돈을 내고 💸 , 맞는 카드를 냈는지 확인하고, 비용이 제대로 지불되었는지 확인하고, 주문이 제대로 들어갔는지 확인을 하는 작업 등등을 수행합니다.
+그 다음 여러분 차례가 되면, 여러분은 실제로 "생산적인" 일을 합니다. 메뉴를 처리하고, 무엇을 먹을지 결정하고, 짝사랑 상대의 선택을 확인하고, 결제하고, 올바른 현금이나 카드를 냈는지 확인하고, 정확히 청구되었는지 확인하고, 주문에 올바른 항목들이 들어갔는지 확인하는 등등을 합니다.
-하지만 이후에는, 버거 🍔 를 아직 받지 못했음에도, 버거가 준비될 때까지 기다려야 🕙 하기 때문에 점원 💁 과의 작업은 "일시정지" ⏸ 상태입니다.
+하지만 그 다음에는, 아직 햄버거를 받지 못했더라도, 햄버거가 준비될 때까지 기다려야 🕙 하므로 점원과의 작업은 "일시정지" ⏸ 상태입니다.
-하지만 번호표를 받고 카운터에서 나와 테이블에 앉으면, 당신은 짝사랑 상대 😍 와 그 "작업" ⏯ 🤓 에 번갈아가며 🔀 집중합니다. 그러면 당신은 다시 짝사랑 상대 😍 에게 작업을 거는 매우 "생산적인" 일 🤓 을 합니다.
+하지만 번호를 들고 카운터에서 벗어나 테이블에 앉으면, 여러분은 짝사랑 상대에게 관심을 전환 🔀 하고, 그에 대한 "작업" ⏯ 🤓 을 할 수 있습니다. 그러면 여러분은 다시 짝사랑 상대에게 작업을 거는 매우 "생산적인" 일을 하게 됩니다 😍.
-점원 💁 이 카운터 화면에 당신의 번호를 표시함으로써 "버거 🍔 가 준비되었습니다"라고 해도, 당신은 즉시 뛰쳐나가지는 않을 것입니다. 당신은 당신의 번호를 갖고있고, 다른 사람들은 그들의 번호를 갖고있기 때문에, 아무도 당신의 버거 🍔 를 훔쳐가지 않는다는 사실을 알기 때문입니다.
+그 다음 점원 💁 이 카운터 화면에 여러분 번호를 띄워 "햄버거를 만들었어요"라고 말하지만, 표시된 번호가 여러분 차례로 바뀌었다고 해서 즉시 미친 듯이 뛰어가지는 않습니다. 여러분은 여러분 번호를 갖고 있고, 다른 사람들은 그들의 번호를 갖고 있으니, 아무도 여러분 햄버거를 훔쳐갈 수 없다는 것을 알기 때문입니다.
-그래서 당신은 짝사랑 상대 😍 가 이야기를 끝낼 때까지 기다린 후 (현재 작업 완료 ⏯ / 진행 중인 작업 처리 🤓 ), 정중하게 미소짓고 버거를 가지러 가겠다고 말합니다 ⏸.
+그래서 여러분은 짝사랑 상대가 이야기를 끝낼 때까지 기다린 다음(현재 작업 ⏯ / 처리 중인 작업 🤓 을 끝내고), 부드럽게 미소 지으며 햄버거를 가지러 가겠다고 말합니다 ⏸.
-그다음 당신은 카운터에 가서 🔀 , 초기 작업을 이제 완료하고 ⏯ , 버거 🍔 를 받고, 감사하다고 말하고 테이블로 가져옵니다. 이로써 카운터와의 상호작용 단계 / 작업이 종료됩니다 ⏹.
+그 다음 여러분은 카운터로 가서 🔀, 이제 끝난 초기 작업 ⏯ 으로 돌아와 햄버거를 받고, 감사 인사를 하고, 테이블로 가져옵니다. 이로써 카운터와 상호작용하는 그 단계/작업이 끝납니다 ⏹. 그리고 이는 새로운 작업인 "햄버거 먹기" 🔀 ⏯ 를 만들지만, 이전 작업인 "햄버거 받기"는 끝났습니다 ⏹.
-이전 작업인 "버거 받기"가 종료되면 ⏹ "버거 먹기"라는 새로운 작업이 생성됩니다 🔀 ⏯.
+### 병렬 햄버거 { #parallel-burgers }
-### 병렬 버거
+이제 이것이 "동시 햄버거"가 아니라 "병렬 햄버거"라고 상상해봅시다.
-이제 "동시 버거"가 아닌 "병렬 버거"를 상상해보십시오.
+여러분은 짝사랑 상대와 함께 병렬 패스트푸드를 먹으러 갑니다.
-당신은 짝사랑 상대 😍 와 함께 병렬 패스트푸드 🍔 를 먹으러 갔습니다.
+여러분은 여러 명(예: 8명)의 점원이 동시에 요리사이기도 하여 여러분 앞 사람들의 주문을 받는 동안 줄을 서 있습니다.
-당신은 여러명(8명이라고 가정합니다)의 점원이 당신 앞 사람들의 주문을 받으며 동시에 요리 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳 도 하는 동안 줄을 서서 기다립니다.
+여러분 앞의 모든 사람들은, 8명의 점원 각각이 다음 주문을 받기 전에 바로 햄버거를 준비하러 가기 때문에, 카운터를 떠나지 않고 햄버거가 준비될 때까지 기다립니다.
-당신 앞 모든 사람들이 버거가 준비될 때까지 카운터에서 떠나지 않고 기다립니다 🕙 . 왜냐하면 8명의 직원들이 다음 주문을 받기 전에 버거를 준비하러 가기 때문입니다.
+
-마침내 당신의 차례가 왔고, 당신은 당신과 짝사랑 상대 😍 를 위한 두 개의 고급스러운 버거 🍔 를 주문합니다.
+마침내 여러분 차례가 되어, 여러분과 짝사랑 상대를 위해 매우 고급스러운 햄버거 2개를 주문합니다.
-당신이 비용을 지불합니다 💸 .
+여러분이 돈을 냅니다 💸.
-점원이 주방에 갑니다 👨🍳 .
+
-당신은 번호표가 없기 때문에 누구도 당신의 버거 🍔 를 대신 가져갈 수 없도록 카운터에 서서 기다립니다 🕙 .
+점원은 주방으로 갑니다.
-당신과 짝사랑 상대 😍 는 다른 사람이 새치기해서 버거를 가져가지 못하게 하느라 바쁘기 때문에 🕙 , 짝사랑 상대에게 주의를 기울일 수 없습니다 😞 .
+여러분은 번호표가 없으므로, 다른 사람이 여러분보다 먼저 햄버거를 가져가지 못하도록 카운터 앞에 서서 기다립니다 🕙.
-이것은 "동기" 작업이고, 당신은 점원/요리사 👨🍳 와 "동기화" 되었습니다. 당신은 기다리고 🕙 , 점원/요리사 👨🍳 가 버거 🍔 준비를 완료한 후 당신에게 주거나, 누군가가 그것을 가져가는 그 순간에 그 곳에 있어야합니다.
+
-카운터 앞에서 오랫동안 기다린 후에 🕙 , 점원/요리사 👨🍳 가 당신의 버거 🍔 를 가지고 돌아옵니다.
+여러분과 짝사랑 상대는 햄버거가 나오면 다른 사람이 끼어들어 가져가지 못하게 하느라 바쁘기 때문에, 짝사랑 상대에게 집중할 수 없습니다. 😞
-당신은 버거를 받고 짝사랑 상대와 함께 테이블로 돌아옵니다.
+이것은 "동기" 작업이며, 여러분은 점원/요리사 👨🍳 와 "동기화"되어 있습니다. 점원/요리사 👨🍳 가 햄버거를 완성해 여러분에게 주는 정확한 순간에 그 자리에 있어야 하므로, 여러분은 기다려야 🕙 하고, 그렇지 않으면 다른 사람이 가져갈 수도 있습니다.
-단지 먹기만 하다가, 다 먹었습니다 🍔 ⏹.
+
-카운터 앞에서 기다리면서 🕙 너무 많은 시간을 허비했기 때문에 대화를 하거나 작업을 걸 시간이 거의 없었습니다 😞 .
+그러다 점원/요리사 👨🍳 가 카운터 앞에서 오랫동안 기다린 🕙 끝에 마침내 햄버거를 가지고 돌아옵니다.
----
+
+
+여러분은 햄버거를 받아 짝사랑 상대와 테이블로 갑니다.
+
+그냥 먹고, 끝입니다. ⏹
+
+
+
+대부분의 시간을 카운터 앞에서 기다리는 데 🕙 썼기 때문에, 대화하거나 작업을 걸 시간은 많지 않았습니다. 😞
-이 병렬 버거 시나리오에서, 당신은 기다리고 🕙 , 오랜 시간동안 "카운터에서 기다리는" 🕙 데에 주의를 기울이는 ⏯ 두 개의 프로세서(당신과 짝사랑 상대😍)를 가진 컴퓨터 / 프로그램 🤖 입니다.
+/// info | 정보
-패스트푸드점에는 8개의 프로세서(점원/요리사) 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳 가 있습니다. 동시 버거는 단 두 개(한 명의 직원과 한 명의 요리사) 💁 👨🍳 만을 가지고 있었습니다.
+아름다운 일러스트: Ketrina Thompson. 🎨
-하지만 여전히, 병렬 버거 예시가 최선은 아닙니다 😞 .
+///
---
-이 예시는 버거🍔 이야기와 결이 같습니다.
+이 병렬 햄버거 시나리오에서, 여러분은 두 개의 프로세서(여러분과 짝사랑 상대)를 가진 컴퓨터/프로그램 🤖 이며, 둘 다 기다리고 🕙 오랫동안 "카운터에서 기다리기" 🕙 에 주의를 ⏯ 기울입니다.
+
+패스트푸드점에는 8개의 프로세서(점원/요리사)가 있습니다. 동시 햄버거 가게는 2개(점원 1명, 요리사 1명)만 있었을 것입니다.
-더 "현실적인" 예시로, 은행을 상상해보십시오.
+하지만 여전히 최종 경험은 그다지 좋지 않습니다. 😞
-최근까지, 대다수의 은행에는 다수의 은행원들 👨💼👨💼👨💼👨💼 과 긴 줄 🕙🕙🕙🕙🕙🕙🕙🕙 이 있습니다.
+---
-모든 은행원들은 한 명 한 명의 고객들을 차례로 상대합니다 👨💼⏯ .
+이것이 햄버거의 병렬 버전에 해당하는 이야기입니다. 🍔
-그리고 당신은 오랫동안 줄에서 기다려야하고 🕙 , 그렇지 않으면 당신의 차례를 잃게 됩니다.
+좀 더 "현실적인" 예시로, 은행을 상상해보세요.
-아마 당신은 은행 🏦 심부름에 짝사랑 상대 😍 를 데려가고 싶지는 않을 것입니다.
+최근까지 대부분의 은행에는 여러 은행원 👨💼👨💼👨💼👨💼 과 긴 줄 🕙🕙🕙🕙🕙🕙🕙🕙 이 있었습니다.
-### 버거 예시의 결론
+모든 은행원이 한 고객씩 순서대로 모든 일을 처리합니다 👨💼⏯.
-"짝사랑 상대와의 패스트푸드점 버거" 시나리오에서, 오랜 기다림 🕙 이 있기 때문에 동시 시스템 ⏸🔀⏯ 을 사용하는 것이 더 합리적입니다.
+그리고 여러분은 오랫동안 줄에서 기다려야 🕙 하며, 그렇지 않으면 차례를 잃습니다.
-대다수의 웹 응용프로그램의 경우가 그러합니다.
+아마 은행 🏦 업무를 보러 갈 때 짝사랑 상대 😍 를 데려가고 싶지는 않을 것입니다.
-매우 많은 수의 유저가 있지만, 서버는 그들의 요청을 전송하기 위해 그닥-좋지-않은 연결을 기다려야 합니다 🕙 .
+### 햄버거 예시의 결론 { #burger-conclusion }
-그리고 응답이 돌아올 때까지 다시 기다려야 합니다 🕙 .
+"짝사랑 상대와의 패스트푸드점 햄버거" 시나리오에서는 기다림 🕙 이 많기 때문에, 동시 시스템 ⏸🔀⏯ 을 사용하는 것이 훨씬 더 합리적입니다.
-이 "기다림" 🕙 은 마이크로초 단위이지만, 모두 더해지면, 결국에는 매우 긴 대기시간이 됩니다.
+대부분의 웹 애플리케이션이 그렇습니다.
-따라서 웹 API를 위해 비동기 ⏸🔀⏯ 코드를 사용하는 것이 합리적입니다.
+매우 많은 사용자들이 있고, 서버는 그들의 좋지 않은 연결을 통해 요청이 전송되기를 기다립니다 🕙.
-대부분의 존재하는 유명한 파이썬 프레임워크 (Flask와 Django 등)은 새로운 비동기 기능들이 파이썬에 존재하기 전에 만들어졌습니다. 그래서, 그들의 배포 방식은 병렬 실행과 새로운 기능만큼 강력하지는 않은 예전 버전의 비동기 실행을 지원합니다.
+그리고 응답이 돌아오기를 다시 기다립니다 🕙.
-비동기 웹 파이썬(ASGI)에 대한 주요 명세가 웹소켓을 지원하기 위해 Django에서 개발 되었음에도 그렇습니다.
+이 "기다림" 🕙 은 마이크로초 단위로 측정되지만, 모두 합치면 결국 꽤 많은 대기 시간이 됩니다.
-이러한 종류의 비동기성은 (NodeJS는 병렬적이지 않음에도) NodeJS가 사랑받는 이유이고, 프로그래밍 언어로서의 Go의 강점입니다.
+그래서 웹 API에는 비동기 ⏸🔀⏯ 코드를 사용하는 것이 매우 합리적입니다.
-그리고 **FastAPI**를 사용함으로써 동일한 성능을 낼 수 있습니다.
+이러한 종류의 비동기성은 NodeJS가 인기 있는 이유(비록 NodeJS가 병렬은 아니지만)이자, 프로그래밍 언어로서 Go의 강점입니다.
-또한 병렬성과 비동기성을 동시에 사용할 수 있기 때문에, 대부분의 테스트가 완료된 NodeJS 프레임워크보다 더 높은 성능을 얻고 C에 더 가까운 컴파일 언어인 Go와 동등한 성능을 얻을 수 있습니다(모두 Starlette 덕분입니다).
+그리고 이것이 **FastAPI**로 얻는 것과 같은 수준의 성능입니다.
-### 동시성이 병렬성보다 더 나은가?
+또한 병렬성과 비동기성을 동시에 사용할 수 있으므로, 대부분의 테스트된 NodeJS 프레임워크보다 더 높은 성능을 얻고, C에 더 가까운 컴파일 언어인 Go와 동등한 성능을 얻을 수 있습니다 (모두 Starlette 덕분입니다).
-그렇지 않습니다! 그것이 이야기의 교훈은 아닙니다.
+### 동시성이 병렬성보다 더 나은가요? { #is-concurrency-better-than-parallelism }
-동시성은 병렬성과 다릅니다. 그리고 그것은 많은 대기를 필요로하는 **특정한** 시나리오에서는 더 낫습니다. 이로 인해, 웹 응용프로그램 개발에서 동시성이 병렬성보다 일반적으로 훨씬 낫습니다. 하지만 모든 경우에 그런 것은 아닙니다.
+아니요! 그게 이 이야기의 교훈은 아닙니다.
-따라서, 균형을 맞추기 위해, 다음의 짧은 이야기를 상상해보십시오:
+동시성은 병렬성과 다릅니다. 그리고 많은 기다림이 포함되는 **특정한** 시나리오에서는 더 낫습니다. 그 때문에 웹 애플리케이션 개발에서는 일반적으로 병렬성보다 훨씬 더 낫습니다. 하지만 모든 것에 해당하진 않습니다.
-> 당신은 크고, 더러운 집을 청소해야합니다.
+그래서 균형을 맞추기 위해, 다음의 짧은 이야기를 상상해보세요:
+
+> 여러분은 크고 더러운 집을 청소해야 합니다.
*네, 이게 전부입니다*.
---
-어디에도 대기 🕙 는 없고, 집안 곳곳에서 해야하는 많은 작업들만 있습니다.
+어디에도 기다림 🕙 은 없고, 집의 여러 장소에서 해야 할 일이 많을 뿐입니다.
-버거 예시처럼 처음에는 거실, 그 다음은 부엌과 같은 식으로 순서를 정할 수도 있으나, 무엇도 기다리지 🕙 않고 계속해서 청소 작업만 수행하기 때문에, 순서는 아무런 영향을 미치지 않습니다.
+햄버거 예시처럼 거실부터, 그 다음은 부엌처럼 순서를 정할 수도 있지만, 어떤 것도 기다리지 🕙 않고 계속 청소만 하기 때문에, 순서는 아무런 영향을 주지 않습니다.
-순서가 있든 없든 동일한 시간이 소요될 것이고(동시성) 동일한 양의 작업을 하게 될 것입니다.
+순서가 있든 없든(동시성) 끝내는 데 걸리는 시간은 같고, 같은 양의 일을 하게 됩니다.
-하지만 이 경우에서, 8명의 전(前)-점원/요리사이면서-현(現)-청소부 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳 를 고용할 수 있고, 그들 각자(그리고 당신)가 집의 한 부분씩 맡아 청소를 한다면, 당신은 **병렬적**으로 작업을 수행할 수 있고, 조금의 도움이 있다면, 훨씬 더 빨리 끝낼 수 있습니다.
+하지만 이 경우, 전(前) 점원/요리사이자 현(現) 청소부가 된 8명을 데려올 수 있고, 각자(그리고 여러분)가 집의 구역을 하나씩 맡아 청소한다면, 추가 도움과 함께 모든 일을 **병렬**로 수행하여 훨씬 더 빨리 끝낼 수 있습니다.
-이 시나리오에서, (당신을 포함한) 각각의 청소부들은 프로세서가 될 것이고, 각자의 역할을 수행합니다.
+이 시나리오에서 (여러분을 포함한) 각 청소부는 프로세서가 되어, 맡은 일을 수행합니다.
-실행 시간의 대부분이 대기가 아닌 실제 작업에 소요되고, 컴퓨터에서 작업은 CPU에서 이루어지므로, 이러한 문제를 "CPU에 묶였"다고 합니다.
+그리고 실행 시간의 대부분이 기다림이 아니라 실제 작업에 쓰이고, 컴퓨터에서 작업은 CPU가 수행하므로, 이런 문제를 "CPU bound"라고 부릅니다.
---
-CPU에 묶인 연산에 관한 흔한 예시는 복잡한 수학 처리를 필요로 하는 경우입니다.
+CPU bound 작업의 흔한 예시는 복잡한 수학 처리가 필요한 것들입니다.
예를 들어:
-* **오디오** 또는 **이미지** 처리.
-* **컴퓨터 비전**: 하나의 이미지는 수백개의 픽셀로 구성되어있고, 각 픽셀은 3개의 값 / 색을 갖고 있으며, 일반적으로 해당 픽셀들에 대해 동시에 무언가를 계산해야하는 처리.
-* **머신러닝**: 일반적으로 많은 "행렬"과 "벡터" 곱셈이 필요합니다. 거대한 스프레드 시트에 수들이 있고 그 수들을 동시에 곱해야 한다고 생각해보십시오.
-* **딥러닝**: 머신러닝의 하위영역으로, 동일한 예시가 적용됩니다. 단지 이 경우에는 하나의 스프레드 시트에 곱해야할 수들이 있는 것이 아니라, 거대한 세트의 스프레드 시트들이 있고, 많은 경우에, 이 모델들을 만들고 사용하기 위해 특수한 프로세서를 사용합니다.
+* **오디오** 또는 **이미지** 처리
+* **컴퓨터 비전**: 이미지는 수백만 개의 픽셀로 구성되며, 각 픽셀은 3개의 값/색을 갖습니다. 보통 그 픽셀들에 대해 동시에 무언가를 계산해야 합니다.
+* **머신러닝**: 보통 많은 "matrix"와 "vector" 곱셈이 필요합니다. 숫자가 있는 거대한 스프레드시트를 생각하고, 그 모든 수를 동시에 곱한다고 생각해보세요.
+* **딥러닝**: 머신러닝의 하위 분야이므로 동일하게 적용됩니다. 다만 곱해야 할 숫자가 있는 스프레드시트가 하나가 아니라, 아주 큰 집합이며, 많은 경우 그 모델을 만들고/또는 사용하기 위해 특별한 프로세서를 사용합니다.
-### 동시성 + 병렬성: 웹 + 머신러닝
+### 동시성 + 병렬성: 웹 + 머신러닝 { #concurrency-parallelism-web-machine-learning }
-**FastAPI**를 사용하면 웹 개발에서는 매우 흔한 동시성의 이점을 (NodeJS의 주된 매력만큼) 얻을 수 있습니다.
+**FastAPI**를 사용하면 웹 개발에서 매우 흔한 동시성의 이점을( NodeJS의 주요 매력과 같은) 얻을 수 있습니다.
-뿐만 아니라 머신러닝 시스템과 같이 **CPU에 묶인** 작업을 위해 병렬성과 멀티프로세싱(다수의 프로세스를 병렬적으로 동작시키는 것)을 이용하는 것도 가능합니다.
+또한 머신러닝 시스템처럼 **CPU bound** 워크로드에 대해 병렬성과 멀티프로세싱(여러 프로세스를 병렬로 실행)을 활용할 수도 있습니다.
-파이썬이 **데이터 사이언스**, 머신러닝과 특히 딥러닝에 의 주된 언어라는 간단한 사실에 더해서, 이것은 FastAPI를 데이터 사이언스 / 머신러닝 웹 API와 응용프로그램에 (다른 것들보다) 좋은 선택지가 되게 합니다.
+이것은 파이썬이 **데이터 사이언스**, 머신러닝, 특히 딥러닝의 주요 언어라는 단순한 사실과 더해져, FastAPI를 데이터 사이언스/머신러닝 웹 API 및 애플리케이션(그 외에도 많은 것들)에 매우 잘 맞는 선택으로 만들어 줍니다.
-배포시 병렬을 어떻게 가능하게 하는지 알고싶다면, [배포](deployment/index.md){.internal-link target=_blank}문서를 참고하십시오.
+프로덕션에서 이 병렬성을 어떻게 달성하는지 보려면 [배포](deployment/index.md){.internal-link target=_blank} 섹션을 참고하세요.
-## `async`와 `await`
+## `async`와 `await` { #async-and-await }
-최신 파이썬 버전에는 비동기 코드를 정의하는 매우 직관적인 방법이 있습니다. 이는 이것을 평범한 "순차적" 코드로 보이게 하고, 적절한 순간에 당신을 위해 "대기"합니다.
+최신 파이썬 버전에는 비동기 코드를 정의하는 매우 직관적인 방법이 있습니다. 이 방법은 이를 평범한 "순차" 코드처럼 보이게 하고, 적절한 순간에 여러분을 위해 "기다림"을 수행합니다.
-연산이 결과를 전달하기 전에 대기를 해야하고 새로운 파이썬 기능들을 지원한다면, 이렇게 코드를 작성할 수 있습니다:
+결과를 주기 전에 기다림이 필요한 작업이 있고, 이러한 새로운 파이썬 기능을 지원한다면, 다음과 같이 작성할 수 있습니다:
```Python
burgers = await get_burgers(2)
```
-여기서 핵심은 `await`입니다. 이것은 파이썬에게 `burgers` 결과를 저장하기 이전에 `get_burgers(2)`의 작업이 완료되기를 🕙 기다리라고 ⏸ 말합니다. 이로 인해, 파이썬은 그동안 (다른 요청을 받는 것과 같은) 다른 작업을 수행해도 된다는 것을 🔀 ⏯ 알게될 것입니다.
+여기서 핵심은 `await`입니다. 이는 파이썬에게 `get_burgers(2)`가 그 일을 끝낼 때까지 🕙 기다리도록 ⏸ 말하고, 그 결과를 `burgers`에 저장하기 전에 완료되기를 기다리라고 합니다. 이를 통해 파이썬은 그동안(예: 다른 요청을 받는 것처럼) 다른 일을 하러 갈 수 있다는 것 🔀 ⏯ 을 알게 됩니다.
-`await`가 동작하기 위해, 이것은 비동기를 지원하는 함수 내부에 있어야 합니다. 이를 위해서 함수를 `async def`를 사용해 정의하기만 하면 됩니다:
+`await`가 동작하려면, 이 비동기성을 지원하는 함수 내부에 있어야 합니다. 그러려면 `async def`로 선언하기만 하면 됩니다:
```Python hl_lines="1"
async def get_burgers(number: int):
- # Do some asynchronous stuff to create the burgers
+ # 햄버거를 만들기 위한 비동기 처리를 수행
return burgers
```
-...`def`를 사용하는 대신:
+...`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`를 사용하면, 파이썬은 그 함수 내부에서 `await` 표현식에 주의해야 하며, 그 함수의 실행을 "일시정지" ⏸ 하고 다시 돌아오기 전에 다른 일을 하러 갈 수 있다는 것 🔀 을 알게 됩니다.
-`async def`f 함수를 호출하고자 할 때, "대기"해야합니다. 따라서, 아래는 동작하지 않습니다.
+`async def` 함수를 호출하고자 할 때는, 그 함수를 "await" 해야 합니다. 따라서 아래는 동작하지 않습니다:
```Python
-# This won't work, because get_burgers was defined with: async def
+# 동작하지 않습니다. get_burgers는 async def로 정의되었습니다
burgers = get_burgers(2)
```
---
-따라서, `await`f를 사용해서 호출할 수 있는 라이브러리를 사용한다면, 다음과 같이 `async def`를 사용하는 *경로 작동 함수*를 생성해야 합니다:
+따라서, `await`로 호출할 수 있다고 말하는 라이브러리를 사용한다면, 다음과 같이 그것을 사용하는 *경로 처리 함수*를 `async def`로 만들어야 합니다:
```Python hl_lines="2-3"
@app.get('/burgers')
@@ -317,94 +349,96 @@ async def read_burgers():
return burgers
```
-### 더 세부적인 기술적 사항
+### 더 세부적인 기술적 사항 { #more-technical-details }
+
+`await`는 `async def`로 정의된 함수 내부에서만 사용할 수 있다는 것을 눈치채셨을 것입니다.
-`await`가 `async def`를 사용하는 함수 내부에서만 사용이 가능하다는 것을 눈치채셨을 것입니다.
+하지만 동시에, `async def`로 정의된 함수는 "await" 되어야 합니다. 따라서 `async def`를 가진 함수는 `async def`로 정의된 함수 내부에서만 호출될 수 있습니다.
-하지만 동시에, `async def`로 정의된 함수들은 "대기"되어야만 합니다. 따라서, `async def`를 사용한 함수들은 역시 `async def`를 사용한 함수 내부에서만 호출될 수 있습니다.
+그렇다면, 닭이 먼저냐 달걀이 먼저냐처럼, 첫 번째 `async` 함수는 어떻게 호출할 수 있을까요?
-그렇다면 닭이 먼저냐, 달걀이 먼저냐, 첫 `async` 함수를 어떻게 호출할 수 있겠습니까?
+**FastAPI**로 작업한다면 걱정할 필요가 없습니다. 그 "첫" 함수는 여러분의 *경로 처리 함수*가 될 것이고, FastAPI는 올바르게 처리하는 방법을 알고 있기 때문입니다.
-**FastAPI**를 사용해 작업한다면 이것을 걱정하지 않아도 됩니다. 왜냐하면 그 "첫" 함수는 당신의 *경로 작동 함수*가 될 것이고, FastAPI는 어떻게 올바르게 처리할지 알고있기 때문입니다.
+하지만 FastAPI 없이 `async` / `await`를 사용하고 싶다면, 그것도 가능합니다.
-하지만 FastAPI를 사용하지 않고 `async` / `await`를 사용하고 싶다면, 이 역시 가능합니다.
+### 여러분만의 async 코드 작성하기 { #write-your-own-async-code }
-### 당신만의 비동기 코드 작성하기
+Starlette(그리고 **FastAPI**)는 AnyIO를 기반으로 하고 있으며, 파이썬 표준 라이브러리 asyncio와 Trio 모두와 호환됩니다.
-Starlette(그리고 FastAPI)는 AnyIO를 기반으로 하고있고, 따라서 파이썬 표준 라이브러리인 asyncio 및 Trio와 호환됩니다.
+특히, 코드에서 더 고급 패턴이 필요한 고급 동시성 사용 사례에서는 직접 AnyIO를 사용할 수 있습니다.
-특히, 코드에서 고급 패턴이 필요한 고급 동시성을 사용하는 경우 직접적으로 AnyIO를 사용할 수 있습니다.
+그리고 FastAPI를 사용하지 않더라도, 높은 호환성을 확보하고 그 이점(예: *structured concurrency*)을 얻기 위해 AnyIO로 여러분만의 async 애플리케이션을 작성할 수도 있습니다.
-FastAPI를 사용하지 않더라도, 높은 호환성 및 AnyIO의 이점(예: *구조화된 동시성*)을 취하기 위해 AnyIO를 사용해 비동기 응용프로그램을 작성할 수 있습니다.
+저는 AnyIO 위에 얇은 레이어로 또 다른 라이브러리를 만들었는데, 타입 어노테이션을 조금 개선하고 더 나은 **자동완성**, **인라인 오류** 등을 얻기 위한 것입니다. 또한 **이해**하고 **여러분만의 async 코드**를 작성하도록 돕는 친절한 소개와 튜토리얼도 제공합니다: Asyncer. 특히 **async 코드와 일반**(blocking/동기) 코드를 **결합**해야 한다면 아주 유용합니다.
-### 비동기 코드의 다른 형태
+### 비동기 코드의 다른 형태 { #other-forms-of-asynchronous-code }
-파이썬에서 `async`와 `await`를 사용하게 된 것은 비교적 최근의 일입니다.
+`async`와 `await`를 사용하는 이 스타일은 언어에서 비교적 최근에 추가되었습니다.
-하지만 이로 인해 비동기 코드 작업이 훨씬 간단해졌습니다.
+하지만 비동기 코드를 다루는 일을 훨씬 더 쉽게 만들어 줍니다.
-같은 (또는 거의 유사한) 문법은 최신 버전의 자바스크립트(브라우저와 NodeJS)에도 추가되었습니다.
+거의 동일한 문법이 최근 브라우저와 NodeJS의 최신 JavaScript에도 포함되었습니다.
-하지만 그 이전에, 비동기 코드를 처리하는 것은 꽤 복잡하고 어려운 일이었습니다.
+하지만 그 이전에는 비동기 코드를 처리하는 것이 훨씬 더 복잡하고 어려웠습니다.
-파이썬의 예전 버전이라면, 스레드 또는 Gevent를 사용할 수 있을 것입니다. 하지만 코드를 이해하고, 디버깅하고, 이에 대해 생각하는게 훨씬 복잡합니다.
+이전 버전의 파이썬에서는 스레드 또는 Gevent를 사용할 수 있었을 것입니다. 하지만 코드를 이해하고, 디버깅하고, 이에 대해 생각하는 것이 훨씬 더 복잡합니다.
-예전 버전의 NodeJS / 브라우저 자바스크립트라면, "콜백 함수"를 사용했을 것입니다. 그리고 이로 인해 "콜백 지옥"에 빠지게 될 수 있습니다.
+이전 버전의 NodeJS/브라우저 JavaScript에서는 "callback"을 사용했을 것입니다. 이는 "callback hell"로 이어집니다.
-## 코루틴
+## 코루틴 { #coroutines }
-**코루틴**은 `async def` 함수가 반환하는 것을 칭하는 매우 고급스러운 용어일 뿐입니다. 파이썬은 그것이 시작되고 어느 시점에서 완료되지만 내부에 `await`가 있을 때마다 내부적으로 일시정지⏸될 수도 있는 함수와 유사한 것이라는 사실을 알고있습니다.
+**코루틴**은 `async def` 함수가 반환하는 것에 대한 매우 고급스러운 용어일 뿐입니다. 파이썬은 그것이 함수와 비슷한 무언가로서 시작할 수 있고, 어느 시점에 끝나지만, 내부에 `await`가 있을 때마다 내부적으로도 일시정지 ⏸ 될 수 있다는 것을 알고 있습니다.
-그러나 `async` 및 `await`와 함께 비동기 코드를 사용하는 이 모든 기능들은 "코루틴"으로 간단히 요약됩니다. 이것은 Go의 주된 핵심 기능인 "고루틴"에 견줄 수 있습니다.
+하지만 `async` 및 `await`와 함께 비동기 코드를 사용하는 이 모든 기능은 종종 "코루틴"을 사용한다고 요약됩니다. 이는 Go의 주요 핵심 기능인 "Goroutines"에 비견됩니다.
-## 결론
+## 결론 { #conclusion }
-상기 문장을 다시 한 번 봅시다:
+위의 같은 문장을 다시 봅시다:
-> 최신 파이썬 버전은 **`async` 및 `await`** 문법과 함께 **“코루틴”**이라고 하는 것을 사용하는 **“비동기 코드”**를 지원합니다.
+> 최신 파이썬 버전은 **“코루틴”**이라고 하는 것을 사용하는 **“비동기 코드”**를 **`async` 및 `await`** 문법과 함께 지원합니다.
-이제 이 말을 조금 더 이해할 수 있을 것입니다. ✨
+이제 더 이해가 될 것입니다. ✨
-이것이 (Starlette을 통해) FastAPI를 강하게 하면서 그것이 인상적인 성능을 낼 수 있게 합니다.
+이 모든 것이 FastAPI(Starlette을 통해)를 구동하고, 인상적인 성능을 내게 하는 원동력입니다.
-## 매우 세부적인 기술적 사항
+## 매우 세부적인 기술적 사항 { #very-technical-details }
/// warning | 경고
-이 부분은 넘어가도 됩니다.
+이 부분은 아마 건너뛰어도 됩니다.
이것들은 **FastAPI**가 내부적으로 어떻게 동작하는지에 대한 매우 세부적인 기술사항입니다.
-만약 기술적 지식(코루틴, 스레드, 블록킹 등)이 있고 FastAPI가 어떻게 `async def` vs `def`를 다루는지 궁금하다면, 계속하십시오.
+(코루틴, 스레드, 블로킹 등) 같은 기술 지식이 꽤 있고 FastAPI가 `async def`와 일반 `def`를 어떻게 처리하는지 궁금하다면, 계속 읽어보세요.
///
-### 경로 작동 함수
+### 경로 처리 함수 { #path-operation-functions }
-경로 작동 함수를 `async def` 대신 일반적인 `def`로 선언하는 경우, (서버를 차단하는 것처럼) 그것을 직접 호출하는 대신 대기중인 외부 스레드풀에서 실행됩니다.
+*경로 처리 함수*를 `async def` 대신 일반적인 `def`로 선언하면, (서버를 블로킹할 수 있으므로 직접 호출하는 대신) 외부 스레드풀에서 실행되고 그 결과를 await 합니다.
-만약 상기에 묘사된대로 동작하지 않는 비동기 프로그램을 사용해왔고 약간의 성능 향상 (약 100 나노초)을 위해 `def`를 사용해서 계산만을 위한 사소한 *경로 작동 함수*를 정의해왔다면, **FastAPI**는 이와는 반대라는 것에 주의하십시오. 이러한 경우에, *경로 작동 함수*가 블로킹 I/O를 수행하는 코드를 사용하지 않는 한 `async def`를 사용하는 편이 더 낫습니다.
+위에서 설명한 방식으로 동작하지 않는 다른 async 프레임워크를 사용해본 적이 있고, 아주 작은 성능 향상(약 100 나노초)을 위해 계산만 하는 사소한 *경로 처리 함수*를 일반 `def`로 정의하곤 했다면, **FastAPI**에서는 그 효과가 정반대가 될 수 있다는 점에 유의하세요. 이런 경우에는 *경로 처리 함수*에서 블로킹 I/O 를 수행하는 코드를 사용하지 않는 한 `async def`를 사용하는 편이 더 낫습니다.
-하지만 두 경우 모두, FastAPI가 당신이 전에 사용하던 프레임워크보다 [더 빠를](index.md#_11){.internal-link target=_blank} (최소한 비견될) 확률이 높습니다.
+그럼에도 두 경우 모두, **FastAPI**는 이전에 사용하던 프레임워크보다 [여전히 더 빠를](index.md#performance){.internal-link target=_blank} 가능성이 높습니다(또는 최소한 비슷합니다).
-### 의존성
+### 의존성 { #dependencies }
-의존성에도 동일하게 적용됩니다. 의존성이 `async def`가 아닌 표준 `def` 함수라면, 외부 스레드풀에서 실행됩니다.
+[의존성](tutorial/dependencies/index.md){.internal-link target=_blank}에도 동일하게 적용됩니다. 의존성이 `async def` 대신 표준 `def` 함수라면, 외부 스레드풀에서 실행됩니다.
-### 하위-의존성
+### 하위 의존성 { #sub-dependencies }
-함수 정의시 매개변수로 서로를 필요로하는 다수의 의존성과 하위-의존성을 가질 수 있고, 그 중 일부는 `async def`로, 다른 일부는 일반적인 `def`로 생성되었을 수 있습니다. 이것은 여전히 잘 동작하고, 일반적인 `def`로 생성된 것들은 "대기"되는 대신에 (스레드풀로부터) 외부 스레드에서 호출됩니다.
+서로를 필요로 하는 여러 의존성과 [하위 의존성](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}을 함수 정의의 매개변수로 가질 수 있으며, 그중 일부는 `async def`로, 다른 일부는 일반 `def`로 생성되었을 수 있습니다. 그래도 정상 동작하며, 일반 `def`로 생성된 것들은 "await"되는 대신 (스레드풀에서) 외부 스레드에서 호출됩니다.
-### 다른 유틸리티 함수
+### 다른 유틸리티 함수 { #other-utility-functions }
-직접 호출되는 다른 모든 유틸리티 함수는 일반적인 `def`나 `async def`로 생성될 수 있고 FastAPI는 이를 호출하는 방식에 영향을 미치지 않습니다.
+직접 호출하는 다른 모든 유틸리티 함수는 일반 `def`나 `async def`로 생성될 수 있으며, FastAPI는 호출 방식에 영향을 주지 않습니다.
-이것은 FastAPI가 당신을 위해 호출하는 함수와는 반대입니다: *경로 작동 함수*와 의존성
+이는 FastAPI가 여러분을 위해 호출하는 함수(즉, *경로 처리 함수*와 의존성)와 대비됩니다.
-만약 당신의 유틸리티 함수가 `def`를 사용한 일반적인 함수라면, 스레드풀에서가 아니라 직접 호출(당신이 코드에 작성한 대로)될 것이고, `async def`로 생성된 함수라면 코드에서 호출할 때 그 함수를 `await` 해야 합니다.
+유틸리티 함수가 `def`로 만든 일반 함수라면, 스레드풀이 아니라 직접(코드에 작성한 대로) 호출됩니다. 그리고 `async def`로 생성된 함수라면, 코드에서 호출할 때 그 함수를 `await` 해야 합니다.
---
-다시 말하지만, 이것은 당신이 이것에 대해 찾고있던 경우에 한해 유용할 매우 세부적인 기술사항입니다.
+다시 말하지만, 이것들은 아마도 이를 찾고 있었던 경우에 유용한 매우 세부적인 기술사항입니다.
-그렇지 않은 경우, 상기의 가이드라인만으로도 충분할 것입니다: [바쁘신 경우](#_1).
+그렇지 않다면, 위 섹션의 가이드라인이면 충분합니다: 바쁘신가요?.
diff --git a/docs/ko/docs/benchmarks.md b/docs/ko/docs/benchmarks.md
index aff8ae70e..2d4fdbedd 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 dbc814bbd..0705e120c 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/concepts.md b/docs/ko/docs/deployment/concepts.md
new file mode 100644
index 000000000..dd7edd1ba
--- /dev/null
+++ b/docs/ko/docs/deployment/concepts.md
@@ -0,0 +1,321 @@
+# 배포 개념 { #deployments-concepts }
+
+**FastAPI** 애플리케이션(사실 어떤 종류의 웹 API든)을 배포할 때는, 여러분이 신경 써야 할 여러 개념이 있습니다. 그리고 이 개념들을 활용하면 **애플리케이션을 배포하기 위한 가장 적절한 방법**을 찾을 수 있습니다.
+
+중요한 개념 몇 가지는 다음과 같습니다:
+
+* 보안 - HTTPS
+* 시작 시 실행
+* 재시작
+* 복제(실행 중인 프로세스 수)
+* 메모리
+* 시작 전 사전 단계
+
+이것들이 **배포**에 어떤 영향을 주는지 살펴보겠습니다.
+
+결국 최종 목표는 **API 클라이언트에 서비스를 제공**할 때 **보안**을 보장하고, **중단을 피하며**, **컴퓨팅 리소스**(예: 원격 서버/가상 머신)를 가능한 한 효율적으로 사용하는 것입니다. 🚀
+
+여기서 이 **개념들**을 조금 더 설명하겠습니다. 그러면 서로 매우 다른 환경, 심지어 아직 존재하지 않는 **미래**의 환경에서도 API를 어떻게 배포할지 결정하는 데 필요한 **직관**을 얻을 수 있을 것입니다.
+
+이 개념들을 고려하면, 여러분은 **자신의 API**를 배포하기 위한 최선의 방법을 **평가하고 설계**할 수 있습니다.
+
+다음 장들에서는 FastAPI 애플리케이션을 배포하기 위한 더 **구체적인 레시피**를 제공하겠습니다.
+
+하지만 지금은, 이 중요한 **개념적 아이디어**들을 확인해 봅시다. 이 개념들은 다른 어떤 종류의 웹 API에도 동일하게 적용됩니다. 💡
+
+## 보안 - HTTPS { #security-https }
+
+[이전 HTTPS 장](https.md){.internal-link target=_blank}에서 HTTPS가 API에 암호화를 제공하는 방식에 대해 배웠습니다.
+
+또한 HTTPS는 일반적으로 애플리케이션 서버 바깥의 **외부** 컴포넌트인 **TLS Termination Proxy**가 제공한다는 것도 확인했습니다.
+
+그리고 **HTTPS 인증서 갱신**을 담당하는 무언가가 필요합니다. 같은 컴포넌트가 그 역할을 할 수도 있고, 다른 무언가가 담당할 수도 있습니다.
+
+### HTTPS를 위한 도구 예시 { #example-tools-for-https }
+
+TLS Termination Proxy로 사용할 수 있는 도구는 예를 들어 다음과 같습니다:
+
+* Traefik
+ * 인증서 갱신을 자동으로 처리 ✨
+* Caddy
+ * 인증서 갱신을 자동으로 처리 ✨
+* Nginx
+ * 인증서 갱신을 위해 Certbot 같은 외부 컴포넌트 사용
+* HAProxy
+ * 인증서 갱신을 위해 Certbot 같은 외부 컴포넌트 사용
+* Nginx 같은 Ingress Controller를 사용하는 Kubernetes
+ * 인증서 갱신을 위해 cert-manager 같은 외부 컴포넌트 사용
+* 클라우드 제공자가 서비스 일부로 내부적으로 처리(아래를 읽어보세요 👇)
+
+또 다른 선택지는 HTTPS 설정을 포함해 더 많은 일을 대신해주는 **클라우드 서비스**를 사용하는 것입니다. 제약이 있거나 비용이 더 들 수도 있습니다. 하지만 그 경우에는 TLS Termination Proxy를 직접 설정할 필요가 없습니다.
+
+다음 장에서 구체적인 예시를 보여드리겠습니다.
+
+---
+
+다음으로 고려할 개념들은 실제로 여러분의 API를 실행하는 프로그램(예: Uvicorn)과 관련된 내용입니다.
+
+## 프로그램과 프로세스 { #program-and-process }
+
+실행 중인 "**프로세스**"에 대해 많이 이야기하게 될 텐데, 이 말이 무엇을 의미하는지, 그리고 "**프로그램**"이라는 단어와 무엇이 다른지 명확히 해두는 것이 유용합니다.
+
+### 프로그램이란 { #what-is-a-program }
+
+**프로그램**이라는 단어는 보통 여러 가지를 가리키는 데 사용됩니다:
+
+* 여러분이 작성하는 **코드**, 즉 **Python 파일**들
+* 운영체제에서 **실행**할 수 있는 **파일**, 예: `python`, `python.exe`, `uvicorn`
+* 운영체제에서 **실행 중**인 특정 프로그램으로, CPU를 사용하고 메모리에 내용을 저장합니다. 이것을 **프로세스**라고도 합니다.
+
+### 프로세스란 { #what-is-a-process }
+
+**프로세스**라는 단어는 보통 더 구체적으로, 운영체제에서 실행 중인 것(위 마지막 항목처럼)만을 가리키는 데 사용됩니다:
+
+* 운영체제에서 **실행 중**인 특정 프로그램
+ * 파일이나 코드를 의미하는 것이 아니라, 운영체제가 **실제로 실행**하고 관리하는 대상을 **구체적으로** 의미합니다.
+* 어떤 프로그램이든 어떤 코드든, **실행**될 때만 무언가를 **할 수 있습니다**. 즉, **프로세스가 실행 중**일 때입니다.
+* 프로세스는 여러분이, 혹은 운영체제가 **종료**(또는 “kill”)할 수 있습니다. 그러면 실행이 멈추고, 더 이상 **아무것도 할 수 없습니다**.
+* 컴퓨터에서 실행 중인 각 애플리케이션 뒤에는 프로세스가 있습니다. 실행 중인 프로그램, 각 창 등도 마찬가지입니다. 그리고 컴퓨터가 켜져 있는 동안 보통 많은 프로세스가 **동시에** 실행됩니다.
+* **같은 프로그램**의 **여러 프로세스**가 동시에 실행될 수도 있습니다.
+
+운영체제의 “작업 관리자(task manager)”나 “시스템 모니터(system monitor)”(또는 비슷한 도구)를 확인해 보면, 이런 프로세스가 많이 실행 중인 것을 볼 수 있습니다.
+
+또 예를 들어, 같은 브라우저 프로그램(Firefox, Chrome, Edge 등)을 실행하는 프로세스가 여러 개 있는 것도 보일 가능성이 큽니다. 보통 탭마다 하나의 프로세스를 실행하고, 그 외에도 추가 프로세스 몇 개가 더 있습니다.
+
+
+
+---
+
+이제 **프로세스**와 **프로그램**의 차이를 알았으니, 배포에 대한 이야기를 계속해 보겠습니다.
+
+## 시작 시 실행 { #running-on-startup }
+
+대부분의 경우 웹 API를 만들면, 클라이언트가 언제나 접근할 수 있도록 **항상 실행**되고 중단되지 않기를 원합니다. 물론 특정 상황에서만 실행하고 싶은 특별한 이유가 있을 수는 있지만, 대부분은 지속적으로 실행되며 **사용 가능**한 상태이기를 원합니다.
+
+### 원격 서버에서 { #in-a-remote-server }
+
+원격 서버(클라우드 서버, 가상 머신 등)를 설정할 때, 가장 단순한 방법은 로컬 개발 때처럼 수동으로 `fastapi run`(Uvicorn을 사용합니다)이나 비슷한 명령을 실행하는 것입니다.
+
+이 방식은 동작하고, **개발 중에는** 유용합니다.
+
+하지만 서버에 대한 연결이 끊기면, 실행 중인 **프로세스**도 아마 종료될 것입니다.
+
+또 서버가 재시작되면(예: 업데이트 이후, 혹은 클라우드 제공자의 마이그레이션 이후) 여러분은 아마 **알아차리지 못할** 겁니다. 그 결과, 프로세스를 수동으로 다시 시작해야 한다는 사실도 모르게 됩니다. 그러면 API는 그냥 죽은 상태로 남습니다. 😱
+
+### 시작 시 자동 실행 { #run-automatically-on-startup }
+
+일반적으로 서버 프로그램(예: Uvicorn)은 서버가 시작될 때 자동으로 시작되고, **사람의 개입** 없이도 FastAPI 앱을 실행하는 프로세스가 항상 실행 중이도록(예: FastAPI 앱을 실행하는 Uvicorn) 구성하고 싶을 것입니다.
+
+### 별도의 프로그램 { #separate-program }
+
+이를 위해 보통 애플리케이션이 시작 시 실행되도록 보장하는 **별도의 프로그램**을 둡니다. 그리고 많은 경우, 데이터베이스 같은 다른 컴포넌트나 애플리케이션도 함께 실행되도록 보장합니다.
+
+### 시작 시 실행을 위한 도구 예시 { #example-tools-to-run-at-startup }
+
+이 역할을 할 수 있는 도구 예시는 다음과 같습니다:
+
+* Docker
+* Kubernetes
+* Docker Compose
+* Swarm Mode의 Docker
+* Systemd
+* Supervisor
+* 클라우드 제공자가 서비스 일부로 내부적으로 처리
+* 기타...
+
+다음 장에서 더 구체적인 예시를 제공하겠습니다.
+
+## 재시작 { #restarts }
+
+애플리케이션이 시작 시 실행되도록 보장하는 것과 비슷하게, 장애가 발생했을 때 **재시작**되도록 보장하고 싶을 것입니다.
+
+### 우리는 실수합니다 { #we-make-mistakes }
+
+사람은 언제나 **실수**합니다. 소프트웨어에는 거의 *항상* 여기저기에 숨은 **버그**가 있습니다. 🐛
+
+그리고 개발자는 버그를 발견하고 새로운 기능을 구현하면서 코드를 계속 개선합니다(새로운 버그도 추가할 수 있겠죠 😅).
+
+### 작은 오류는 자동으로 처리됨 { #small-errors-automatically-handled }
+
+FastAPI로 웹 API를 만들 때 코드에 오류가 있으면, FastAPI는 보통 그 오류를 발생시킨 단일 요청 안에만 문제를 가둡니다. 🛡
+
+클라이언트는 해당 요청에 대해 **500 Internal Server Error**를 받지만, 애플리케이션은 완전히 크래시하지 않고 다음 요청부터는 계속 동작합니다.
+
+### 더 큰 오류 - 크래시 { #bigger-errors-crashes }
+
+그럼에도 불구하고, 우리가 작성한 코드가 **전체 애플리케이션을 크래시**시켜 Uvicorn과 Python 자체가 종료되는 경우가 있을 수 있습니다. 💥
+
+그래도 한 군데 오류 때문에 애플리케이션이 죽은 채로 남아 있기를 바라지는 않을 것입니다. 망가진 경로 처리를 제외한 나머지 *경로 처리*라도 **계속 실행**되기를 원할 가능성이 큽니다.
+
+### 크래시 후 재시작 { #restart-after-crash }
+
+하지만 실행 중인 **프로세스**가 크래시하는 정말 심각한 오류의 경우에는, 적어도 몇 번은 프로세스를 **재시작**하도록 담당하는 외부 컴포넌트가 필요합니다...
+
+/// tip | 팁
+
+...다만 애플리케이션 전체가 **즉시 계속 크래시**한다면, 무한히 재시작하는 것은 아마 의미가 없을 것입니다. 그런 경우에는 개발 중에, 또는 최소한 배포 직후에 알아차릴 가능성이 큽니다.
+
+그러니 여기서는, 특정한 경우에만 전체가 크래시할 수 있고 **미래**에도 그럴 수 있으며, 그래도 재시작하는 것이 의미 있는 주요 사례에 집중해 봅시다.
+
+///
+
+애플리케이션을 재시작하는 역할은 **외부 컴포넌트**가 맡는 편이 보통 좋습니다. 그 시점에는 Uvicorn과 Python을 포함한 애플리케이션이 이미 크래시했기 때문에, 같은 앱의 같은 코드 안에서 이를 해결할 방법이 없기 때문입니다.
+
+### 자동 재시작을 위한 도구 예시 { #example-tools-to-restart-automatically }
+
+대부분의 경우 **시작 시 실행**에 사용한 도구가 자동 **재시작**도 함께 처리합니다.
+
+예를 들어 다음이 가능합니다:
+
+* Docker
+* Kubernetes
+* Docker Compose
+* Swarm Mode의 Docker
+* Systemd
+* Supervisor
+* 클라우드 제공자가 서비스 일부로 내부적으로 처리
+* 기타...
+
+## 복제 - 프로세스와 메모리 { #replication-processes-and-memory }
+
+FastAPI 애플리케이션은 Uvicorn을 실행하는 `fastapi` 명령 같은 서버 프로그램을 사용하면, **하나의 프로세스**로 실행하더라도 여러 클라이언트를 동시에 처리할 수 있습니다.
+
+하지만 많은 경우, 여러 워커 프로세스를 동시에 실행하고 싶을 것입니다.
+
+### 여러 프로세스 - 워커 { #multiple-processes-workers }
+
+단일 프로세스가 처리할 수 있는 것보다 클라이언트가 더 많고(예: 가상 머신이 그리 크지 않을 때), 서버 CPU에 **여러 코어**가 있다면, 같은 애플리케이션을 실행하는 **여러 프로세스**를 동시에 띄우고 요청을 분산시킬 수 있습니다.
+
+같은 API 프로그램을 **여러 프로세스**로 실행할 때, 이 프로세스들을 보통 **workers**라고 부릅니다.
+
+### 워커 프로세스와 포트 { #worker-processes-and-ports }
+
+[HTTPS에 대한 문서](https.md){.internal-link target=_blank}에서, 서버에서 하나의 포트와 IP 주소 조합에는 하나의 프로세스만 리스닝할 수 있다는 것을 기억하시나요?
+
+이것은 여전히 사실입니다.
+
+따라서 **여러 프로세스**를 동시에 실행하려면, 먼저 **포트에서 리스닝하는 단일 프로세스**가 있어야 하고, 그 프로세스가 어떤 방식으로든 각 워커 프로세스로 통신을 전달해야 합니다.
+
+### 프로세스당 메모리 { #memory-per-process }
+
+이제 프로그램이 메모리에 무언가를 로드한다고 해봅시다. 예를 들어 머신러닝 모델을 변수에 올리거나 큰 파일 내용을 변수에 올리는 경우입니다. 이런 것들은 서버의 **메모리(RAM)**를 어느 정도 사용합니다.
+
+그리고 여러 프로세스는 보통 **메모리를 공유하지 않습니다**. 즉, 각 실행 중인 프로세스는 자체 변수와 메모리를 갖습니다. 코드에서 메모리를 많이 사용한다면, **각 프로세스**가 그만큼의 메모리를 사용하게 됩니다.
+
+### 서버 메모리 { #server-memory }
+
+예를 들어 코드가 크기 **1 GB**의 머신러닝 모델을 로드한다고 해봅시다. API를 프로세스 하나로 실행하면 RAM을 최소 1GB 사용합니다. 그리고 **4개 프로세스**(워커 4개)를 시작하면 각각 1GB RAM을 사용합니다. 즉 총 **4 GB RAM**을 사용합니다.
+
+그런데 원격 서버나 가상 머신의 RAM이 3GB뿐이라면, 4GB를 넘게 로드하려고 할 때 문제가 생깁니다. 🚨
+
+### 여러 프로세스 - 예시 { #multiple-processes-an-example }
+
+이 예시에서는 **Manager Process**가 두 개의 **Worker Processes**를 시작하고 제어합니다.
+
+이 Manager Process는 아마 IP의 **포트**에서 리스닝하는 역할을 합니다. 그리고 모든 통신을 워커 프로세스로 전달합니다.
+
+워커 프로세스들이 실제로 애플리케이션을 실행하며, **요청**을 받아 **응답**을 반환하는 주요 연산을 수행하고, RAM에 변수로 로드한 모든 내용을 담습니다.
+
+---- +**FastAPI**는 다른 사람들이 이전에 해온 작업이 없었다면 존재하지 않았을 것입니다. -## 조사 +그 전에 만들어진 많은 도구들이 이것의 탄생에 영감을 주었습니다. -여러 대안을 사용해 보며 다양한 도구에서 배운 점들을 모아 저와 개발팀에게 가장 적합한 방식을 찾았습니다. +저는 여러 해 동안 새로운 프레임워크를 만드는 것을 피하고 있었습니다. 처음에는 **FastAPI**가 다루는 모든 기능을 여러 다른 프레임워크, 플러그인, 도구들을 사용해 해결하려고 했습니다. -예를 들어, 표준 :term:Python 타입 힌트-그래서 **FastAPI**의 코드를 작성하기 전에 몇 달 동안 OpenAPI, JSON Schema, OAuth2 명세를 연구하며 이들의 관계와 겹치는 부분, 차이점을 이해했습니다. +## 조사 { #investigation } ---- +이전의 모든 대안을 사용해 보면서, 각 도구로부터 배울 기회를 얻었고, 아이디어를 가져와 제가 일해온 개발 팀들과 저 자신에게 가장 적합하다고 찾은 방식으로 조합할 수 있었습니다. -## 디자인 +예를 들어, 이상적으로는 표준 Python 타입 힌트에 기반해야 한다는 점이 분명했습니다. -그 후, **FastAPI** 사용자가 될 개발자로서 사용하고 싶은 개발자 "API"를 디자인했습니다. +또한, 가장 좋은 접근법은 이미 존재하는 표준을 사용하는 것이었습니다. -[Python Developer Survey](https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools)에 따르면 약 80%의 Python 개발자가 PyCharm, VS Code, Jedi 기반 편집기 등에서 개발합니다. 이 과정에서 여러 아이디어를 테스트했습니다. +그래서 **FastAPI**의 코딩을 시작하기도 전에, OpenAPI, JSON Schema, OAuth2 등과 같은 명세를 몇 달 동안 공부했습니다. 이들의 관계, 겹치는 부분, 차이점을 이해하기 위해서였습니다. -대부분의 다른 편집기도 유사하게 동작하기 때문에, **FastAPI**의 이점은 거의 모든 편집기에서 누릴 수 있습니다. +## 디자인 { #design } -이 과정을 통해 코드 중복을 최소화하고, 모든 곳에서 자동 완성, 타입 검사, 에러 확인 기능이 제공되는 최적의 방식을 찾아냈습니다. +그 다음에는 (FastAPI를 사용하는 개발자로서) 사용자로서 갖고 싶었던 개발자 "API"를 디자인하는 데 시간을 썼습니다. -이 모든 것은 개발자들에게 최고의 개발 경험을 제공하기 위해 설계되었습니다. +가장 인기 있는 Python 편집기들: PyCharm, VS Code, Jedi 기반 편집기에서 여러 아이디어를 테스트했습니다. ---- +약 80%의 사용자를 포함하는 최근 Python Developer Survey에 따르면 그렇습니다. -## 필요조건 +즉, **FastAPI**는 Python 개발자의 80%가 사용하는 편집기들로 특별히 테스트되었습니다. 그리고 대부분의 다른 편집기도 유사하게 동작하는 경향이 있으므로, 그 모든 이점은 사실상 모든 편집기에서 동작해야 합니다. -여러 대안을 테스트한 후, [Pydantic](https://docs.pydantic.dev/)을 사용하기로 결정했습니다. +그렇게 해서 코드 중복을 가능한 한 많이 줄이고, 어디서나 자동 완성, 타입 및 에러 검사 등을 제공하는 최선의 방법을 찾을 수 있었습니다. -이후 저는 **Pydantic**이 JSON Schema와 완벽히 호환되도록 개선하고, 다양한 제약 조건 선언을 지원하며, 여러 편집기에서의 자동 완성과 타입 검사 기능을 향상하기 위해 기여했습니다. +모든 개발자에게 최고의 개발 경험을 제공하는 방식으로 말입니다. -또한, 또 다른 주요 필요조건이었던 [Starlette](https://www.starlette.io/)에도 기여했습니다. +## 필요조건 { #requirements } ---- +여러 대안을 테스트한 후, 장점 때문에 **Pydantic**을 사용하기로 결정했습니다. -## 개발 +그 후, JSON Schema를 완전히 준수하도록 하고, 제약 조건 선언을 정의하는 다양한 방식을 지원하며, 여러 편집기에서의 테스트를 바탕으로 편집기 지원(타입 검사, 자동 완성)을 개선하기 위해 기여했습니다. -**FastAPI**를 개발하기 시작할 즈음에는 대부분의 준비가 이미 완료된 상태였습니다. 설계가 정의되었고, 필요조건과 도구가 준비되었으며, 표준과 명세에 대한 지식도 충분했습니다. +개발 과정에서, 또 다른 핵심 필요조건인 **Starlette**에도 기여했습니다. ---- +## 개발 { #development } -## 미래 +**FastAPI** 자체를 만들기 시작했을 때쯤에는, 대부분의 조각들이 이미 갖춰져 있었고, 디자인은 정의되어 있었으며, 필요조건과 도구는 준비되어 있었고, 표준과 명세에 대한 지식도 명확하고 최신 상태였습니다. -현시점에서 **FastAPI**가 많은 사람들에게 유용하다는 것이 명백해졌습니다. +## 미래 { #future } -여러 용도에 더 적합한 도구로서 기존 대안보다 선호되고 있습니다. -이미 많은 개발자와 팀들이 **FastAPI**에 의존해 프로젝트를 진행 중입니다 (저와 제 팀도 마찬가지입니다). +이 시점에는, **FastAPI**가 그 아이디어와 함께 많은 사람들에게 유용하다는 것이 이미 분명합니다. -하지만 여전히 개선해야 할 점과 추가할 기능들이 많이 남아 있습니다. +많은 사용 사례에 더 잘 맞기 때문에 이전 대안들보다 선택되고 있습니다. + +많은 개발자와 팀이 이미 자신의 프로젝트를 위해 **FastAPI**에 의존하고 있습니다(저와 제 팀도 포함해서요). + +하지만 여전히, 앞으로 나올 개선 사항과 기능들이 많이 있습니다. + +**FastAPI**의 미래는 밝습니다. -**FastAPI**는 밝은 미래로 나아가고 있습니다. 그리고 [여러분의 도움](help-fastapi.md){.internal-link target=_blank}은 큰 힘이 됩니다. diff --git a/docs/ko/docs/how-to/authentication-error-status-code.md b/docs/ko/docs/how-to/authentication-error-status-code.md new file mode 100644 index 000000000..47120cae6 --- /dev/null +++ b/docs/ko/docs/how-to/authentication-error-status-code.md @@ -0,0 +1,17 @@ +# 이전 403 인증 오류 상태 코드 사용하기 { #use-old-403-authentication-error-status-codes } + +FastAPI 버전 `0.122.0` 이전에는, 통합 보안 유틸리티가 인증 실패 후 클라이언트에 오류를 반환할 때 HTTP 상태 코드 `403 Forbidden`을 사용했습니다. + +FastAPI 버전 `0.122.0`부터는 더 적절한 HTTP 상태 코드 `401 Unauthorized`를 사용하며, HTTP 명세인 RFC 7235, RFC 9110를 따라 응답에 합리적인 `WWW-Authenticate` 헤더를 반환합니다. + +하지만 어떤 이유로든 클라이언트가 이전 동작에 의존하고 있다면, 보안 클래스에서 `make_not_authenticated_error` 메서드를 오버라이드하여 이전 동작으로 되돌릴 수 있습니다. + +예를 들어, 기본값인 `401 Unauthorized` 오류 대신 `403 Forbidden` 오류를 반환하는 `HTTPBearer`의 서브클래스를 만들 수 있습니다: + +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} + +/// tip | 팁 + +함수는 예외를 `raise`하는 것이 아니라 예외 인스턴스를 `return`한다는 점에 유의하세요. 예외를 발생시키는(`raise`) 작업은 내부 코드의 나머지 부분에서 수행됩니다. + +/// diff --git a/docs/ko/docs/how-to/conditional-openapi.md b/docs/ko/docs/how-to/conditional-openapi.md index 79c7f0dd2..16e683366 100644 --- a/docs/ko/docs/how-to/conditional-openapi.md +++ b/docs/ko/docs/how-to/conditional-openapi.md @@ -1,46 +1,41 @@ -# 조건부적인 OpenAPI +# 조건부 OpenAPI { #conditional-openapi } -필요한 경우, 설정 및 환경 변수를 사용하여 환경에 따라 조건부로 OpenAPI를 구성하고 완전히 비활성화할 수도 있습니다. +필요한 경우, 설정 및 환경 변수를 사용하여 환경에 따라 OpenAPI를 조건부로 구성하고 완전히 비활성화할 수도 있습니다. -## 보안, API 및 docs에 대해서 +## 보안, API 및 docs에 대해서 { #about-security-apis-and-docs } 프로덕션에서, 문서화된 사용자 인터페이스(UI)를 숨기는 것이 API를 보호하는 방법이 *되어서는 안 됩니다*. -이는 API에 추가적인 보안을 제공하지 않으며, *경로 작업*은 여전히 동일한 위치에서 사용 할 수 있습니다. +이는 API에 추가적인 보안을 제공하지 않으며, *경로 처리*는 여전히 동일한 위치에서 사용 할 수 있습니다. 코드에 보안 결함이 있다면, 그 결함은 여전히 존재할 것입니다. -문서를 숨기는 것은 API와 상호작용하는 방법을 이해하기 어렵게 만들며, 프로덕션에서 디버깅을 더 어렵게 만들 수 있습니다. 이는 단순히 '모호성에 의한 보안'의 한 형태로 간주될 수 있습니다. +문서를 숨기는 것은 API와 상호작용하는 방법을 이해하기 어렵게 만들며, 프로덕션에서 디버깅을 더 어렵게 만들 수 있습니다. 이는 단순히 Security through obscurity의 한 형태로 간주될 수 있습니다. API를 보호하고 싶다면, 예를 들어 다음과 같은 더 나은 방법들이 있습니다: -* 요청 본문과 응답에 대해 잘 정의된 Pydantic 모델을 사용하도록 하세요. - +* 요청 본문과 응답에 대해 잘 정의된 Pydantic 모델이 있는지 확인하세요. * 종속성을 사용하여 필요한 권한과 역할을 구성하세요. - -* 평문 비밀번호를 절대 저장하지 말고, 오직 암호화된 비밀번호만 저장하세요. - -* Passlib과 JWT 토큰과 같은 잘 알려진 암호화 도구들을 구현하고 사용하세요. - +* 평문 비밀번호를 절대 저장하지 말고, 비밀번호 해시만 저장하세요. +* pwdlib와 JWT 토큰 등과 같은 잘 알려진 암호화 도구들을 구현하고 사용하세요. * 필요한 곳에 OAuth2 범위를 사용하여 더 세분화된 권한 제어를 추가하세요. +* ...등등. -* 등등.... - -그럼에도 불구하고, 특정 환경(예: 프로덕션)에서 또는 환경 변수의 설정에 따라 API 문서를 비활성화해야 하는 매우 특정한 사용 사례가 있을 수 있습니다. +그럼에도 불구하고, 특정 환경(예: 프로덕션)에서 또는 환경 변수의 설정에 따라 API docs를 비활성화해야 하는 매우 특정한 사용 사례가 있을 수 있습니다. -## 설정 및 환경변수의 조건부 OpenAPI +## 설정 및 환경변수의 조건부 OpenAPI { #conditional-openapi-from-settings-and-env-vars } -동일한 Pydantic 설정을 사용하여 생성된 OpenAPI 및 문서 UI를 쉽게 구성할 수 있습니다. +동일한 Pydantic 설정을 사용하여 생성된 OpenAPI 및 docs UI를 쉽게 구성할 수 있습니다. 예를 들어: -{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} +{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} 여기서 `openapi_url` 설정을 기본값인 `"/openapi.json"`으로 선언합니다. 그런 뒤, 우리는 `FastAPI` 앱을 만들 때 그것을 사용합니다. -환경 변수 `OPENAPI_URL`을 빈 문자열로 설정하여 OpenAPI(문서 UI 포함)를 비활성화할 수도 있습니다. 예를 들어: +그런 다음 환경 변수 `OPENAPI_URL`을 빈 문자열로 설정하여 OpenAPI(UI docs 포함)를 비활성화할 수도 있습니다. 예를 들어:에 기반하는 것이 이상적이라는 점이 명확했습니다. +하지만 어느 시점에는, 이전 도구들의 최고의 아이디어를 가져와 가능한 한 최선의 방식으로 조합하고, 이전에는 존재하지 않았던 언어 기능(Python 3.6+ type hints)을 사용해 이 모든 기능을 제공하는 무언가를 만드는 것 외에는 다른 선택지가 없었습니다. -또한, 이미 존재하는 표준을 활용하는 것이 가장 좋은 접근법이라 판단했습니다. +
-## 테마 변경
+## 테마 변경 { #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/how-to/custom-docs-ui-assets.md b/docs/ko/docs/how-to/custom-docs-ui-assets.md
new file mode 100644
index 000000000..d6383c29c
--- /dev/null
+++ b/docs/ko/docs/how-to/custom-docs-ui-assets.md
@@ -0,0 +1,185 @@
+# 커스텀 Docs UI 정적 에셋(자체 호스팅) { #custom-docs-ui-static-assets-self-hosting }
+
+API 문서는 **Swagger UI**와 **ReDoc**을 사용하며, 각각 JavaScript와 CSS 파일이 필요합니다.
+
+기본적으로 이러한 파일은 CDN에서 제공됩니다.
+
+하지만 이를 커스터마이징할 수 있으며, 특정 CDN을 지정하거나 파일을 직접 제공할 수도 있습니다.
+
+## JavaScript와 CSS용 커스텀 CDN { #custom-cdn-for-javascript-and-css }
+
+예를 들어 다른 CDN을 사용하고 싶다고 해봅시다. 예를 들면 `https://unpkg.com/`을 사용하려는 경우입니다.
+
+이는 예를 들어 특정 국가에서 일부 URL을 제한하는 경우에 유용할 수 있습니다.
+
+### 자동 문서 비활성화하기 { #disable-the-automatic-docs }
+
+첫 번째 단계는 자동 문서를 비활성화하는 것입니다. 기본적으로 자동 문서는 기본 CDN을 사용하기 때문입니다.
+
+비활성화하려면 `FastAPI` 앱을 생성할 때 해당 URL을 `None`으로 설정하세요:
+
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *}
+
+### 커스텀 문서 포함하기 { #include-the-custom-docs }
+
+이제 커스텀 문서를 위한 *경로 처리*를 만들 수 있습니다.
+
+FastAPI 내부 함수를 재사용해 문서용 HTML 페이지를 생성하고, 필요한 인자를 전달할 수 있습니다:
+
+* `openapi_url`: 문서 HTML 페이지가 API의 OpenAPI 스키마를 가져올 수 있는 URL입니다. 여기서는 `app.openapi_url` 속성을 사용할 수 있습니다.
+* `title`: API의 제목입니다.
+* `oauth2_redirect_url`: 기본값을 사용하려면 여기서 `app.swagger_ui_oauth2_redirect_url`을 사용할 수 있습니다.
+* `swagger_js_url`: Swagger UI 문서의 HTML이 **JavaScript** 파일을 가져올 수 있는 URL입니다. 커스텀 CDN URL입니다.
+* `swagger_css_url`: Swagger UI 문서의 HTML이 **CSS** 파일을 가져올 수 있는 URL입니다. 커스텀 CDN URL입니다.
+
+ReDoc도 마찬가지입니다...
+
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *}
+
+/// tip | 팁
+
+`swagger_ui_redirect`에 대한 *경로 처리*는 OAuth2를 사용할 때 도움이 되는 헬퍼입니다.
+
+API를 OAuth2 provider와 통합하면 인증을 수행한 뒤 획득한 자격 증명으로 API 문서로 다시 돌아올 수 있습니다. 그리고 실제 OAuth2 인증을 사용해 API와 상호작용할 수 있습니다.
+
+Swagger UI가 이 과정을 백그라운드에서 처리해 주지만, 이를 위해 이 "redirect" 헬퍼가 필요합니다.
+
+///
+
+### 테스트용 *경로 처리* 만들기 { #create-a-path-operation-to-test-it }
+
+이제 모든 것이 제대로 동작하는지 테스트할 수 있도록 *경로 처리*를 하나 만드세요:
+
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *}
+
+### 테스트하기 { #test-it }
+
+이제 http://127.0.0.1:8000/docs에서 문서에 접속한 뒤 페이지를 새로고침하면, 새 CDN에서 에셋을 불러오는 것을 확인할 수 있습니다.
+
+## 문서용 JavaScript와 CSS 자체 호스팅하기 { #self-hosting-javascript-and-css-for-docs }
+
+JavaScript와 CSS를 자체 호스팅하는 것은 예를 들어, 오프라인 상태이거나 외부 인터넷에 접근할 수 없는 환경, 또는 로컬 네트워크에서도 앱이 계속 동작해야 할 때 유용할 수 있습니다.
+
+여기서는 동일한 FastAPI 앱에서 해당 파일을 직접 제공하고, 문서가 이를 사용하도록 설정하는 방법을 살펴봅니다.
+
+### 프로젝트 파일 구조 { #project-file-structure }
+
+프로젝트 파일 구조가 다음과 같다고 해봅시다:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+```
+
+이제 해당 정적 파일을 저장할 디렉터리를 만드세요.
+
+새 파일 구조는 다음과 같을 수 있습니다:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+└── static/
+```
+
+### 파일 다운로드하기 { #download-the-files }
+
+문서에 필요한 정적 파일을 다운로드해서 `static/` 디렉터리에 넣으세요.
+
+각 링크를 우클릭한 뒤 "링크를 다른 이름으로 저장..."과 비슷한 옵션을 선택하면 될 것입니다.
+
+**Swagger UI**는 다음 파일을 사용합니다:
+
+* `swagger-ui-bundle.js`
+* `swagger-ui.css`
+
+**ReDoc**은 다음 파일을 사용합니다:
+
+* `redoc.standalone.js`
+
+이후 파일 구조는 다음과 같을 수 있습니다:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+└── static
+ ├── redoc.standalone.js
+ ├── swagger-ui-bundle.js
+ └── swagger-ui.css
+```
+
+### 정적 파일 제공하기 { #serve-the-static-files }
+
+* `StaticFiles`를 import합니다.
+* 특정 경로에 `StaticFiles()` 인스턴스를 "마운트(mount)"합니다.
+
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *}
+
+### 정적 파일 테스트하기 { #test-the-static-files }
+
+애플리케이션을 시작하고 http://127.0.0.1:8000/static/redoc.standalone.js로 이동하세요.
+
+**ReDoc**용 매우 긴 JavaScript 파일이 보일 것입니다.
+
+예를 들어 다음과 같이 시작할 수 있습니다:
+
+```JavaScript
+/*! For license information please see redoc.standalone.js.LICENSE.txt */
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):
+...
+```
+
+이는 앱에서 정적 파일을 제공할 수 있고, 문서용 정적 파일을 올바른 위치에 배치했다는 것을 확인해 줍니다.
+
+이제 문서가 이 정적 파일을 사용하도록 앱을 설정할 수 있습니다.
+
+### 정적 파일을 위한 자동 문서 비활성화하기 { #disable-the-automatic-docs-for-static-files }
+
+커스텀 CDN을 사용할 때와 마찬가지로, 첫 단계는 자동 문서를 비활성화하는 것입니다. 자동 문서는 기본적으로 CDN을 사용합니다.
+
+비활성화하려면 `FastAPI` 앱을 생성할 때 해당 URL을 `None`으로 설정하세요:
+
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *}
+
+### 정적 파일을 위한 커스텀 문서 포함하기 { #include-the-custom-docs-for-static-files }
+
+그리고 커스텀 CDN을 사용할 때와 동일한 방식으로, 이제 커스텀 문서를 위한 *경로 처리*를 만들 수 있습니다.
+
+다시 한 번, FastAPI 내부 함수를 재사용해 문서용 HTML 페이지를 생성하고, 필요한 인자를 전달할 수 있습니다:
+
+* `openapi_url`: 문서 HTML 페이지가 API의 OpenAPI 스키마를 가져올 수 있는 URL입니다. 여기서는 `app.openapi_url` 속성을 사용할 수 있습니다.
+* `title`: API의 제목입니다.
+* `oauth2_redirect_url`: 기본값을 사용하려면 여기서 `app.swagger_ui_oauth2_redirect_url`을 사용할 수 있습니다.
+* `swagger_js_url`: Swagger UI 문서의 HTML이 **JavaScript** 파일을 가져올 수 있는 URL입니다. **이제는 여러분의 앱이 직접 제공하는 파일입니다**.
+* `swagger_css_url`: Swagger UI 문서의 HTML이 **CSS** 파일을 가져올 수 있는 URL입니다. **이제는 여러분의 앱이 직접 제공하는 파일입니다**.
+
+ReDoc도 마찬가지입니다...
+
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *}
+
+/// tip | 팁
+
+`swagger_ui_redirect`에 대한 *경로 처리*는 OAuth2를 사용할 때 도움이 되는 헬퍼입니다.
+
+API를 OAuth2 provider와 통합하면 인증을 수행한 뒤 획득한 자격 증명으로 API 문서로 다시 돌아올 수 있습니다. 그리고 실제 OAuth2 인증을 사용해 API와 상호작용할 수 있습니다.
+
+Swagger UI가 이 과정을 백그라운드에서 처리해 주지만, 이를 위해 이 "redirect" 헬퍼가 필요합니다.
+
+///
+
+### 정적 파일 테스트용 *경로 처리* 만들기 { #create-a-path-operation-to-test-static-files }
+
+이제 모든 것이 제대로 동작하는지 테스트할 수 있도록 *경로 처리*를 하나 만드세요:
+
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *}
+
+### 정적 파일 UI 테스트하기 { #test-static-files-ui }
+
+이제 WiFi 연결을 끊고 http://127.0.0.1:8000/docs에서 문서에 접속한 뒤 페이지를 새로고침해 보세요.
+
+인터넷이 없어도 API 문서를 보고, API와 상호작용할 수 있을 것입니다.
diff --git a/docs/ko/docs/how-to/custom-request-and-route.md b/docs/ko/docs/how-to/custom-request-and-route.md
new file mode 100644
index 000000000..335193bb3
--- /dev/null
+++ b/docs/ko/docs/how-to/custom-request-and-route.md
@@ -0,0 +1,109 @@
+# 커스텀 Request 및 APIRoute 클래스 { #custom-request-and-apiroute-class }
+
+일부 경우에는 `Request`와 `APIRoute` 클래스에서 사용되는 로직을 오버라이드하고 싶을 수 있습니다.
+
+특히, 이는 middleware에 있는 로직의 좋은 대안이 될 수 있습니다.
+
+예를 들어, 애플리케이션에서 처리되기 전에 요청 바디를 읽거나 조작하고 싶을 때가 그렇습니다.
+
+/// danger | 위험
+
+이 기능은 "고급" 기능입니다.
+
+**FastAPI**를 이제 막 시작했다면 이 섹션은 건너뛰는 것이 좋습니다.
+
+///
+
+## 사용 사례 { #use-cases }
+
+사용 사례에는 다음이 포함됩니다:
+
+* JSON이 아닌 요청 바디를 JSON으로 변환하기(예: `msgpack`).
+* gzip으로 압축된 요청 바디 압축 해제하기.
+* 모든 요청 바디를 자동으로 로깅하기.
+
+## 커스텀 요청 바디 인코딩 처리하기 { #handling-custom-request-body-encodings }
+
+커스텀 `Request` 서브클래스를 사용해 gzip 요청의 압축을 해제하는 방법을 살펴보겠습니다.
+
+그리고 그 커스텀 요청 클래스를 사용하기 위한 `APIRoute` 서브클래스도 함께 보겠습니다.
+
+### 커스텀 `GzipRequest` 클래스 만들기 { #create-a-custom-gziprequest-class }
+
+/// tip | 팁
+
+이 예시는 동작 방식 시연을 위한 장난감 예제입니다. Gzip 지원이 필요하다면 제공되는 [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}를 사용할 수 있습니다.
+
+///
+
+먼저, `GzipRequest` 클래스를 만듭니다. 이 클래스는 `Request.body()` 메서드를 덮어써서, 적절한 헤더가 있는 경우 바디를 압축 해제합니다.
+
+헤더에 `gzip`이 없으면 바디를 압축 해제하려고 시도하지 않습니다.
+
+이렇게 하면 동일한 route 클래스가 gzip으로 압축된 요청과 압축되지 않은 요청을 모두 처리할 수 있습니다.
+
+{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *}
+
+### 커스텀 `GzipRoute` 클래스 만들기 { #create-a-custom-gziproute-class }
+
+다음으로, `GzipRequest`를 활용하는 `fastapi.routing.APIRoute`의 커스텀 서브클래스를 만듭니다.
+
+이번에는 `APIRoute.get_route_handler()` 메서드를 오버라이드합니다.
+
+이 메서드는 함수를 반환합니다. 그리고 그 함수가 요청을 받아 응답을 반환합니다.
+
+여기서는 원본 요청으로부터 `GzipRequest`를 만들기 위해 이를 사용합니다.
+
+{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *}
+
+/// note | 기술 세부사항
+
+`Request`에는 `request.scope` 속성이 있는데, 이는 요청과 관련된 메타데이터를 담고 있는 Python `dict`입니다.
+
+`Request`에는 또한 `request.receive`가 있는데, 이는 요청의 바디를 "받기(receive)" 위한 함수입니다.
+
+`scope` `dict`와 `receive` 함수는 모두 ASGI 명세의 일부입니다.
+
+그리고 이 두 가지, `scope`와 `receive`가 새로운 `Request` 인스턴스를 만드는 데 필요한 것들입니다.
+
+`Request`에 대해 더 알아보려면 Starlette의 Requests 문서를 확인하세요.
+
+///
+
+`GzipRequest.get_route_handler`가 반환하는 함수가 다르게 하는 유일한 것은 `Request`를 `GzipRequest`로 변환하는 것입니다.
+
+이렇게 하면, 우리의 `GzipRequest`가 *경로 처리*로 전달하기 전에(필요하다면) 데이터의 압축 해제를 담당하게 됩니다.
+
+그 이후의 모든 처리 로직은 동일합니다.
+
+하지만 `GzipRequest.body`에서 변경을 했기 때문에, 필요할 때 **FastAPI**가 로드하는 시점에 요청 바디는 자동으로 압축 해제됩니다.
+
+## 예외 핸들러에서 요청 바디 접근하기 { #accessing-the-request-body-in-an-exception-handler }
+
+/// tip | 팁
+
+같은 문제를 해결하려면 `RequestValidationError`에 대한 커스텀 핸들러에서 `body`를 사용하는 편이 아마 훨씬 더 쉽습니다([오류 처리하기](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}).
+
+하지만 이 예시도 여전히 유효하며, 내부 컴포넌트와 상호작용하는 방법을 보여줍니다.
+
+///
+
+같은 접근 방식을 사용해 예외 핸들러에서 요청 바디에 접근할 수도 있습니다.
+
+필요한 것은 `try`/`except` 블록 안에서 요청을 처리하는 것뿐입니다:
+
+{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *}
+
+예외가 발생하더라도 `Request` 인스턴스는 여전히 스코프 안에 남아 있으므로, 오류를 처리할 때 요청 바디를 읽고 활용할 수 있습니다:
+
+{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *}
+
+## 라우터에서의 커스텀 `APIRoute` 클래스 { #custom-apiroute-class-in-a-router }
+
+`APIRouter`의 `route_class` 파라미터를 설정할 수도 있습니다:
+
+{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *}
+
+이 예시에서는 `router` 아래의 *경로 처리*들이 커스텀 `TimedRoute` 클래스를 사용하며, 응답을 생성하는 데 걸린 시간을 담은 추가 `X-Response-Time` 헤더가 응답에 포함됩니다:
+
+{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *}
diff --git a/docs/ko/docs/how-to/extending-openapi.md b/docs/ko/docs/how-to/extending-openapi.md
new file mode 100644
index 000000000..d04d6c23e
--- /dev/null
+++ b/docs/ko/docs/how-to/extending-openapi.md
@@ -0,0 +1,80 @@
+# OpenAPI 확장하기 { #extending-openapi }
+
+생성된 OpenAPI 스키마를 수정해야 하는 경우가 있습니다.
+
+이 섹션에서 그 방법을 살펴보겠습니다.
+
+## 일반적인 과정 { #the-normal-process }
+
+일반적인(기본) 과정은 다음과 같습니다.
+
+`FastAPI` 애플리케이션(인스턴스)에는 OpenAPI 스키마를 반환해야 하는 `.openapi()` 메서드가 있습니다.
+
+애플리케이션 객체를 생성하는 과정에서 `/openapi.json`(또는 `openapi_url`에 설정한 경로)용 *경로 처리*가 등록됩니다.
+
+이 경로 처리는 애플리케이션의 `.openapi()` 메서드 결과를 JSON 응답으로 반환할 뿐입니다.
+
+기본적으로 `.openapi()` 메서드는 프로퍼티 `.openapi_schema`에 내용이 있는지 확인하고, 있으면 그 내용을 반환합니다.
+
+없으면 `fastapi.openapi.utils.get_openapi`에 있는 유틸리티 함수를 사용해 생성합니다.
+
+그리고 `get_openapi()` 함수는 다음을 파라미터로 받습니다:
+
+* `title`: 문서에 표시되는 OpenAPI 제목.
+* `version`: API 버전. 예: `2.5.0`.
+* `openapi_version`: 사용되는 OpenAPI 스펙 버전. 기본값은 최신인 `3.1.0`.
+* `summary`: API에 대한 짧은 요약.
+* `description`: API 설명. markdown을 포함할 수 있으며 문서에 표시됩니다.
+* `routes`: 라우트 목록. 각각 등록된 *경로 처리*입니다. `app.routes`에서 가져옵니다.
+
+/// info | 정보
+
+`summary` 파라미터는 OpenAPI 3.1.0 이상에서 사용할 수 있으며, FastAPI 0.99.0 이상에서 지원됩니다.
+
+///
+
+## 기본값 덮어쓰기 { #overriding-the-defaults }
+
+위 정보를 바탕으로, 동일한 유틸리티 함수를 사용해 OpenAPI 스키마를 생성하고 필요한 각 부분을 덮어쓸 수 있습니다.
+
+예를 들어, 커스텀 로고를 포함하기 위한 ReDoc의 OpenAPI 확장을 추가해 보겠습니다.
+
+### 일반적인 **FastAPI** { #normal-fastapi }
+
+먼저, 평소처럼 **FastAPI** 애플리케이션을 모두 작성합니다:
+
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[1,4,7:9] *}
+
+### OpenAPI 스키마 생성하기 { #generate-the-openapi-schema }
+
+그다음 `custom_openapi()` 함수 안에서, 동일한 유틸리티 함수를 사용해 OpenAPI 스키마를 생성합니다:
+
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[2,15:21] *}
+
+### OpenAPI 스키마 수정하기 { #modify-the-openapi-schema }
+
+이제 OpenAPI 스키마의 `info` "object"에 커스텀 `x-logo`를 추가하여 ReDoc 확장을 더할 수 있습니다:
+
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *}
+
+### OpenAPI 스키마 캐시하기 { #cache-the-openapi-schema }
+
+생성한 스키마를 저장하기 위한 "cache"로 `.openapi_schema` 프로퍼티를 사용할 수 있습니다.
+
+이렇게 하면 사용자가 API 문서를 열 때마다 애플리케이션이 스키마를 매번 생성하지 않아도 됩니다.
+
+스키마는 한 번만 생성되고, 이후 요청에서는 같은 캐시된 스키마가 사용됩니다.
+
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *}
+
+### 메서드 오버라이드하기 { #override-the-method }
+
+이제 `.openapi()` 메서드를 새 함수로 교체할 수 있습니다.
+
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *}
+
+### 확인하기 { #check-it }
+
+http://127.0.0.1:8000/redoc로 이동하면 커스텀 로고(이 예시에서는 **FastAPI** 로고)를 사용하는 것을 확인할 수 있습니다:
+
+
diff --git a/docs/ko/docs/how-to/general.md b/docs/ko/docs/how-to/general.md
new file mode 100644
index 000000000..a18dc68a2
--- /dev/null
+++ b/docs/ko/docs/how-to/general.md
@@ -0,0 +1,39 @@
+# 일반 - 사용 방법 - 레시피 { #general-how-to-recipes }
+
+일반적이거나 자주 나오는 질문에 대해, 문서의 다른 위치로 안내하는 몇 가지 포인터를 소개합니다.
+
+## 데이터 필터링 - 보안 { #filter-data-security }
+
+반환하면 안 되는 데이터를 과도하게 반환하지 않도록 하려면, [튜토리얼 - 응답 모델 - 반환 타입](../tutorial/response-model.md){.internal-link target=_blank} 문서를 읽어보세요.
+
+## 문서화 태그 - OpenAPI { #documentation-tags-openapi }
+
+*경로 처리*에 태그를 추가하고, 문서 UI에서 이를 그룹화하려면 [튜토리얼 - 경로 처리 구성 - 태그](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank} 문서를 읽어보세요.
+
+## 문서화 요약 및 설명 - OpenAPI { #documentation-summary-and-description-openapi }
+
+*경로 처리*에 요약과 설명을 추가하고, 문서 UI에 표시하려면 [튜토리얼 - 경로 처리 구성 - 요약 및 설명](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank} 문서를 읽어보세요.
+
+## 문서화 응답 설명 - OpenAPI { #documentation-response-description-openapi }
+
+문서 UI에 표시되는 응답의 설명을 정의하려면 [튜토리얼 - 경로 처리 구성 - 응답 설명](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank} 문서를 읽어보세요.
+
+## 문서화 *경로 처리* 지원 중단하기 - OpenAPI { #documentation-deprecate-a-path-operation-openapi }
+
+*경로 처리*를 지원 중단(deprecate)으로 표시하고, 문서 UI에 보여주려면 [튜토리얼 - 경로 처리 구성 - 지원 중단](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank} 문서를 읽어보세요.
+
+## 어떤 데이터든 JSON 호환으로 변환하기 { #convert-any-data-to-json-compatible }
+
+어떤 데이터든 JSON 호환 형식으로 변환하려면 [튜토리얼 - JSON 호환 인코더](../tutorial/encoder.md){.internal-link target=_blank} 문서를 읽어보세요.
+
+## OpenAPI 메타데이터 - 문서 { #openapi-metadata-docs }
+
+라이선스, 버전, 연락처 등의 정보를 포함해 OpenAPI 스키마에 메타데이터를 추가하려면 [튜토리얼 - 메타데이터와 문서 URL](../tutorial/metadata.md){.internal-link target=_blank} 문서를 읽어보세요.
+
+## OpenAPI 사용자 정의 URL { #openapi-custom-url }
+
+OpenAPI URL을 커스터마이즈(또는 제거)하려면 [튜토리얼 - 메타데이터와 문서 URL](../tutorial/metadata.md#openapi-url){.internal-link target=_blank} 문서를 읽어보세요.
+
+## OpenAPI 문서 URL { #openapi-docs-urls }
+
+자동으로 생성되는 문서 사용자 인터페이스에서 사용하는 URL을 업데이트하려면 [튜토리얼 - 메타데이터와 문서 URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank} 문서를 읽어보세요.
diff --git a/docs/ko/docs/how-to/graphql.md b/docs/ko/docs/how-to/graphql.md
new file mode 100644
index 000000000..3cc467eb7
--- /dev/null
+++ b/docs/ko/docs/how-to/graphql.md
@@ -0,0 +1,60 @@
+# GraphQL { #graphql }
+
+**FastAPI**는 **ASGI** 표준을 기반으로 하므로, ASGI와도 호환되는 어떤 **GraphQL** 라이브러리든 매우 쉽게 통합할 수 있습니다.
+
+같은 애플리케이션에서 일반 FastAPI **경로 처리**와 GraphQL을 함께 조합할 수 있습니다.
+
+/// tip | 팁
+
+**GraphQL**은 몇 가지 매우 특정한 사용 사례를 해결합니다.
+
+일반적인 **web API**와 비교했을 때 **장점**과 **단점**이 있습니다.
+
+여러분의 사용 사례에서 **이점**이 **단점**을 상쇄하는지 꼭 평가해 보세요. 🤓
+
+///
+
+## GraphQL 라이브러리 { #graphql-libraries }
+
+다음은 **ASGI** 지원이 있는 **GraphQL** 라이브러리들입니다. **FastAPI**와 함께 사용할 수 있습니다:
+
+* Strawberry 🍓
+ * FastAPI용 문서 제공
+* Ariadne
+ * FastAPI용 문서 제공
+* Tartiflette
+ * ASGI 통합을 제공하기 위해 Tartiflette ASGI 사용
+* Graphene
+ * starlette-graphene3 사용
+
+## Strawberry로 GraphQL 사용하기 { #graphql-with-strawberry }
+
+**GraphQL**로 작업해야 하거나 작업하고 싶다면, **Strawberry**를 **권장**합니다. **FastAPI**의 설계와 가장 가깝고, 모든 것이 **type annotations**에 기반해 있기 때문입니다.
+
+사용 사례에 따라 다른 라이브러리를 선호할 수도 있지만, 제게 묻는다면 아마 **Strawberry**를 먼저 시도해 보라고 제안할 것입니다.
+
+다음은 Strawberry를 FastAPI와 통합하는 방법에 대한 간단한 미리보기입니다:
+
+{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
+
+Strawberry 문서에서 Strawberry에 대해 더 알아볼 수 있습니다.
+
+또한 FastAPI에서 Strawberry 사용에 대한 문서도 확인해 보세요.
+
+## Starlette의 예전 `GraphQLApp` { #older-graphqlapp-from-starlette }
+
+이전 버전의 Starlette에는 Graphene과 통합하기 위한 `GraphQLApp` 클래스가 포함되어 있었습니다.
+
+이것은 Starlette에서 deprecated 되었지만, 이를 사용하던 코드가 있다면 같은 사용 사례를 다루고 **거의 동일한 인터페이스**를 가진 starlette-graphene3로 쉽게 **마이그레이션**할 수 있습니다.
+
+/// tip | 팁
+
+GraphQL이 필요하다면, 커스텀 클래스와 타입 대신 type annotations에 기반한 Strawberry를 여전히 확인해 보시길 권장합니다.
+
+///
+
+## 더 알아보기 { #learn-more }
+
+공식 GraphQL 문서에서 **GraphQL**에 대해 더 알아볼 수 있습니다.
+
+또한 위에서 설명한 각 라이브러리에 대해서도 해당 링크에서 더 자세히 읽어볼 수 있습니다.
diff --git a/docs/ko/docs/how-to/index.md b/docs/ko/docs/how-to/index.md
new file mode 100644
index 000000000..9321c488b
--- /dev/null
+++ b/docs/ko/docs/how-to/index.md
@@ -0,0 +1,13 @@
+# How To - 레시피 { #how-to-recipes }
+
+여기에서는 **여러 주제**에 대한 다양한 레시피(“how to” 가이드)를 볼 수 있습니다.
+
+대부분의 아이디어는 어느 정도 **서로 독립적**이며, 대부분의 경우 **여러분의 프로젝트**에 직접 적용되는 경우에만 학습하면 됩니다.
+
+프로젝트에 흥미롭고 유용해 보이는 것이 있다면 확인해 보세요. 그렇지 않다면 아마 건너뛰어도 됩니다.
+
+/// tip | 팁
+
+**FastAPI를 구조적으로 학습**하고 싶다면(권장), 대신 [튜토리얼 - 사용자 가이드](../tutorial/index.md){.internal-link target=_blank}를 장별로 읽어보세요.
+
+///
diff --git a/docs/ko/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/ko/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
new file mode 100644
index 000000000..6e528ecaf
--- /dev/null
+++ b/docs/ko/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
@@ -0,0 +1,135 @@
+# Pydantic v1에서 Pydantic v2로 마이그레이션하기 { #migrate-from-pydantic-v1-to-pydantic-v2 }
+
+오래된 FastAPI 앱이 있다면 Pydantic 버전 1을 사용하고 있을 수 있습니다.
+
+FastAPI 0.100.0 버전은 Pydantic v1 또는 v2 중 하나를 지원했습니다. 설치되어 있는 쪽을 사용했습니다.
+
+FastAPI 0.119.0 버전에서는 v2로의 마이그레이션을 쉽게 하기 위해, Pydantic v2 내부에서 Pydantic v1을(`pydantic.v1`로) 부분적으로 지원하기 시작했습니다.
+
+FastAPI 0.126.0 버전에서는 Pydantic v1 지원을 중단했지만, `pydantic.v1`은 잠시 동안 계속 지원했습니다.
+
+/// warning | 경고
+
+Pydantic 팀은 **Python 3.14**부터 최신 Python 버전에서 Pydantic v1 지원을 중단했습니다.
+
+여기에는 `pydantic.v1`도 포함되며, Python 3.14 이상에서는 더 이상 지원되지 않습니다.
+
+Python의 최신 기능을 사용하려면 Pydantic v2를 사용하고 있는지 확인해야 합니다.
+
+///
+
+Pydantic v1을 사용하는 오래된 FastAPI 앱이 있다면, 여기서는 이를 Pydantic v2로 마이그레이션하는 방법과 점진적 마이그레이션을 돕는 **FastAPI 0.119.0의 기능**을 소개하겠습니다.
+
+## 공식 가이드 { #official-guide }
+
+Pydantic에는 v1에서 v2로의 공식 Migration Guide가 있습니다.
+
+여기에는 무엇이 바뀌었는지, 검증이 이제 어떻게 더 정확하고 엄격해졌는지, 가능한 주의사항 등도 포함되어 있습니다.
+
+변경된 내용을 더 잘 이해하기 위해 읽어보면 좋습니다.
+
+## 테스트 { #tests }
+
+앱에 대한 [tests](../tutorial/testing.md){.internal-link target=_blank}가 있는지 확인하고, 지속적 통합(CI)에서 테스트를 실행하세요.
+
+이렇게 하면 업그레이드를 진행하면서도 모든 것이 기대한 대로 계속 동작하는지 확인할 수 있습니다.
+
+## `bump-pydantic` { #bump-pydantic }
+
+많은 경우, 커스터마이징 없이 일반적인 Pydantic 모델을 사용하고 있다면 Pydantic v1에서 Pydantic v2로의 마이그레이션 과정 대부분을 자동화할 수 있습니다.
+
+같은 Pydantic 팀이 제공하는 `bump-pydantic`를 사용할 수 있습니다.
+
+이 도구는 변경해야 하는 코드의 대부분을 자동으로 바꾸는 데 도움을 줍니다.
+
+그 다음 테스트를 실행해서 모든 것이 동작하는지 확인하면 됩니다. 잘 된다면 끝입니다. 😎
+
+## v2 안의 Pydantic v1 { #pydantic-v1-in-v2 }
+
+Pydantic v2는 Pydantic v1의 모든 것을 서브모듈 `pydantic.v1`로 포함합니다. 하지만 이는 Python 3.13보다 높은 버전에서는 더 이상 지원되지 않습니다.
+
+즉, Pydantic v2의 최신 버전을 설치한 뒤, 이 서브모듈에서 예전 Pydantic v1 구성 요소를 import하여 예전 Pydantic v1을 설치한 것처럼 사용할 수 있습니다.
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *}
+
+### v2 안의 Pydantic v1에 대한 FastAPI 지원 { #fastapi-support-for-pydantic-v1-in-v2 }
+
+FastAPI 0.119.0부터는 v2로의 마이그레이션을 쉽게 하기 위해, Pydantic v2 내부의 Pydantic v1에 대해서도 부분적인 지원이 있습니다.
+
+따라서 Pydantic을 최신 v2로 업그레이드하고, import를 `pydantic.v1` 서브모듈을 사용하도록 바꾸면, 많은 경우 그대로 동작합니다.
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *}
+
+/// warning | 경고
+
+Pydantic 팀이 Python 3.14부터 최신 Python 버전에서 Pydantic v1을 더 이상 지원하지 않으므로, `pydantic.v1`을 사용하는 것 역시 Python 3.14 이상에서는 지원되지 않는다는 점을 염두에 두세요.
+
+///
+
+### 같은 앱에서 Pydantic v1과 v2 함께 사용하기 { #pydantic-v1-and-v2-on-the-same-app }
+
+Pydantic에서는 Pydantic v2 모델의 필드를 Pydantic v1 모델로 정의하거나 그 반대로 하는 것을 **지원하지 않습니다**.
+
+```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
+```
+
+...하지만 같은 앱에서 Pydantic v1과 v2를 사용하되, 모델을 분리해서 둘 수는 있습니다.
+
+```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
+```
+
+어떤 경우에는 FastAPI 앱의 같은 **경로 처리**에서 Pydantic v1과 v2 모델을 함께 사용하는 것도 가능합니다:
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *}
+
+위 예제에서 입력 모델은 Pydantic v1 모델이고, 출력 모델(`response_model=ItemV2`로 정의됨)은 Pydantic v2 모델입니다.
+
+### Pydantic v1 파라미터 { #pydantic-v1-parameters }
+
+Pydantic v1 모델과 함께 `Body`, `Query`, `Form` 등 파라미터용 FastAPI 전용 도구 일부를 사용해야 한다면, Pydantic v2로의 마이그레이션을 마칠 때까지 `fastapi.temp_pydantic_v1_params`에서 import할 수 있습니다:
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *}
+
+### 단계적으로 마이그레이션하기 { #migrate-in-steps }
+
+/// tip | 팁
+
+먼저 `bump-pydantic`로 시도해 보세요. 테스트가 통과하고 잘 동작한다면, 한 번의 명령으로 끝입니다. ✨
+
+///
+
+`bump-pydantic`가 여러분의 사용 사례에 맞지 않는다면, 같은 앱에서 Pydantic v1과 v2 모델을 모두 지원하는 기능을 이용해 Pydantic v2로 점진적으로 마이그레이션할 수 있습니다.
+
+먼저 Pydantic을 최신 v2로 업그레이드하고, 모든 모델의 import를 `pydantic.v1`을 사용하도록 바꿀 수 있습니다.
+
+그 다음 Pydantic v1에서 v2로 모델을 그룹 단위로, 점진적인 단계로 마이그레이션을 시작하면 됩니다. 🚶
diff --git a/docs/ko/docs/how-to/separate-openapi-schemas.md b/docs/ko/docs/how-to/separate-openapi-schemas.md
new file mode 100644
index 000000000..055429c26
--- /dev/null
+++ b/docs/ko/docs/how-to/separate-openapi-schemas.md
@@ -0,0 +1,102 @@
+# 입력과 출력에 대해 OpenAPI 스키마를 분리할지 여부 { #separate-openapi-schemas-for-input-and-output-or-not }
+
+**Pydantic v2**가 릴리스된 이후, 생성되는 OpenAPI는 이전보다 조금 더 정확하고 **올바르게** 만들어집니다. 😎
+
+실제로 어떤 경우에는, 같은 Pydantic 모델에 대해 OpenAPI 안에 **두 개의 JSON Schema**가 생기기도 합니다. **기본값(default value)**이 있는지 여부에 따라, 입력용과 출력용으로 나뉩니다.
+
+이것이 어떻게 동작하는지, 그리고 필요하다면 어떻게 변경할 수 있는지 살펴보겠습니다.
+
+## 입력과 출력을 위한 Pydantic 모델 { #pydantic-models-for-input-and-output }
+
+예를 들어, 다음처럼 기본값이 있는 Pydantic 모델이 있다고 해보겠습니다:
+
+{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *}
+
+### 입력용 모델 { #model-for-input }
+
+이 모델을 다음처럼 입력으로 사용하면:
+
+{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *}
+
+...`description` 필드는 **필수가 아닙니다**. `None`이라는 기본값이 있기 때문입니다.
+
+### 문서에서의 입력 모델 { #input-model-in-docs }
+
+문서에서 `description` 필드에 **빨간 별표**가 없고, 필수로 표시되지 않는 것을 확인할 수 있습니다:
+
+
+
+
+
+
+
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**가 이렇게 되었으면 합니다 - 그걸 만든 누군가를 보는 것은 많은 영감을 줍니다._"
-
-* 웹 부분을 위한 Starlette.
-* 데이터 부분을 위한 Pydantic.
+## **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`을 사용하려면 필요.
-* ujson - `UJSONResponse`를 사용하려면 필요.
+* 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`로 설치할 수 있습니다.
-`pip install fastapi[all]`를 통해 이 모두를 설치 할 수 있습니다.
+### `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`를 사용하려면 필요.
-## 라이센스
+## 라이센스 { #license }
이 프로젝트는 MIT 라이센스 조약에 따라 라이센스가 부여됩니다.
diff --git a/docs/ko/docs/learn/index.md b/docs/ko/docs/learn/index.md
index 7ac3a99b6..0b4d14ff4 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 96339aa96..000000000
--- 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 dd11fca70..73ea67d3e 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 18d4b341e..dc264df80 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()`로 각 첫 문자를 대문자로 변환시킵니다.
-* 두 단어를 중간에 공백을 두고 연결합니다.
-
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
+* `title()`로 각각의 첫 글자를 대문자로 변환합니다.
+* 가운데에 공백을 두고 연결합니다.
+{* ../../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)`과 같이 문자열로 바꾸게 됩니다:
-
-{* ../../docs_src/python_types/tutorial004.py hl[2] *}
+이제 고쳐야 한다는 것을 알고, `age`를 `str(age)`로 문자열로 바꿉니다:
+{* ../../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 | 팁
+`list`는 내부 타입을 포함하는 타입이므로, 그 타입들을 대괄호 안에 넣습니다:
+
+{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
+
+/// info | 정보
-대괄호 안의 내부 타입은 "타입 매개변수(type paramters)"라고 합니다.
+대괄호 안의 내부 타입은 "type parameters"라고 부릅니다.
-이번 예제에서는 `str`이 `List`에 들어간 타입 매개변수 입니다.
+이 경우 `str`이 `list`에 전달된 타입 매개변수입니다.
///
-이는 "`items`은 `list`인데, 배열에 들어있는 아이템 각각은 `str`이다"라는 뜻입니다.
+이는 "변수 `items`는 `list`이고, 이 `list`의 각 아이템은 `str`이다"라는 뜻입니다.
-이렇게 함으로써, 에디터는 배열에 들어있는 아이템을 처리할때도 도움을 줄 수 있게 됩니다:
+이렇게 하면, 에디터는 리스트의 아이템을 처리하는 동안에도 지원을 제공할 수 있습니다:
-타입이 없으면 이건 거의 불가능이나 다름 없습니다.
+타입이 없으면, 이는 거의 불가능합니다.
+
+변수 `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] *}
+
+이는 다음을 의미합니다:
-변수 `item`은 `items`의 개별 요소라는 사실을 알아두세요.
+* 변수 `prices`는 `dict`입니다:
+ * 이 `dict`의 키는 `str` 타입입니다(예: 각 아이템의 이름).
+ * 이 `dict`의 값은 `float` 타입입니다(예: 각 아이템의 가격).
-그리고 에디터는 계속 `str`라는 사실을 알고 도와줍니다.
+#### Union { #union }
-#### `Tuple`과 `Set`
+변수가 **여러 타입 중 어떤 것이든** 될 수 있다고 선언할 수 있습니다. 예를 들어 `int` 또는 `str`입니다.
-`tuple`과 `set`도 동일하게 선언할 수 있습니다.
+Python 3.6 이상(3.10 포함)에서는 `typing`의 `Union` 타입을 사용하고, 대괄호 안에 허용할 수 있는 타입들을 넣을 수 있습니다.
-{* ../../docs_src/python_types/tutorial007.py hl[1,4] *}
+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!}
+```
-* 변수 `items_t`는, 차례대로 `int`, `int`, `str`인 `tuple`이다.
-* 변수 `items_s`는, 각 아이템이 `bytes`인 `set`이다.
+////
-#### `Dict`
+두 경우 모두 이는 `item`이 `int` 또는 `str`일 수 있다는 뜻입니다.
-`dict`를 선언하려면 컴마로 구분된 2개의 파라미터가 필요합니다.
+#### `None`일 수도 있음 { #possibly-none }
-첫 번째 매개변수는 `dict`의 키(key)이고,
+값이 `str` 같은 타입일 수도 있지만, `None`일 수도 있다고 선언할 수 있습니다.
-두 번째 매개변수는 `dict`의 값(value)입니다.
+Python 3.6 이상(3.10 포함)에서는 `typing` 모듈에서 `Optional`을 import해서 사용하여 선언할 수 있습니다.
-{* ../../docs_src/python_types/tutorial008.py hl[1,4] *}
+```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`을 사용할 수 있다는 의미이기도 합니다:
-* 변수 `prices`는 `dict`이다:
- * `dict`의 키(key)는 `str`타입이다. (각 아이템의 이름(name))
- * `dict`의 값(value)는 `float`타입이다. (각 아이템의 가격(price))
+//// tab | Python 3.10+
-#### `Optional`
+```Python hl_lines="1"
+{!> ../../docs_src/python_types/tutorial009_py310.py!}
+```
+
+////
-`str`과 같이 타입을 선언할 때 `Optional`을 쓸 수도 있는데, "선택적(Optional)"이기때문에 `None`도 될 수 있습니다:
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
+{!> ../../docs_src/python_types/tutorial009_py39.py!}
```
-`Optional[str]`을 `str` 대신 쓰게 되면, 특정 값이 실제로는 `None`이 될 수도 있는데 항상 `str`이라고 가정하는 상황에서 에디터가 에러를 찾게 도와줄 수 있습니다.
+////
+
+//// 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]`이 의미를 더 명확하게 드러낸다고 생각합니다.
+
+이건 단지 단어와 이름의 문제입니다. 하지만 그런 단어들이 여러분과 팀원이 코드에 대해 생각하는 방식에 영향을 줄 수 있습니다.
+
+예로, 이 함수를 봅시다:
-#### Generic(제네릭) 타입
+{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
-이 타입은 대괄호 안에 매개변수를 가지며, 종류는:
+매개변수 `name`은 `Optional[str]`로 정의되어 있지만, **선택 사항이 아닙니다**. 매개변수 없이 함수를 호출할 수 없습니다:
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
+```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`
-* ...등등
+* ...그 밖의 것들.
-위와 같은 타입은 **Generic(제네릭) 타입** 혹은 **Generics(제네릭스)**라고 불립니다.
+Python 3.10에서는 제네릭 `Union`과 `Optional`을 사용하는 대안으로, 타입 유니온을 선언하기 위해 세로 막대(`|`)를 사용할 수 있는데, 훨씬 더 좋고 단순합니다.
-### 타입으로서의 클래스
+////
-변수의 타입으로 클래스를 선언할 수도 있습니다.
+//// tab | Python 3.9+
+
+대괄호와 내부 타입을 사용해, 동일한 내장 타입들을 제네릭으로 사용할 수 있습니다:
-이름(name)을 가진 `Person` 클래스가 있다고 해봅시다.
+* `list`
+* `tuple`
+* `set`
+* `dict`
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
+그리고 `typing` 모듈의 제네릭들:
+* `Union`
+* `Optional`
+* ...그 밖의 것들.
+
+////
+
+### 타입으로서의 클래스 { #classes-as-types }
+
+변수의 타입으로 클래스를 선언할 수도 있습니다.
-그렇게 하면 변수를 `Person`이라고 선언할 수 있게 됩니다.
+이름을 가진 `Person` 클래스가 있다고 해봅시다:
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
+{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
+그러면 `Person` 타입의 변수를 선언할 수 있습니다:
-그리고 역시나 모든 에디터 도움을 받게 되겠죠.
+{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
+
+그리고 다시, 에디터의 모든 지원을 받을 수 있습니다:
-## Pydantic 모델
+이는 "`one_person`은 `Person` 클래스의 **인스턴스**"라는 뜻입니다.
+
+"`one_person`은 `Person`이라는 **클래스**다"라는 뜻이 아닙니다.
-Pydantic은 데이터 검증(Validation)을 위한 파이썬 라이브러리입니다.
+## Pydantic 모델 { #pydantic-models }
-당신은 속성들을 포함한 클래스 형태로 "모양(shape)"을 선언할 수 있습니다.
+Pydantic은 데이터 검증을 수행하는 파이썬 라이브러리입니다.
-그리고 각 속성은 타입을 가지고 있습니다.
+속성을 가진 클래스 형태로 데이터의 "모양(shape)"을 선언합니다.
-이 클래스를 활용하여서 값을 가지고 있는 인스턴스를 만들게 되면, 필요한 경우에는 적당한 타입으로 변환까지 시키기도 하여 데이터가 포함된 객체를 반환합니다.
+그리고 각 속성은 타입을 가집니다.
-그리고 결과 객체에 대해서는 에디터의 도움을 받을 수 있게 됩니다.
+그 다음 그 클래스의 인스턴스를 몇 가지 값으로 생성하면, 값들을 검증하고, (그런 경우라면) 적절한 타입으로 변환한 뒤, 모든 데이터를 가진 객체를 제공합니다.
-Pydantic 공식 문서 예시:
+그리고 그 결과 객체에 대해 에디터의 모든 지원을 받을 수 있습니다.
-{* ../../docs_src/python_types/tutorial011.py *}
+Pydantic 공식 문서의 예시:
+{* ../../docs_src/python_types/tutorial011_py310.py *}
/// info | 정보
-Pydantic<에 대해 더 배우고 싶다면 공식 문서를 참고하세요.
+Pydantic에 대해 더 알아보려면 문서를 확인하세요.
///
-**FastAPI**는 모두 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할 수 있습니다.
-이 모든 것이 실제로 어떻게 사용되는지에 대해서는 [자습서 - 사용자 안내서](tutorial/index.md){.internal-link target=_blank} 에서 더 많이 확인하실 수 있습니다.
+{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
+
+파이썬 자체는 이 `Annotated`로 아무것도 하지 않습니다. 그리고 에디터와 다른 도구들에게는 타입이 여전히 `str`입니다.
+
+하지만 `Annotated`의 이 공간을 사용해, 애플리케이션이 어떻게 동작하길 원하는지에 대한 추가 메타데이터를 **FastAPI**에 제공할 수 있습니다.
+
+기억해야 할 중요한 점은 `Annotated`에 전달하는 **첫 번째 *타입 매개변수***가 **실제 타입**이라는 것입니다. 나머지는 다른 도구를 위한 메타데이터일 뿐입니다.
+
+지금은 `Annotated`가 존재하며, 표준 파이썬이라는 것만 알면 됩니다. 😎
+
+나중에 이것이 얼마나 **강력**할 수 있는지 보게 될 것입니다.
+
+/// tip | 팁
+
+이것이 **표준 파이썬**이라는 사실은, 에디터에서 가능한 **최고의 개발자 경험**을 계속 얻을 수 있다는 뜻이기도 합니다. 사용하는 도구로 코드를 분석하고 리팩터링하는 등에서도요. ✨
+
+또한 코드가 많은 다른 파이썬 도구 및 라이브러리와 매우 호환된다는 뜻이기도 합니다. 🚀
+
+///
-## **FastAPI**에서의 타입 힌트
+## **FastAPI**에서의 타입 힌트 { #type-hints-in-fastapi }
-**FastAPI**는 여러 부분에서 타입 힌트의 장점을 취하고 있습니다.
+**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 e804dd4d5..477b93a58 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 5a6c733f0..000000000
--- 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 a2c4abbd9..9e868f2fa 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` 파일에 작성됩니다.
-## 기술적 세부사항
+요청에 쿼리가 있었다면, 백그라운드 작업으로 로그에 작성됩니다.
-`BackgroundTasks` 클래스는 `starlette.background`에서 직접 가져옵니다.
+그 다음 *경로 처리 함수*에서 생성된 또 다른 백그라운드 작업이 `email` 경로 매개변수를 사용해 메시지를 작성합니다.
-`BackgroundTasks` 클래스는 FastAPI에서 직접 임포트하거나 포함하기 때문에 실수로 `BackgroundTask` (끝에 `s`가 없음)을 임포트하더라도 starlette.background에서 `BackgroundTask`를 가져오는 것을 방지할 수 있습니다.
+## 기술적 세부사항 { #technical-details }
-(`BackgroundTask`가 아닌) `BackgroundTasks`를 사용하면, _경로 작동 함수_ 매개변수로 사용할 수 있게 되고 나머지는 **FastAPI**가 대신 처리하도록 할 수 있습니다. 이것은 `Request` 객체를 직접 사용하는 것과 같은 방식입니다.
+`BackgroundTasks` 클래스는 `starlette.background`에서 직접 가져옵니다.
-FastAPI에서 `BackgroundTask`를 단독으로 사용하는 것은 여전히 가능합니다. 하지만 객체를 코드에서 생성하고, 이 객체를 포함하는 Starlette `Response`를 반환해야 합니다.
+FastAPI에 직접 임포트/포함되어 있으므로 `fastapi`에서 임포트할 수 있고, 실수로 `starlette.background`에서 대안인 `BackgroundTask`(끝에 `s`가 없음)를 임포트하는 것을 피할 수 있습니다.
-`Starlette의 공식 문서`에서 백그라운드 작업에 대한 자세한 내용을 확인할 수 있습니다.
+`BackgroundTask`가 아닌 `BackgroundTasks`만 사용하면, 이를 *경로 처리 함수*의 매개변수로 사용할 수 있고 나머지는 **FastAPI**가 `Request` 객체를 직접 사용할 때처럼 대신 처리해 줍니다.
-## 경고
+FastAPI에서 `BackgroundTask`만 단독으로 사용하는 것도 가능하지만, 코드에서 객체를 생성하고 이를 포함하는 Starlette `Response`를 반환해야 합니다.
-만약 무거운 백그라운드 작업을 수행해야하고 동일한 프로세스에서 실행할 필요가 없는 경우 (예: 메모리, 변수 등을 공유할 필요가 없음) `Celery`와 같은 큰 도구를 사용하면 도움이 될 수 있습니다.
+더 자세한 내용은 Starlette의 Background Tasks 공식 문서에서 확인할 수 있습니다.
-RabbitMQ 또는 Redis와 같은 메시지/작업 큐 시스템 보다 복잡한 구성이 필요한 경향이 있지만, 여러 작업 프로세스를 특히 여러 서버의 백그라운드에서 실행할 수 있습니다.
+## 주의사항 { #caveat }
-그러나 동일한 FastAPI 앱에서 변수 및 개체에 접근해야햐는 작은 백그라운드 수행이 필요한 경우 (예 : 알림 이메일 보내기) 간단하게 `BackgroundTasks`를 사용해보세요.
+무거운 백그라운드 계산을 수행해야 하고, 반드시 동일한 프로세스에서 실행할 필요가 없다면(예: 메모리, 변수 등을 공유할 필요가 없음) Celery 같은 더 큰 도구를 사용하는 것이 도움이 될 수 있습니다.
-## 요약
+이들은 RabbitMQ나 Redis 같은 메시지/작업 큐 관리자 등 더 복잡한 설정을 필요로 하는 경향이 있지만, 여러 프로세스에서, 특히 여러 서버에서 백그라운드 작업을 실행할 수 있습니다.
-백그라운드 작업을 추가하기 위해 _경로 작동 함수_ 에 매개변수로 `BackgroundTasks`를 가져오고 사용합니다.
+하지만 동일한 **FastAPI** 앱의 변수와 객체에 접근해야 하거나(또는 이메일 알림 전송처럼) 작은 백그라운드 작업을 수행해야 한다면, `BackgroundTasks`를 간단히 사용하면 됩니다.
+
+## 요약 { #recap }
+
+*경로 처리 함수*와 의존성에서 매개변수로 `BackgroundTasks`를 임포트해 사용하여 백그라운드 작업을 추가합니다.
diff --git a/docs/ko/docs/tutorial/bigger-applications.md b/docs/ko/docs/tutorial/bigger-applications.md
new file mode 100644
index 000000000..cfc3900d4
--- /dev/null
+++ b/docs/ko/docs/tutorial/bigger-applications.md
@@ -0,0 +1,504 @@
+# 더 큰 애플리케이션 - 여러 파일 { #bigger-applications-multiple-files }
+
+애플리케이션이나 웹 API를 만들 때, 모든 것을 하나의 파일에 담을 수 있는 경우는 드뭅니다.
+
+**FastAPI**는 모든 유연성을 유지하면서도 애플리케이션을 구조화할 수 있게 해주는 편리한 도구를 제공합니다.
+
+/// info | 정보
+
+Flask를 사용해 보셨다면, 이는 Flask의 Blueprints에 해당하는 개념입니다.
+
+///
+
+## 예시 파일 구조 { #an-example-file-structure }
+
+다음과 같은 파일 구조가 있다고 해봅시다:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+│ ├── dependencies.py
+│ └── routers
+│ │ ├── __init__.py
+│ │ ├── items.py
+│ │ └── users.py
+│ └── internal
+│ ├── __init__.py
+│ └── admin.py
+```
+
+/// tip | 팁
+
+`__init__.py` 파일이 여러 개 있습니다: 각 디렉터리 또는 하위 디렉터리에 하나씩 있습니다.
+
+이 파일들이 한 파일의 코드를 다른 파일로 import할 수 있게 해줍니다.
+
+예를 들어 `app/main.py`에는 다음과 같은 줄이 있을 수 있습니다:
+
+```
+from app.routers import items
+```
+
+///
+
+* `app` 디렉터리에는 모든 것이 들어 있습니다. 그리고 비어 있는 파일 `app/__init__.py`가 있어 "Python package"(“Python modules”의 모음)인 `app`이 됩니다.
+* `app/main.py` 파일이 있습니다. Python package(`__init__.py` 파일이 있는 디렉터리) 안에 있으므로, 이 package의 "module"입니다: `app.main`.
+* `app/dependencies.py` 파일도 있습니다. `app/main.py`와 마찬가지로 "module"입니다: `app.dependencies`.
+* `app/routers/` 하위 디렉터리가 있고, 여기에 또 `__init__.py` 파일이 있으므로 "Python subpackage"입니다: `app.routers`.
+* `app/routers/items.py` 파일은 `app/routers/` package 안에 있으므로, submodule입니다: `app.routers.items`.
+* `app/routers/users.py`도 동일하게 또 다른 submodule입니다: `app.routers.users`.
+* `app/internal/` 하위 디렉터리도 있고 여기에 `__init__.py`가 있으므로 또 다른 "Python subpackage"입니다: `app.internal`.
+* 그리고 `app/internal/admin.py` 파일은 또 다른 submodule입니다: `app.internal.admin`.
+
+
+
+## 같은 router를 다른 `prefix`로 여러 번 포함하기 { #include-the-same-router-multiple-times-with-different-prefix }
+
+`.include_router()`를 사용해 *같은* router를 서로 다른 prefix로 여러 번 포함할 수도 있습니다.
+
+예를 들어 `/api/v1`과 `/api/latest`처럼 서로 다른 prefix로 동일한 API를 노출할 때 유용할 수 있습니다.
+
+이는 고급 사용 방식이라 실제로 필요하지 않을 수도 있지만, 필요할 때를 위해 제공됩니다.
+
+## `APIRouter`에 다른 `APIRouter` 포함하기 { #include-an-apirouter-in-another }
+
+`APIRouter`를 `FastAPI` 애플리케이션에 포함할 수 있는 것과 같은 방식으로, 다음을 사용해 `APIRouter`를 다른 `APIRouter`에 포함할 수 있습니다:
+
+```Python
+router.include_router(other_router)
+```
+
+`FastAPI` 앱에 `router`를 포함하기 전에 수행해야 하며, 그래야 `other_router`의 *path operations*도 함께 포함됩니다.
diff --git a/docs/ko/docs/tutorial/body-fields.md b/docs/ko/docs/tutorial/body-fields.md
index 4708e7099..c98734ab3 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 edf892dfa..701351e63 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`를 추가할 필요가 없고, 아래처럼 할 수 있습니다:
+기본적으로 단일 값은 쿼리 매개변수로 해석되므로, 명시적으로 `Query`를 추가할 필요 없이 이렇게 하면 됩니다:
-{* ../../docs_src/body_multiple_params/tutorial004.py hl[27] *}
+```Python
+q: Union[str, None] = None
+```
-이렇게:
+또는 Python 3.10 이상에서는:
```Python
-q: Optional[str] = None
+q: str | None = None
```
-/// info | 정보
+예를 들어:
-`Body` 또한 `Query`, `Path` 그리고 이후에 볼 다른 것들처럼 동일한 추가 검증과 메타데이터 매개변수를 갖고 있습니다.
+{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *}
-///
-## 단일 본문 매개변수 삽입하기
+/// info | 정보
+
+`Body` 또한 `Query`, `Path` 그리고 이후에 볼 다른 것들과 마찬가지로 동일한 추가 검증과 메타데이터 매개변수를 모두 갖고 있습니다.
-Pydantic 모델 `Item`의 `item`을 본문 매개변수로 오직 한개만 갖고있다고 하겠습니다.
+///
-기본적으로 **FastAPI**는 직접 본문으로 예측할 것입니다.
+## 단일 본문 매개변수 삽입하기 { #embed-a-single-body-parameter }
-하지만, 만약 모델 내용에 `item `키를 가진 JSON으로 예측하길 원한다면, 추가적인 본문 매개변수를 선언한 것처럼 `Body`의 특별한 매개변수인 `embed`를 사용할 수 있습니다:
+Pydantic 모델 `Item`에서 가져온 단일 `item` 본문 매개변수만 있다고 하겠습니다.
-{* ../../docs_src/body_multiple_params/tutorial005.py hl[17] *}
+기본적으로 **FastAPI**는 그 본문을 직접 예상합니다.
-아래 처럼:
+하지만 추가 본문 매개변수를 선언할 때처럼, `item` 키를 가지고 그 안에 모델 내용이 들어 있는 JSON을 예상하게 하려면, `Body`의 특별한 매개변수 `embed`를 사용할 수 있습니다:
```Python
-item: Item = Body(..., embed=True)
+item: Item = Body(embed=True)
```
-이 경우에 **FastAPI**는 본문을 아래 대신에:
+다음과 같이요:
-```JSON hl_lines="2"
-{
- "name": "Foo",
- "description": "The pretender",
- "price": 42.0,
- "tax": 3.2
-}
-```
+{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *}
-아래 처럼 예측할 것 입니다:
-```JSON
+이 경우 **FastAPI**는 다음과 같은 본문을 예상합니다:
+
+```JSON hl_lines="2"
{
"item": {
"name": "Foo",
@@ -158,12 +153,23 @@ item: Item = Body(..., embed=True)
}
```
-## 정리
+다음 대신에:
+
+```JSON
+{
+ "name": "Foo",
+ "description": "The pretender",
+ "price": 42.0,
+ "tax": 3.2
+}
+```
+
+## 정리 { #recap }
-요청이 단 한개의 본문을 가지고 있더라도, *경로 작동 함수*로 다중 본문 매개변수를 추가할 수 있습니다.
+요청은 본문을 하나만 가질 수 있지만, *경로 처리 함수*에 다중 본문 매개변수를 추가할 수 있습니다.
-하지만, **FastAPI**는 이를 처리하고, 함수에 올바른 데이터를 제공하며, *경로 작동*으로 올바른 스키마를 검증하고 문서화 합니다.
+하지만 **FastAPI**는 이를 처리하고, 함수에 올바른 데이터를 제공하며, *경로 처리*에서 올바른 스키마를 검증하고 문서화합니다.
-또한, 단일 값을 본문의 일부로 받도록 선언할 수 있습니다.
+또한 단일 값을 본문의 일부로 받도록 선언할 수 있습니다.
-그리고 **FastAPI**는 단 한개의 매개변수가 선언 되더라도, 본문 내의 키로 삽입 시킬 수 있습니다.
+그리고 단 하나의 매개변수만 선언되어 있더라도, **FastAPI**에 본문을 키 안에 삽입하도록 지시할 수 있습니다.
diff --git a/docs/ko/docs/tutorial/body-nested-models.md b/docs/ko/docs/tutorial/body-nested-models.md
index ebd7b3ba6..4a8c1afc1 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`는:
-
-{* ../../docs_src/body_nested_models/tutorial001.py hl[14] *}
-
-이는 `tags`를 항목 리스트로 만듭니다. 각 항목의 타입을 선언하지 않더라도요.
+## 리스트 필드 { #list-fields }
-## 타입 매개변수가 있는 리스트 필드
-
-하지만 파이썬은 내부의 타입이나 "타입 매개변수"를 선언할 수 있는 특정 방법이 있습니다:
+어트리뷰트를 서브타입으로 정의할 수 있습니다. 예를 들어 파이썬 `list`는:
-### typing의 `List` 임포트
+{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *}
-먼저, 파이썬 표준 `typing` 모듈에서 `List`를 임포트합니다:
+이는 `tags`를 리스트로 만들지만, 리스트 요소의 타입을 선언하지는 않습니다.
-{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
+## 타입 매개변수가 있는 리스트 필드 { #list-fields-with-type-parameter }
-### 타입 매개변수로 `List` 선언
+하지만 파이썬에는 내부 타입, 즉 "타입 매개변수"를 사용해 리스트를 선언하는 특정한 방법이 있습니다:
-`list`, `dict`, `tuple`과 같은 타입 매개변수(내부 타입)를 갖는 타입을 선언하려면:
+### 타입 매개변수로 `list` 선언 { #declare-a-list-with-a-type-parameter }
-* `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-updates.md b/docs/ko/docs/tutorial/body-updates.md
new file mode 100644
index 000000000..3719e1ffa
--- /dev/null
+++ b/docs/ko/docs/tutorial/body-updates.md
@@ -0,0 +1,100 @@
+# Body - 업데이트 { #body-updates }
+
+## `PUT`으로 교체 업데이트하기 { #update-replacing-with-put }
+
+항목을 업데이트하려면 HTTP `PUT` 작업을 사용할 수 있습니다.
+
+`jsonable_encoder`를 사용해 입력 데이터를 JSON으로 저장할 수 있는 데이터로 변환할 수 있습니다(예: NoSQL 데이터베이스 사용 시). 예를 들어 `datetime`을 `str`로 변환하는 경우입니다.
+
+{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *}
+
+`PUT`은 기존 데이터를 **대체**해야 하는 데이터를 받는 데 사용합니다.
+
+### 대체 시 주의사항 { #warning-about-replacing }
+
+즉, `PUT`으로 항목 `bar`를 업데이트하면서 다음과 같은 body를 보낸다면:
+
+```Python
+{
+ "name": "Barz",
+ "price": 3,
+ "description": None,
+}
+```
+
+이미 저장된 속성 `"tax": 20.2`가 포함되어 있지 않기 때문에, 입력 모델은 `"tax": 10.5`라는 기본값을 사용하게 됩니다.
+
+그리고 데이터는 그 “새로운” `tax` 값 `10.5`로 저장됩니다.
+
+## `PATCH`로 부분 업데이트하기 { #partial-updates-with-patch }
+
+HTTP `PATCH` 작업을 사용해 데이터를 *부분적으로* 업데이트할 수도 있습니다.
+
+이는 업데이트하려는 데이터만 보내고, 나머지는 그대로 두는 것을 의미합니다.
+
+/// note | 참고
+
+`PATCH`는 `PUT`보다 덜 일반적으로 사용되고 덜 알려져 있습니다.
+
+그리고 많은 팀이 부분 업데이트에도 `PUT`만 사용합니다.
+
+여러분은 원하는 방식으로 **자유롭게** 사용할 수 있으며, **FastAPI**는 어떤 제한도 강제하지 않습니다.
+
+다만 이 가이드는 의도된 사용 방식이 대략 어떻게 되는지를 보여줍니다.
+
+///
+
+### Pydantic의 `exclude_unset` 파라미터 사용하기 { #using-pydantics-exclude-unset-parameter }
+
+부분 업데이트를 받으려면 Pydantic 모델의 `.model_dump()`에서 `exclude_unset` 파라미터를 사용하는 것이 매우 유용합니다.
+
+예: `item.model_dump(exclude_unset=True)`.
+
+이는 `item` 모델을 만들 때 실제로 설정된 데이터만 포함하는 `dict`를 생성하고, 기본값은 제외합니다.
+
+그 다음 이를 사용해 (요청에서 전송되어) 설정된 데이터만 포함하고 기본값은 생략한 `dict`를 만들 수 있습니다:
+
+{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *}
+
+### Pydantic의 `update` 파라미터 사용하기 { #using-pydantics-update-parameter }
+
+이제 `.model_copy()`를 사용해 기존 모델의 복사본을 만들고, 업데이트할 데이터가 들어있는 `dict`를 `update` 파라미터로 전달할 수 있습니다.
+
+예: `stored_item_model.model_copy(update=update_data)`:
+
+{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
+
+### 부분 업데이트 요약 { #partial-updates-recap }
+
+정리하면, 부분 업데이트를 적용하려면 다음을 수행합니다:
+
+* (선택 사항) `PUT` 대신 `PATCH`를 사용합니다.
+* 저장된 데이터를 조회합니다.
+* 그 데이터를 Pydantic 모델에 넣습니다.
+* 입력 모델에서 기본값이 제외된 `dict`를 생성합니다(`exclude_unset` 사용).
+ * 이렇게 하면 모델의 기본값으로 이미 저장된 값을 덮어쓰지 않고, 사용자가 실제로 설정한 값만 업데이트할 수 있습니다.
+* 저장된 모델의 복사본을 만들고, 받은 부분 업데이트로 해당 속성들을 갱신합니다(`update` 파라미터 사용).
+* 복사한 모델을 DB에 저장할 수 있는 형태로 변환합니다(예: `jsonable_encoder` 사용).
+ * 이는 모델의 `.model_dump()` 메서드를 다시 사용하는 것과 비슷하지만, JSON으로 변환 가능한 데이터 타입으로 값이 확실히 변환되도록 보장합니다(예: `datetime` → `str`).
+* 데이터를 DB에 저장합니다.
+* 업데이트된 모델을 반환합니다.
+
+{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *}
+
+/// tip | 팁
+
+동일한 기법을 HTTP `PUT` 작업에서도 실제로 사용할 수 있습니다.
+
+하지만 여기의 예시는 이런 사용 사례를 위해 만들어진 `PATCH`를 사용합니다.
+
+///
+
+/// note | 참고
+
+입력 모델은 여전히 검증된다는 점에 유의하세요.
+
+따라서 모든 속성을 생략할 수 있는 부분 업데이트를 받으려면, 모든 속성이 optional로 표시된(기본값을 가지거나 `None`을 기본값으로 가지는) 모델이 필요합니다.
+
+**업데이트**를 위한 “모든 값이 optional인” 모델과, **생성**을 위한 “필수 값이 있는” 모델을 구분하려면 [추가 모델](extra-models.md){.internal-link target=_blank}에 설명된 아이디어를 사용할 수 있습니다.
+
+///
diff --git a/docs/ko/docs/tutorial/body.md b/docs/ko/docs/tutorial/body.md
index b3914fa4b..1e66c60c2 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 e7eef0b1d..00238d1b7 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 fba756d49..0591a5e96 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 | 정보
+
+**브라우저는 쿠키를** 내부적으로 특별한 방식으로 처리하기 때문에, **JavaScript**가 쉽게 쿠키를 다루도록 허용하지 않는다는 점을 염두에 두세요.
+
+`/docs`의 **API docs UI**로 이동하면 *경로 처리*에 대한 쿠키 **문서**를 확인할 수 있습니다.
+
+하지만 **데이터를 채우고** "Execute"를 클릭하더라도, docs UI는 **JavaScript**로 동작하기 때문에 쿠키가 전송되지 않고, 아무 값도 입력하지 않은 것처럼 **오류** 메시지를 보게 될 것입니다.
+
+///
+
+## 요약 { #recap }
-`Cookie`는 `Query`, `Path`와 동일한 패턴을 사용하여 선언합니다.
+`Query`와 `Path`에서 사용하는 것과 동일한 공통 패턴으로, `Cookie`를 사용해 쿠키를 선언합니다.
diff --git a/docs/ko/docs/tutorial/cors.md b/docs/ko/docs/tutorial/cors.md
index 1ef5a7480..0f3948a3d 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 e42f1ba88..ca20acff6 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 3e5cdcc8c..68bba669a 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] *}
+{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *}
-...이렇게 코드를 단축하여도 **FastAPI**는 무엇을 해야하는지 알고 있습니다.
+...그리고 **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 4a3854cef..39c78c078 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 ff174937d..9bf6c0693 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는
contact 필드| 매개변수 | 타입 | 설명 |
|---|---|---|
name | str | 연락처 인물/조직의 식별명입니다. |
url | str | 연락처 정보가 담긴 URL입니다. URL 형식이어야 합니다. |
email | str | 연락처 인물/조직의 이메일 주소입니다. 이메일 주소 형식이어야 합니다. |
license_info 필드| 매개변수 | 타입 | 설명 |
|---|---|---|
name | str | 필수 (license_info가 설정된 경우). API에 사용된 라이선스 이름입니다. |
identifier | str | API에 대한 SPDX 라이선스 표현입니다. identifier 필드는 url 필드와 상호 배타적입니다. OpenAPI 3.1.0, FastAPI 0.99.0부터 사용 가능 |
url | str | API에 사용된 라이선스의 URL입니다. URL 형식이어야 합니다. |
license_info 필드| 매개변수 | 타입 | 설명 |
|---|---|---|
name | str | 필수 (license_info가 설정된 경우). API에 사용된 라이선스 이름입니다. |
identifier | str | API에 대한 SPDX 라이선스 표현입니다. identifier 필드는 url 필드와 상호 배타적입니다. OpenAPI 3.1.0, FastAPI 0.99.0부터 사용 가능. |
url | str | API에 사용된 라이선스의 URL입니다. URL 형식이어야 합니다. |
-## 라이선스 식별자
+## 라이선스 식별자 { #license-identifier }
-OpenAPI 3.1.0 및 FastAPI 0.99.0부터 `license_info`에 `identifier`를 URL 대신 설정할 수 있습니다.
+OpenAPI 3.1.0 및 FastAPI 0.99.0부터 `license_info`에 `url` 대신 `identifier`를 설정할 수도 있습니다.
예:
-{* ../../docs_src/metadata/tutorial001_1.py hl[31] *}
+{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *}
-## 태그에 대한 메타데이터
+## 태그에 대한 메타데이터 { #metadata-for-tags }
-`openapi_tags` 매개변수를 사용하여 경로 작동을 그룹화하는 데 사용되는 태그에 추가 메타데이터를 추가할 수 있습니다.
+`openapi_tags` 매개변수를 사용하여 경로 처리을 그룹화하는 데 사용되는 여러 태그에 추가 메타데이터를 추가할 수도 있습니다.
-리스트는 각 태그에 대해 하나의 딕셔너리를 포함해야 합니다.
+리스트는 각 태그에 대해 하나의 딕셔너리를 포함합니다.
각 딕셔너리에는 다음이 포함될 수 있습니다:
-* `name` (**필수**): `tags` 매개변수에서 *경로 작동*과 `APIRouter`에 사용된 태그 이름과 동일한 `str`입니다.
-* `description`: 태그에 대한 간단한 설명을 담은 `str`입니다. 마크다운을 사용할 수 있으며 문서 UI에 표시됩니다.
+* `name` (**필수**): *경로 처리* 및 `APIRouter`의 `tags` 매개변수에서 사용하는 태그 이름과 동일한 `str`입니다.
+* `description`: 태그에 대한 간단한 설명을 담은 `str`입니다. 마크다운을 포함할 수 있으며 문서 UI에 표시됩니다.
* `externalDocs`: 외부 문서를 설명하는 `dict`이며:
* `description`: 외부 문서에 대한 간단한 설명을 담은 `str`입니다.
* `url` (**필수**): 외부 문서의 URL을 담은 `str`입니다.
-### 태그에 대한 메타데이터 생성
+### 태그에 대한 메타데이터 생성 { #create-metadata-for-tags }
-`users` 및 `items`에 대한 태그 예시와 함께 메타데이터를 생성하고 이를 `openapi_tags` 매개변수로 전달해 보겠습니다:
+`users` 및 `items`에 대한 태그 예시로 시도해 보겠습니다.
-{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
+태그에 대한 메타데이터를 생성하고 이를 `openapi_tags` 매개변수로 전달하세요:
-설명 안에 마크다운을 사용할 수 있습니다. 예를 들어 "login"은 굵게(**login**) 표시되고, "fancy"는 기울임꼴(_fancy_)로 표시됩니다.
+{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *}
-/// tip
+설명 안에 마크다운을 사용할 수 있다는 점에 유의하세요. 예를 들어 "login"은 굵게(**login**) 표시되고, "fancy"는 기울임꼴(_fancy_)로 표시됩니다.
+
+/// tip | 팁
사용 중인 모든 태그에 메타데이터를 추가할 필요는 없습니다.
///
-### 태그 사용
+### 태그 사용 { #use-your-tags }
-`tags` 매개변수를 *경로 작동* 및 `APIRouter`와 함께 사용하여 태그에 할당할 수 있습니다:
+`tags` 매개변수를 *경로 처리* (및 `APIRouter`)와 함께 사용하여 이를 서로 다른 태그에 할당하세요:
-{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
+{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *}
-/// info
+/// 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 }
-각 태그 메타데이터 딕셔너리의 순서는 문서 UI에 표시되는 순서를 정의합니다.
+각 태그 메타데이터 딕셔너리의 순서는 문서 UI에 표시되는 순서도 정의합니다.
-예를 들어, 알파벳 순서상 `users`는 `items` 뒤에 오지만, 우리는 `users` 메타데이터를 리스트의 첫 번째 딕셔너리로 추가했기 때문에 먼저 표시됩니다.
+예를 들어, 알파벳 순서상 `users`는 `items` 뒤에 오지만, 우리는 해당 메타데이터를 리스트의 첫 번째 딕셔너리로 추가했기 때문에 먼저 표시됩니다.
-## OpenAPI URL
+## OpenAPI URL { #openapi-url }
-OpenAPI 구조는 기본적으로 `/openapi.json`에서 제공됩니다.
+기본적으로 OpenAPI 스키마는 `/openapi.json`에서 제공됩니다.
-`openapi_url` 매개변수를 통해 이를 설정할 수 있습니다.
+하지만 `openapi_url` 매개변수로 이를 설정할 수 있습니다.
-예를 들어, 이를 `/api/v1/openapi.json`에 제공하도록 설정하려면:
+예를 들어, 이를 `/api/v1/openapi.json`에서 제공하도록 설정하려면:
-{* ../../docs_src/metadata/tutorial002.py hl[3] *}
+{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *}
-OpenAPI 구조를 완전히 비활성화하려면 `openapi_url=None`으로 설정할 수 있으며, 이를 사용하여 문서화 사용자 인터페이스도 비활성화됩니다.
+OpenAPI 스키마를 완전히 비활성화하려면 `openapi_url=None`으로 설정할 수 있으며, 이를 사용하여 문서화 사용자 인터페이스도 비활성화됩니다.
-## 문서화 URL
+## 문서화 URL { #docs-urls }
포함된 두 가지 문서화 사용자 인터페이스를 설정할 수 있습니다:
@@ -115,4 +117,4 @@ OpenAPI 구조를 완전히 비활성화하려면 `openapi_url=None`으로 설
예를 들어, Swagger UI를 `/documentation`에서 제공하고 ReDoc을 비활성화하려면:
-{* ../../docs_src/metadata/tutorial003.py hl[3] *}
+{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *}
diff --git a/docs/ko/docs/tutorial/middleware.md b/docs/ko/docs/tutorial/middleware.md
index 3cd752a0e..c213c5074 100644
--- a/docs/ko/docs/tutorial/middleware.md
+++ b/docs/ko/docs/tutorial/middleware.md
@@ -1,66 +1,95 @@
-# 미들웨어
+# 미들웨어 { #middleware }
미들웨어를 **FastAPI** 응용 프로그램에 추가할 수 있습니다.
-"미들웨어"는 특정 *경로 작동*에 의해 처리되기 전, 모든 **요청**에 대해서 동작하는 함수입니다. 또한 모든 **응답**이 반환되기 전에도 동일하게 동작합니다.
+"미들웨어"는 특정 *경로 처리*에 의해 처리되기 전, 모든 **요청**에 대해서 동작하는 함수입니다. 또한 모든 **응답**이 반환되기 전에도 동일하게 동작합니다.
-* 미들웨어는 응용 프로그램으로 오는 **요청**를 가져옵니다.
-* **요청** 또는 다른 필요한 코드를 실행 시킬 수 있습니다.
-* **요청**을 응용 프로그램의 *경로 작동*으로 전달하여 처리합니다.
-* 애플리케이션의 *경로 작업*에서 생성한 **응답**를 받습니다.
-* **응답** 또는 다른 필요한 코드를 실행시키는 동작을 할 수 있습니다.
-* **응답**를 반환합니다.
+* 미들웨어는 응용 프로그램으로 오는 각 **요청**을 가져옵니다.
+* 그런 다음 해당 **요청**에 대해 무언가를 하거나 필요한 코드를 실행할 수 있습니다.
+* 그런 다음 **요청**을 나머지 애플리케이션(어떤 *경로 처리*가)을 통해 처리되도록 전달합니다.
+* 그런 다음 애플리케이션(어떤 *경로 처리*가)이 생성한 **응답**을 가져옵니다.
+* 그런 다음 해당 **응답**에 대해 무언가를 하거나 필요한 코드를 실행할 수 있습니다.
+* 그런 다음 **응답**을 반환합니다.
/// note | 기술 세부사항
-만약 `yield`를 사용한 의존성을 가지고 있다면, 미들웨어가 실행되고 난 후에 exit이 실행됩니다.
+`yield`를 사용하는 의존성이 있다면, exit 코드는 미들웨어 *후에* 실행됩니다.
-만약 (나중에 문서에서 다룰) 백그라운드 작업이 있다면, 모든 미들웨어가 실행되고 *난 후에* 실행됩니다.
+백그라운드 작업(뒤에서 보게 될 [Background Tasks](background-tasks.md){.internal-link target=_blank} 섹션에서 다룹니다)이 있다면, 모든 미들웨어 *후에* 실행됩니다.
///
-## 미들웨어 만들기
+## 미들웨어 만들기 { #create-a-middleware }
-미들웨어를 작성하기 위해서 함수 상단에 `@app.middleware("http")` 데코레이터를 사용할 수 있습니다.
+미들웨어를 만들기 위해 함수 상단에 데코레이터 `@app.middleware("http")`를 사용합니다.
-미들웨어 함수는 다음 항목들을 받습니다:
+미들웨어 함수는 다음을 받습니다:
* `request`.
* `request`를 매개변수로 받는 `call_next` 함수.
- * 이 함수는 `request`를 해당하는 *경로 작업*으로 전달합니다.
- * 그런 다음, *경로 작업*에 의해 생성된 `response` 를 반환합니다.
-* `response`를 반환하기 전에 추가로 `response`를 수정할 수 있습니다.
+ * 이 함수는 `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 (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`를 사용할 수도 있습니다.
+
+**FastAPI**는 개발자인 여러분의 편의를 위해 이를 제공합니다. 하지만 이는 Starlette에서 직접 가져온 것입니다.
+
+///
+
+### `response`의 전과 후 { #before-and-after-the-response }
+
+어떤 *경로 처리*가 받기 전에, `request`와 함께 실행될 코드를 추가할 수 있습니다.
+
+또한 `response`가 생성된 후, 반환하기 전에 코드를 추가할 수도 있습니다.
-`from starlette.requests import request`를 사용할 수도 있습니다.
+예를 들어, 요청을 처리하고 응답을 생성하는 데 걸린 시간을 초 단위로 담는 사용자 정의 헤더 `X-Process-Time`을 추가할 수 있습니다:
-**FastAPI**는 개발자에게 편의를 위해 이를 제공합니다. 그러나 Starlette에서 직접 파생되었습니다.
+{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *}
+
+/// tip | 팁
+
+여기서는 이러한 사용 사례에서 더 정확할 수 있기 때문에 `time.time()` 대신 `time.perf_counter()`를 사용합니다. 🤓
///
-### `response`의 전과 후
+## 여러 미들웨어 실행 순서 { #multiple-middleware-execution-order }
+
+`@app.middleware()` 데코레이터 또는 `app.add_middleware()` 메서드를 사용해 여러 미들웨어를 추가하면, 새로 추가된 각 미들웨어가 애플리케이션을 감싸 스택을 형성합니다. 마지막에 추가된 미들웨어가 *가장 바깥쪽*이고, 처음에 추가된 미들웨어가 *가장 안쪽*입니다.
+
+요청 경로에서는 *가장 바깥쪽* 미들웨어가 먼저 실행됩니다.
+
+응답 경로에서는 마지막에 실행됩니다.
+
+예를 들어:
+
+```Python
+app.add_middleware(MiddlewareA)
+app.add_middleware(MiddlewareB)
+```
-*경로 작동*을 받기 전 `request`와 함께 작동할 수 있는 코드를 추가할 수 있습니다.
+이 경우 실행 순서는 다음과 같습니다:
-그리고 `response` 또한 생성된 후 반환되기 전에 코드를 추가 할 수 있습니다.
+* **요청**: MiddlewareB → MiddlewareA → route
-예를 들어, 요청을 수행하고 응답을 생성하는데 까지 걸린 시간 값을 가지고 있는 `X-Process-Time` 같은 사용자 정의 헤더를 추가할 수 있습니다.
+* **응답**: route → MiddlewareA → MiddlewareB
-{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
+이러한 스태킹 동작은 미들웨어가 예측 가능하고 제어 가능한 순서로 실행되도록 보장합니다.
-## 다른 미들웨어
+## 다른 미들웨어 { #other-middlewares }
-미들웨어에 대한 더 많은 정보는 [숙련된 사용자 안내서: 향상된 미들웨어](../advanced/middleware.md){.internal-link target=\_blank}에서 확인할 수 있습니다.
+다른 미들웨어에 대한 더 많은 정보는 나중에 [숙련된 사용자 안내서: 향상된 미들웨어](../advanced/middleware.md){.internal-link target=_blank}에서 확인할 수 있습니다.
-다음 부분에서 미들웨어와 함께 CORS를 어떻게 다루는지에 대해 확인할 것입니다.
+다음 섹션에서 미들웨어로 CORS를 처리하는 방법을 보게 될 것입니다.
diff --git a/docs/ko/docs/tutorial/path-operation-configuration.md b/docs/ko/docs/tutorial/path-operation-configuration.md
index 81914182a..b8a87667a 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 f21c9290e..f2c52d4aa 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 | 정보
+
+FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이를 권장하기 시작했습니다).
+
+더 오래된 버전이 있다면 `Annotated`를 사용하려고 할 때 오류가 발생합니다.
+
+`Annotated`를 사용하기 전에 최소 0.95.1까지 [FastAPI 버전 업그레이드](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}를 꼭 하세요.
+
+///
+
+## 메타데이터 선언 { #declare-metadata }
`Query`에 동일한 매개변수를 선언할 수 있습니다.
-예를 들어, `title` 메타데이터 값을 경로 매개변수 `item_id`에 선언하려면 다음과 같이 입력할 수 있습니다:
+예를 들어, 경로 매개변수 `item_id`에 `title` 메타데이터 값을 선언하려면 다음과 같이 입력할 수 있습니다:
-{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[10] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *}
/// note | 참고
-경로 매개변수는 경로의 일부여야 하므로 언제나 필수적입니다.
+경로 매개변수는 경로의 일부여야 하므로 언제나 필수입니다. `None`으로 선언하거나 기본값을 지정하더라도 아무 영향이 없으며, 항상 필수입니다.
-즉, `...`로 선언해서 필수임을 나타내는게 좋습니다.
+///
-그럼에도 `None`으로 선언하거나 기본값을 지정할지라도 아무 영향을 끼치지 않으며 언제나 필수입니다.
+## 필요한 대로 매개변수 정렬하기 { #order-the-parameters-as-you-need }
-///
+/// tip | 팁
-## 필요한 경우 매개변수 정렬하기
+`Annotated`를 사용한다면 이것은 아마 그렇게 중요하지 않거나 필요하지 않을 수 있습니다.
+
+///
`str` 형인 쿼리 매개변수 `q`를 필수로 선언하고 싶다고 해봅시다.
-해당 매개변수에 대해 아무런 선언을 할 필요가 없으므로 `Query`를 정말로 써야할 필요는 없습니다.
+해당 매개변수에 대해 아무런 선언을 할 필요가 없으므로 `Query`를 정말로 써야 할 필요는 없습니다.
+
+하지만 `item_id` 경로 매개변수는 여전히 `Path`를 사용해야 합니다. 그리고 어떤 이유로 `Annotated`를 사용하고 싶지 않다고 해봅시다.
-하지만 `item_id` 경로 매개변수는 여전히 `Path`를 사용해야 합니다.
+파이썬은 "기본값"이 있는 값을 "기본값"이 없는 값 앞에 두면 불평합니다.
+
+하지만 순서를 재정렬해서 기본값이 없는 값(쿼리 매개변수 `q`)을 앞에 둘 수 있습니다.
+
+**FastAPI**에서는 중요하지 않습니다. 이름, 타입 그리고 기본값 선언(`Query`, `Path` 등)로 매개변수를 감지하며 순서는 신경 쓰지 않습니다.
+
+따라서 함수를 다음과 같이 선언할 수 있습니다:
+
+{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *}
+
+하지만 `Annotated`를 사용하면 이 문제가 없다는 점을 기억하세요. `Query()`나 `Path()`에 함수 매개변수 기본값을 사용하지 않기 때문에, 순서는 중요하지 않습니다.
+
+{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
+
+## 필요한 대로 매개변수 정렬하기, 트릭 { #order-the-parameters-as-you-need-tricks }
+
+/// tip | 팁
+
+`Annotated`를 사용한다면 이것은 아마 그렇게 중요하지 않거나 필요하지 않을 수 있습니다.
+
+///
-파이썬은 "기본값"이 없는 값 앞에 "기본값"이 있는 값을 입력하면 불평합니다.
+유용할 수 있는 **작은 트릭**이 하나 있지만, 자주 필요하진 않을 겁니다.
-그러나 매개변수들을 재정렬함으로써 기본값(쿼리 매개변수 `q`)이 없는 값을 처음 부분에 위치 할 수 있습니다.
+만약 다음을 원한다면:
-**FastAPI**에서는 중요하지 않습니다. 이름, 타입 그리고 선언구(`Query`, `Path` 등)로 매개변수를 감지하며 순서는 신경 쓰지 않습니다.
+* `Query`나 어떤 기본값 없이 쿼리 매개변수 `q`를 선언하기
+* `Path`를 사용해서 경로 매개변수 `item_id`를 선언하기
+* 이들을 다른 순서로 두기
+* `Annotated`를 사용하지 않기
-따라서 함수를 다음과 같이 선언 할 수 있습니다:
+...이를 위해 파이썬에는 작은 특별한 문법이 있습니다.
-{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
+함수의 첫 번째 매개변수로 `*`를 전달하세요.
-## 필요한 경우 매개변수 정렬하기, 트릭
+파이썬은 `*`으로 아무것도 하지 않지만, 뒤따르는 모든 매개변수는 키워드 인자(키-값 쌍)로 호출되어야 함을 알게 됩니다. 이는 kwargs로도 알려져 있습니다. 기본값이 없더라도 마찬가지입니다.
-`Query`나 아무런 기본값으로도 `q` 경로 매개변수를 선언하고 싶지 않지만 `Path`를 사용하여 경로 매개변수를 `item_id` 다른 순서로 선언하고 싶다면, 파이썬은 이를 위한 작고 특별한 문법이 있습니다.
+{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
-`*`를 함수의 첫 번째 매개변수로 전달하세요.
+### `Annotated`를 쓰면 더 좋습니다 { #better-with-annotated }
-파이썬은 `*`으로 아무런 행동도 하지 않지만, 따르는 매개변수들은 kwargs로도 알려진 키워드 인자(키-값 쌍)여야 함을 인지합니다. 기본값을 가지고 있지 않더라도 그렇습니다.
+`Annotated`를 사용하면 함수 매개변수 기본값을 사용하지 않기 때문에 이 문제가 발생하지 않으며, 아마 `*`도 사용할 필요가 없다는 점을 기억하세요.
-{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
+{* ../../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`는 `1`보다 "크거나(`g`reater) 같은(`e`qual)" 정수형 숫자여야 합니다.
+여기서 `ge=1`인 경우, `item_id`는 `1`보다 "`g`reater than or `e`qual"(크거나 같은) 정수형 숫자여야 합니다.
-{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *}
+{* ../../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 b72787e0b..ea5170ecc 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,73 +108,73 @@
이 중 몇 가지는 자습서의 다음 장에 설명되어 있습니다.
-## 순서 문제
+## 순서 문제 { #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` 클래스 생성
+경로가 먼저 매칭되기 때문에 첫 번째 것이 항상 사용됩니다.
-`Enum`을 임포트하고 `str`과 `Enum`을 상속하는 서브 클래스를 만듭니다.
+## 사전정의 값 { #predefined-values }
-`str`을 상속함으로써 API 문서는 값이 `string` 형이어야 하는 것을 알게 되고 이는 문서에 제대로 표시됩니다.
+만약 *경로 매개변수*를 받는 *경로 처리*가 있지만, 가능한 유효한 *경로 매개변수* 값들을 미리 정의하고 싶다면 파이썬 표준 `Enum`을 사용할 수 있습니다.
-가능한 값들에 해당하는 고정된 값의 클래스 어트리뷰트들을 만듭니다:
+### `Enum` 클래스 생성 { #create-an-enum-class }
-{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
+`Enum`을 임포트하고 `str`과 `Enum`을 상속하는 서브 클래스를 만듭니다.
-/// info | 정보
+`str`을 상속함으로써 API 문서는 값이 `string` 형이어야 하는 것을 알게 되고 이는 문서에 제대로 표시됩니다.
-열거형(또는 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 2ca65a331..d354c56c3 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 d5b9837c4..5124f73bf 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 9162b353c..cc0000921 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,77 +85,79 @@ 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] *}
-이 기능을 사용하기 위해 , `bytes` 의 `List` 또는 `UploadFile` 를 선언하기 바랍니다:
+## 추가 메타데이터를 포함한 `UploadFile` { #uploadfile-with-additional-metadata }
-{* ../../docs_src/request_files/tutorial002.py hl[10,15] *}
+추가 메타데이터를 설정하기 위해 예를 들어 `UploadFile`과 함께 `File()`을 사용할 수도 있습니다:
-선언한대로, `bytes` 의 `list` 또는 `UploadFile` 들을 전송받을 것입니다.
+{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
-/// note | 참고
+## 다중 파일 업로드 { #multiple-file-uploads }
-2019년 4월 14일부터 Swagger UI가 하나의 폼 필드로 다수의 파일을 업로드하는 것을 지원하지 않습니다. 더 많은 정보를 원하면, #4276과 #3641을 참고하세요.
+여러 파일을 동시에 업로드 할 수 있습니다.
-그럼에도, **FastAPI**는 표준 Open API를 사용해 이미 호환이 가능합니다.
+그들은 "폼 데이터"를 사용하여 전송된 동일한 "폼 필드"에 연결됩니다.
-따라서 Swagger UI 또는 기타 그 외의 OpenAPI를 지원하는 툴이 다중 파일 업로드를 지원하는 경우, 이들은 **FastAPI**와 호환됩니다.
+이 기능을 사용하기 위해 , `bytes` 의 `List` 또는 `UploadFile` 를 선언하기 바랍니다:
-///
+{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *}
+
+선언한대로, `bytes` 의 `list` 또는 `UploadFile` 들을 전송받을 것입니다.
-/// note | 기술적 세부사항
+/// note | 기술 세부사항
`from starlette.responses import HTMLResponse` 역시 사용할 수 있습니다.
@@ -158,6 +165,12 @@ HTML의 폼들(``)이 서버에 데이터를 전송하는 방식은
///
-## 요약
+### 추가 메타데이터를 포함한 다중 파일 업로드 { #multiple-file-uploads-with-additional-metadata }
+
+이전과 같은 방식으로 `UploadFile`에 대해서도 `File()`을 사용해 추가 매개변수를 설정할 수 있습니다:
+
+{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
+
+## 요약 { #recap }
-폼 데이터로써 입력 매개변수로 업로드할 파일을 선언할 경우 `File` 을 사용하기 바랍니다.
+`File`, `bytes`, `UploadFile`을 사용하여 폼 데이터로 전송되는 요청에서 업로드할 파일을 선언하세요.
diff --git a/docs/ko/docs/tutorial/request-form-models.md b/docs/ko/docs/tutorial/request-form-models.md
index 3316a93d5..b37186dfb 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 a71d649f9..6246ed9ad 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`로 선언할 수 있습니다.
-* 해당 모델의 출력 데이터 제한. 이것이 얼마나 중요한지 아래에서 볼 것입니다.
+이렇게 하면 에디터에 의도적으로 어떤 값이든 반환한다고 알려줍니다. 하지만 FastAPI는 여전히 `response_model`을 사용하여 데이터 문서화, 검증, 필터링 등을 수행합니다.
-/// note | 기술 세부사항
+///
-응답 모델은 함수의 타입 어노테이션 대신 이 매개변수로 선언하는데, 경로 함수가 실제 응답 모델을 반환하지 않고 `dict`, 데이터베이스 객체나 기타 다른 모델을 `response_model`을 사용하여 필드 제한과 직렬화를 수행하고 반환할 수 있기 때문입니다
+### `response_model` 우선순위 { #response-model-priority }
-///
+반환 타입과 `response_model`을 둘 다 선언하면, `response_model`이 우선순위를 가지며 FastAPI에서 사용됩니다.
+
+이렇게 하면 응답 모델과 다른 타입을 반환하는 경우에도 에디터와 mypy 같은 도구에서 사용할 올바른 타입 어노테이션을 함수에 추가할 수 있습니다. 그리고 동시에 FastAPI가 `response_model`을 사용하여 데이터 검증, 문서화 등을 수행하게 할 수도 있습니다.
+
+또한 `response_model=None`을 사용해 해당 *경로 처리*에 대한 응답 모델 생성을 비활성화할 수도 있습니다. 이는 유효한 Pydantic 필드가 아닌 것들에 대해 타입 어노테이션을 추가하는 경우에 필요할 수 있으며, 아래 섹션 중 하나에서 예시를 볼 수 있습니다.
+
+## 동일한 입력 데이터 반환 { #return-the-same-input-data }
-## 동일한 입력 데이터 반환
+여기서는 평문 비밀번호를 포함하는 `UserIn` 모델을 선언합니다:
-여기서 우리는 평문 비밀번호를 포함하는 `UserIn` 모델을 선언합니다:
+{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *}
-{* ../../docs_src/response_model/tutorial002.py hl[9,11] *}
+/// 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_py310.py hl[9,11,16] *}
+
+여기서 *경로 처리 함수*가 비밀번호를 포함하는 동일한 입력 사용자를 반환하더라도:
+
+{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *}
+
+...비밀번호를 포함하지 않는 `UserOut` 모델로 `response_model`을 선언했습니다:
+
+{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *}
+
+따라서 **FastAPI**는 출력 모델에 선언되지 않은 모든 데이터를 (Pydantic을 사용하여) 필터링합니다.
+
+### `response_model` 또는 반환 타입 { #response-model-or-return-type }
+
+이 경우 두 모델이 서로 다르기 때문에, 함수 반환 타입을 `UserOut`으로 어노테이션하면 에디터와 도구는 서로 다른 클래스인데 잘못된 타입을 반환하고 있다고 불평할 것입니다.
+
+그래서 이 예제에서는 `response_model` 매개변수로 선언해야 합니다.
-대신 평문 비밀번호로 입력 모델을 만들고 해당 비밀번호 없이 출력 모델을 만들 수 있습니다:
+...하지만 아래를 계속 읽으면 이를 극복하는 방법을 볼 수 있습니다.
-{* ../../docs_src/response_model/tutorial003.py hl[9,11,16] *}
+## 반환 타입과 데이터 필터링 { #return-type-and-data-filtering }
-여기서 *경로 작동 함수*가 비밀번호를 포함하는 동일한 입력 사용자를 반환할지라도:
+이전 예제에서 계속해 봅시다. 함수에 **하나의 타입으로 어노테이션**을 하고 싶지만, 함수에서 실제로는 **더 많은 데이터**를 포함하는 것을 반환할 수 있길 원했습니다.
-{* ../../docs_src/response_model/tutorial003.py hl[24] *}
+FastAPI가 응답 모델을 사용해 데이터를 계속 **필터링**하길 원합니다. 그래서 함수가 더 많은 데이터를 반환하더라도, 응답에는 응답 모델에 선언된 필드만 포함되게 합니다.
-...`response_model`을 `UserOut` 모델로 선언했기 때문에 비밀번호를 포함하지 않습니다:
+이전 예제에서는 클래스가 달랐기 때문에 `response_model` 매개변수를 써야 했습니다. 하지만 이는 에디터와 도구가 함수 반환 타입을 체크해 주는 지원을 받지 못한다는 뜻이기도 합니다.
-{* ../../docs_src/response_model/tutorial003.py hl[22] *}
+하지만 대부분 이런 작업이 필요한 경우에는, 이 예제처럼 모델로 일부 데이터를 **필터링/제거**하길 원하는 경우가 많습니다.
-따라서 **FastAPI**는 출력 모델에서 선언하지 않은 모든 데이터를 (Pydantic을 사용하여) 필터링합니다.
+그리고 그런 경우에는 클래스와 상속을 사용하여 함수 **타입 어노테이션**을 활용해 에디터/도구에서 더 나은 지원을 받으면서도 FastAPI의 **데이터 필터링**을 유지할 수 있습니다.
-## 문서에서 보기
+{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *}
-자동 생성 문서를 보면 입력 모델과 출력 모델이 각자의 JSON 스키마를 가지고 있음을 확인할 수 있습니다:
+이를 통해 이 코드는 타입 관점에서 올바르므로 에디터와 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 77e94db72..b2b54836a 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/first-steps.md b/docs/ko/docs/tutorial/security/first-steps.md
new file mode 100644
index 000000000..4c9181b31
--- /dev/null
+++ b/docs/ko/docs/tutorial/security/first-steps.md
@@ -0,0 +1,203 @@
+# 보안 - 첫 단계 { #security-first-steps }
+
+어떤 도메인에 **backend** API가 있다고 가정해 보겠습니다.
+
+그리고 다른 도메인에 **frontend**가 있거나, 같은 도메인의 다른 경로에 있거나(또는 모바일 애플리케이션에 있을 수도 있습니다).
+
+그리고 frontend가 **username**과 **password**를 사용해 backend에 인증할 수 있는 방법이 필요하다고 해봅시다.
+
+**FastAPI**와 함께 **OAuth2**를 사용해서 이를 구현할 수 있습니다.
+
+하지만 필요한 작은 정보 조각들을 찾기 위해 길고 긴 전체 스펙을 읽느라 시간을 쓰지 않도록 하겠습니다.
+
+보안을 처리하기 위해 **FastAPI**가 제공하는 도구들을 사용해 봅시다.
+
+## 어떻게 보이는지 { #how-it-looks }
+
+먼저 코드를 그냥 사용해서 어떻게 동작하는지 보고, 그다음에 무슨 일이 일어나는지 이해하러 다시 돌아오겠습니다.
+
+## `main.py` 만들기 { #create-main-py }
+
+예제를 파일 `main.py`에 복사하세요:
+
+{* ../../docs_src/security/tutorial001_an_py39.py *}
+
+## 실행하기 { #run-it }
+
+/// info | 정보
+
+`python-multipart` 패키지는 `pip install "fastapi[standard]"` 명령을 실행하면 **FastAPI**와 함께 자동으로 설치됩니다.
+
+하지만 `pip install fastapi` 명령을 사용하면 `python-multipart` 패키지가 기본으로 포함되지 않습니다.
+
+수동으로 설치하려면, [가상 환경](../../virtual-environments.md){.internal-link target=_blank}을 만들고 활성화한 다음, 아래로 설치하세요:
+
+```console
+$ pip install python-multipart
+```
+
+이는 **OAuth2**가 `username`과 `password`를 보내기 위해 "form data"를 사용하기 때문입니다.
+
+///
+
+다음으로 예제를 실행하세요:
+
+
+
+/// check | Authorize 버튼!
+
+반짝이는 새 "Authorize" 버튼이 이미 있습니다.
+
+그리고 *경로 처리*에는 오른쪽 상단에 클릭할 수 있는 작은 자물쇠가 있습니다.
+
+///
+
+그리고 이를 클릭하면 `username`과 `password`(그리고 다른 선택적 필드들)를 입력할 수 있는 작은 인증 폼이 나타납니다:
+
+
+
+/// note | 참고
+
+폼에 무엇을 입력하든 아직은 동작하지 않습니다. 하지만 곧 여기까지 구현할 것입니다.
+
+///
+
+물론 이것은 최종 사용자를 위한 frontend는 아니지만, 모든 API를 대화형으로 문서화하는 훌륭한 자동 도구입니다.
+
+frontend 팀(그게 본인일 수도 있습니다)이 사용할 수 있습니다.
+
+서드파티 애플리케이션과 시스템에서도 사용할 수 있습니다.
+
+그리고 동일한 애플리케이션을 디버그하고, 확인하고, 테스트하기 위해 본인이 사용할 수도 있습니다.
+
+## `password` 플로우 { #the-password-flow }
+
+이제 조금 돌아가서 이것들이 무엇인지 이해해 봅시다.
+
+`password` "flow"는 보안과 인증을 처리하기 위해 OAuth2에서 정의한 여러 방식("flows") 중 하나입니다.
+
+OAuth2는 backend 또는 API가 사용자를 인증하는 서버와 독립적일 수 있도록 설계되었습니다.
+
+하지만 이 경우에는 같은 **FastAPI** 애플리케이션이 API와 인증을 모두 처리합니다.
+
+따라서, 단순화된 관점에서 다시 정리해보면:
+
+* 사용자가 frontend에서 `username`과 `password`를 입력하고 `Enter`를 누릅니다.
+* frontend(사용자의 브라우저에서 실행됨)는 해당 `username`과 `password`를 우리 API의 특정 URL로 보냅니다(`tokenUrl="token"`로 선언됨).
+* API는 `username`과 `password`를 확인하고 "token"으로 응답합니다(아직 아무것도 구현하지 않았습니다).
+ * "token"은 나중에 이 사용자를 검증하는 데 사용할 수 있는 어떤 내용이 담긴 문자열일 뿐입니다.
+ * 보통 token은 일정 시간이 지나면 만료되도록 설정합니다.
+ * 그래서 사용자는 나중에 어느 시점엔 다시 로그인해야 합니다.
+ * 그리고 token이 도난당하더라도 위험이 더 낮습니다. 대부분의 경우 영구적으로 항상 동작하는 키와는 다릅니다.
+* frontend는 그 token을 임시로 어딘가에 저장합니다.
+* 사용자가 frontend에서 클릭해서 frontend 웹 앱의 다른 섹션으로 이동합니다.
+* frontend는 API에서 더 많은 데이터를 가져와야 합니다.
+ * 하지만 그 특정 endpoint에는 인증이 필요합니다.
+ * 그래서 우리 API에 인증하기 위해 `Authorization` 헤더를, 값은 `Bearer `에 token을 더한 형태로 보냅니다.
+ * token에 `foobar`가 들어 있다면 `Authorization` 헤더의 내용은 `Bearer foobar`가 됩니다.
+
+## **FastAPI**의 `OAuth2PasswordBearer` { #fastapis-oauth2passwordbearer }
+
+**FastAPI**는 이런 보안 기능을 구현하기 위해, 서로 다른 추상화 수준에서 여러 도구를 제공합니다.
+
+이 예제에서는 **OAuth2**의 **Password** 플로우와 **Bearer** token을 사용합니다. 이를 위해 `OAuth2PasswordBearer` 클래스를 사용합니다.
+
+/// info | 정보
+
+"bearer" token만이 유일한 선택지는 아닙니다.
+
+하지만 이 사용 사례에는 가장 적합한 선택입니다.
+
+또한 OAuth2 전문가로서 왜 다른 옵션이 더 적합한지 정확히 아는 경우가 아니라면, 대부분의 사용 사례에도 가장 적합할 가능성이 큽니다.
+
+그런 경우를 위해서도 **FastAPI**는 이를 구성할 수 있는 도구를 제공합니다.
+
+///
+
+`OAuth2PasswordBearer` 클래스의 인스턴스를 만들 때 `tokenUrl` 파라미터를 전달합니다. 이 파라미터에는 클라이언트(사용자의 브라우저에서 실행되는 frontend)가 token을 받기 위해 `username`과 `password`를 보낼 URL이 들어 있습니다.
+
+{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *}
+
+/// tip | 팁
+
+여기서 `tokenUrl="token"`은 아직 만들지 않은 상대 URL `token`을 가리킵니다. 상대 URL이므로 `./token`과 동일합니다.
+
+상대 URL을 사용하므로, 예를 들어 API가 `https://example.com/`에 있다면 `https://example.com/token`을 가리킵니다. 하지만 API가 `https://example.com/api/v1/`에 있다면 `https://example.com/api/v1/token`을 가리킵니다.
+
+상대 URL을 사용하는 것은 [프록시 뒤에서](../../advanced/behind-a-proxy.md){.internal-link target=_blank} 같은 고급 사용 사례에서도 애플리케이션이 계속 동작하도록 보장하는 데 중요합니다.
+
+///
+
+이 파라미터는 그 endpoint / *경로 처리*를 만들지는 않지만, URL `/token`이 클라이언트가 token을 얻기 위해 사용해야 할 URL이라고 선언합니다. 이 정보는 OpenAPI에 사용되고, 이어서 대화형 API 문서 시스템에서도 사용됩니다.
+
+곧 실제 경로 처리를 만들 것입니다.
+
+/// info | 정보
+
+엄격한 "Pythonista"라면 `token_url` 대신 `tokenUrl` 같은 파라미터 이름 스타일이 마음에 들지 않을 수도 있습니다.
+
+이는 OpenAPI 스펙에서 사용하는 이름과 동일하게 맞춘 것이기 때문입니다. 그래서 이런 보안 스킴에 대해 더 조사해야 할 때, 그대로 복사해서 붙여 넣어 더 많은 정보를 찾을 수 있습니다.
+
+///
+
+`oauth2_scheme` 변수는 `OAuth2PasswordBearer`의 인스턴스이지만, "callable"이기도 합니다.
+
+다음처럼 호출될 수 있습니다:
+
+```Python
+oauth2_scheme(some, parameters)
+```
+
+따라서 `Depends`와 함께 사용할 수 있습니다.
+
+### 사용하기 { #use-it }
+
+이제 `Depends`로 `oauth2_scheme`를 의존성에 전달할 수 있습니다.
+
+{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
+
+이 의존성은 `str`을 제공하고, 그 값은 *경로 처리 함수*의 파라미터 `token`에 할당됩니다.
+
+**FastAPI**는 이 의존성을 사용해 OpenAPI 스키마(및 자동 API 문서)에 "security scheme"를 정의할 수 있다는 것을 알게 됩니다.
+
+/// info | 기술 세부사항
+
+**FastAPI**는 (의존성에 선언된) `OAuth2PasswordBearer` 클래스를 사용해 OpenAPI에서 보안 스킴을 정의할 수 있다는 것을 알고 있습니다. 이는 `OAuth2PasswordBearer`가 `fastapi.security.oauth2.OAuth2`를 상속하고, 이것이 다시 `fastapi.security.base.SecurityBase`를 상속하기 때문입니다.
+
+OpenAPI(및 자동 API 문서)와 통합되는 모든 보안 유틸리티는 `SecurityBase`를 상속하며, 그래서 **FastAPI**가 이를 OpenAPI에 어떻게 통합할지 알 수 있습니다.
+
+///
+
+## 무엇을 하는지 { #what-it-does }
+
+요청에서 `Authorization` 헤더를 찾아, 값이 `Bearer `에 어떤 token이 붙은 형태인지 확인한 뒤, 그 token을 `str`로 반환합니다.
+
+`Authorization` 헤더가 없거나, 값에 `Bearer ` token이 없다면, 곧바로 401 상태 코드 오류(`UNAUTHORIZED`)로 응답합니다.
+
+오류를 반환하기 위해 token이 존재하는지 직접 확인할 필요조차 없습니다. 함수가 실행되었다면 그 token에는 `str`이 들어 있다고 확신할 수 있습니다.
+
+대화형 문서에서 이미 시도해 볼 수 있습니다:
+
+
+
+아직 token의 유효성을 검증하진 않지만, 이것만으로도 시작은 된 셈입니다.
+
+## 요약 { #recap }
+
+즉, 추가로 3~4줄만으로도 이미 원시적인 형태의 보안을 갖추게 됩니다.
diff --git a/docs/ko/docs/tutorial/security/get-current-user.md b/docs/ko/docs/tutorial/security/get-current-user.md
index 98ef3885e..f21a22b7a 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/index.md b/docs/ko/docs/tutorial/security/index.md
new file mode 100644
index 000000000..2320b0657
--- /dev/null
+++ b/docs/ko/docs/tutorial/security/index.md
@@ -0,0 +1,106 @@
+# 보안 { #security }
+
+보안, 인증(authentication), 인가(authorization)를 처리하는 방법은 매우 다양합니다.
+
+그리고 보통 복잡하고 "어려운" 주제이기도 합니다.
+
+많은 프레임워크와 시스템에서 보안과 인증만 처리하는 데도 큰 노력과 코드가 필요합니다(많은 경우 작성된 전체 코드의 50% 이상이 될 수도 있습니다).
+
+**FastAPI**는 모든 보안 명세를 전부 공부하고 배울 필요 없이, 표준적인 방식으로 쉽고 빠르게 **보안(Security)** 을 다룰 수 있도록 여러 도구를 제공합니다.
+
+하지만 먼저, 몇 가지 작은 개념을 확인해 보겠습니다.
+
+## 급하신가요? { #in-a-hurry }
+
+이 용어들에 관심이 없고 사용자명과 비밀번호 기반 인증을 사용한 보안을 *지금 당장* 추가하기만 하면 된다면, 다음 장들로 넘어가세요.
+
+## OAuth2 { #oauth2 }
+
+OAuth2는 인증과 인가를 처리하는 여러 방법을 정의하는 명세입니다.
+
+상당히 방대한 명세이며 여러 복잡한 사용 사례를 다룹니다.
+
+"제3자"를 사용해 인증하는 방법도 포함합니다.
+
+바로 `"Facebook, Google, X (Twitter), GitHub로 로그인"` 같은 시스템들이 내부적으로 사용하는 방식입니다.
+
+### OAuth 1 { #oauth-1 }
+
+OAuth 1도 있었는데, 이는 OAuth2와 매우 다르고 통신을 암호화하는 방법까지 직접 명세에 포함했기 때문에 더 복잡했습니다.
+
+요즘에는 그다지 인기 있거나 사용되지는 않습니다.
+
+OAuth2는 통신을 어떻게 암호화할지는 명세하지 않고, 애플리케이션이 HTTPS로 제공될 것을 기대합니다.
+
+/// tip | 팁
+
+**배포**에 대한 섹션에서 Traefik과 Let's Encrypt를 사용해 무료로 HTTPS를 설정하는 방법을 볼 수 있습니다.
+
+///
+
+## OpenID Connect { #openid-connect }
+
+OpenID Connect는 **OAuth2**를 기반으로 한 또 다른 명세입니다.
+
+OAuth2에서 비교적 모호한 부분을 일부 구체화하여 상호 운용성을 높이려는 확장입니다.
+
+예를 들어, Google 로그인은 OpenID Connect를 사용합니다(내부적으로는 OAuth2를 사용).
+
+하지만 Facebook 로그인은 OpenID Connect를 지원하지 않습니다. 자체적인 변형의 OAuth2를 사용합니다.
+
+### OpenID("OpenID Connect"가 아님) { #openid-not-openid-connect }
+
+"OpenID"라는 명세도 있었습니다. 이는 **OpenID Connect**와 같은 문제를 해결하려고 했지만, OAuth2를 기반으로 하지 않았습니다.
+
+따라서 완전히 별도의 추가 시스템이었습니다.
+
+요즘에는 그다지 인기 있거나 사용되지는 않습니다.
+
+## OpenAPI { #openapi }
+
+OpenAPI(이전에는 Swagger로 알려짐)는 API를 구축하기 위한 공개 명세입니다(현재 Linux Foundation의 일부).
+
+**FastAPI**는 **OpenAPI**를 기반으로 합니다.
+
+이 덕분에 여러 자동 대화형 문서 인터페이스, 코드 생성 등과 같은 기능을 사용할 수 있습니다.
+
+OpenAPI에는 여러 보안 "scheme"을 정의하는 방법이 있습니다.
+
+이를 사용하면 이러한 대화형 문서 시스템을 포함해, 표준 기반 도구들을 모두 활용할 수 있습니다.
+
+OpenAPI는 다음 보안 scheme들을 정의합니다:
+
+* `apiKey`: 다음에서 전달될 수 있는 애플리케이션 전용 키:
+ * 쿼리 파라미터
+ * 헤더
+ * 쿠키
+* `http`: 표준 HTTP 인증 시스템, 예:
+ * `bearer`: `Authorization` 헤더에 `Bearer ` + 토큰 값을 넣는 방식. OAuth2에서 유래했습니다.
+ * HTTP Basic 인증
+ * HTTP Digest 등
+* `oauth2`: 보안을 처리하는 모든 OAuth2 방식(이를 "flow"라고 부릅니다).
+ * 이 flow들 중 여러 개는 OAuth 2.0 인증 제공자(예: Google, Facebook, X (Twitter), GitHub 등)를 구축하는 데 적합합니다:
+ * `implicit`
+ * `clientCredentials`
+ * `authorizationCode`
+ * 하지만 같은 애플리케이션에서 직접 인증을 처리하는 데 완벽하게 사용할 수 있는 특정 "flow"도 하나 있습니다:
+ * `password`: 다음 장들에서 이에 대한 예시를 다룹니다.
+* `openIdConnect`: OAuth2 인증 데이터를 자동으로 탐색(discover)하는 방법을 정의합니다.
+ * 이 자동 탐색은 OpenID Connect 명세에서 정의됩니다.
+
+
+/// tip | 팁
+
+Google, Facebook, X (Twitter), GitHub 등 다른 인증/인가 제공자를 통합하는 것도 가능하며 비교적 쉽습니다.
+
+가장 복잡한 문제는 그런 인증/인가 제공자 자체를 구축하는 것이지만, **FastAPI**는 어려운 작업을 대신 처리해 주면서 이를 쉽게 할 수 있는 도구를 제공합니다.
+
+///
+
+## **FastAPI** 유틸리티 { #fastapi-utilities }
+
+FastAPI는 `fastapi.security` 모듈에서 각 보안 scheme에 대한 여러 도구를 제공하며, 이러한 보안 메커니즘을 더 쉽게 사용할 수 있게 해줍니다.
+
+다음 장들에서는 **FastAPI**가 제공하는 도구를 사용해 API에 보안을 추가하는 방법을 보게 될 것입니다.
+
+또한 대화형 문서 시스템에 어떻게 자동으로 통합되는지도 확인하게 됩니다.
diff --git a/docs/ko/docs/tutorial/security/oauth2-jwt.md b/docs/ko/docs/tutorial/security/oauth2-jwt.md
index 8d27856e8..907795ca4 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 f10c4f588..189dd89f2 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 58c7017d6..3d64cf627 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 }
-애플리케이션을 실행하려면 다음 명령을 사용합니다:
+애플리케이션을 실행할 수 있습니다:
- FastAPI framework, zeer goede prestaties, eenvoudig te leren, snel te programmeren, klaar voor productie -
- - ---- - -**Documentatie**: https://fastapi.tiangolo.com - -**Broncode**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is een modern, snel (zeer goede prestaties), web framework voor het bouwen van API's in Python, gebruikmakend van standaard Python type-hints. - -De belangrijkste kenmerken zijn: - -* **Snel**: Zeer goede prestaties, vergelijkbaar met **NodeJS** en **Go** (dankzij Starlette en Pydantic). [Een van de snelste beschikbare Python frameworks](#prestaties). -* **Snel te programmeren**: Verhoog de snelheid om functionaliteit te ontwikkelen met ongeveer 200% tot 300%. * -* **Minder bugs**: Verminder ongeveer 40% van de door mensen (ontwikkelaars) veroorzaakte fouten. * -* **Intuïtief**: Buitengewoon goede ondersteuning voor editors. Overal automische code aanvulling. Minder tijd kwijt aan debuggen. -* **Eenvoudig**: Ontworpen om gemakkelijk te gebruiken en te leren. Minder tijd nodig om documentatie te lezen. -* **Kort**: Minimaliseer codeduplicatie. Elke parameterdeclaratie ondersteunt meerdere functionaliteiten. Minder bugs. -* **Robust**: Code gereed voor productie. Met automatische interactieve documentatie. -* **Standards-based**: Gebaseerd op (en volledig verenigbaar met) open standaarden voor API's: OpenAPI (voorheen bekend als Swagger) en JSON Schema. - -* schatting op basis van testen met een intern ontwikkelteam en bouwen van productieapplicaties. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} -async def...fastapi dev main.py...email_validator - voor email validatie.
-
-Gebruikt door Starlette:
-
-* httpx - Vereist indien je de `TestClient` wil gebruiken.
-* jinja2 - Vereist als je de standaard templateconfiguratie wil gebruiken.
-* python-multipart - Vereist indien je "parsen" van formulieren wil ondersteunen met `requests.form()`.
-
-Gebruikt door FastAPI / Starlette:
-
-* uvicorn - voor de server die jouw applicatie laadt en bedient.
-* `fastapi-cli` - om het `fastapi` commando te voorzien.
-
-### Zonder `standard` Afhankelijkheden
-
-Indien je de optionele `standard` afhankelijkheden niet wenst te installeren, kan je installeren met `pip install fastapi` in plaats van `pip install "fastapi[standard]"`.
-
-### Bijkomende Optionele Afhankelijkheden
-
-Er zijn nog een aantal bijkomende afhankelijkheden die je eventueel kan installeren.
-
-Bijkomende optionele afhankelijkheden voor Pydantic:
-
-* pydantic-settings - voor het beheren van settings.
-* pydantic-extra-types - voor extra data types die gebruikt kunnen worden met Pydantic.
-
-Bijkomende optionele afhankelijkheden voor FastAPI:
-
-* orjson - Vereist indien je `ORJSONResponse` wil gebruiken.
-* ujson - Vereist indien je `UJSONResponse` wil gebruiken.
-
-## Licentie
-
-Dit project is gelicenseerd onder de voorwaarden van de MIT licentie.
diff --git a/docs/nl/docs/python-types.md b/docs/nl/docs/python-types.md
deleted file mode 100644
index fb8b1e5fd..000000000
--- a/docs/nl/docs/python-types.md
+++ /dev/null
@@ -1,587 +0,0 @@
-# Introductie tot Python Types
-
-Python biedt ondersteuning voor optionele "type hints" (ook wel "type annotaties" genoemd).
-
-Deze **"type hints"** of annotaties zijn een speciale syntax waarmee het type van een variabele kan worden gedeclareerd.
-
-Door types voor je variabelen te declareren, kunnen editors en hulpmiddelen je beter ondersteunen.
-
-Dit is slechts een **korte tutorial/opfrisser** over Python type hints. Het behandelt enkel het minimum dat nodig is om ze te gebruiken met **FastAPI**... en dat is relatief weinig.
-
-**FastAPI** is helemaal gebaseerd op deze type hints, ze geven veel voordelen.
-
-Maar zelfs als je **FastAPI** nooit gebruikt, heb je er baat bij om er iets over te leren.
-
-/// note
-
-Als je een Python expert bent en alles al weet over type hints, sla dan dit hoofdstuk over.
-
-///
-
-## Motivatie
-
-Laten we beginnen met een eenvoudig voorbeeld:
-
-{* ../../docs_src/python_types/tutorial001.py *}
-
-
-Het aanroepen van dit programma leidt tot het volgende resultaat:
-
-```
-John Doe
-```
-
-De functie voert het volgende uit:
-
-* Neem een `first_name` en een `last_name`
-* Converteer de eerste letter van elk naar een hoofdletter met `title()`.
-``
-* Voeg samen met een spatie in het midden.
-
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
-
-
-### Bewerk het
-
-Dit is een heel eenvoudig programma.
-
-Maar stel je nu voor dat je het vanaf nul zou moeten maken.
-
-Op een gegeven moment zou je aan de definitie van de functie zijn begonnen, je had de parameters klaar...
-
-Maar dan moet je “die methode die de eerste letter naar hoofdletters converteert” aanroepen.
-
-Was het `upper`? Was het `uppercase`? `first_uppercase`? `capitalize`?
-
-Dan roep je de hulp in van je oude programmeursvriend, (automatische) code aanvulling in je editor.
-
-Je typt de eerste parameter van de functie, `first_name`, dan een punt (`.`) en drukt dan op `Ctrl+Spatie` om de aanvulling te activeren.
-
-Maar helaas krijg je niets bruikbaars:
-
-
-
-### Types toevoegen
-
-Laten we een enkele regel uit de vorige versie aanpassen.
-
-We zullen precies dit fragment, de parameters van de functie, wijzigen van:
-
-```Python
- first_name, last_name
-```
-
-naar:
-
-```Python
- first_name: str, last_name: str
-```
-
-Dat is alles.
-
-Dat zijn de "type hints":
-
-{* ../../docs_src/python_types/tutorial002.py hl[1] *}
-
-
-Dit is niet hetzelfde als het declareren van standaardwaarden zoals bij:
-
-```Python
- first_name="john", last_name="doe"
-```
-
-Het is iets anders.
-
-We gebruiken dubbele punten (`:`), geen gelijkheidstekens (`=`).
-
-Het toevoegen van type hints verandert normaal gesproken niet wat er gebeurt in je programma t.o.v. wat er zonder type hints zou gebeuren.
-
-Maar stel je voor dat je weer bezig bent met het maken van een functie, maar deze keer met type hints.
-
-Op hetzelfde moment probeer je de automatische aanvulling te activeren met `Ctrl+Spatie` en je ziet:
-
-
-
-Nu kun je de opties bekijken en er doorheen scrollen totdat je de optie vindt die “een belletje doet rinkelen”:
-
-
-
-### Meer motivatie
-
-Bekijk deze functie, deze heeft al type hints:
-
-{* ../../docs_src/python_types/tutorial003.py hl[1] *}
-
-
-Omdat de editor de types van de variabelen kent, krijgt u niet alleen aanvulling, maar ook controles op fouten:
-
-
-
-Nu weet je hoe je het moet oplossen, converteer `age` naar een string met `str(age)`:
-
-{* ../../docs_src/python_types/tutorial004.py hl[2] *}
-
-
-## Types declareren
-
-Je hebt net de belangrijkste plek om type hints te declareren gezien. Namelijk als functieparameters.
-
-Dit is ook de belangrijkste plek waar je ze gebruikt met **FastAPI**.
-
-### Eenvoudige types
-
-Je kunt alle standaard Python types declareren, niet alleen `str`.
-
-Je kunt bijvoorbeeld het volgende gebruiken:
-
-* `int`
-* `float`
-* `bool`
-* `bytes`
-
-{* ../../docs_src/python_types/tutorial005.py hl[1] *}
-
-
-### Generieke types met typeparameters
-
-Er zijn enkele datastructuren die andere waarden kunnen bevatten, zoals `dict`, `list`, `set` en `tuple` en waar ook de interne waarden hun eigen type kunnen hebben.
-
-Deze types die interne types hebben worden “**generieke**” types genoemd. Het is mogelijk om ze te declareren, zelfs met hun interne types.
-
-Om deze types en de interne types te declareren, kun je de standaard Python module `typing` gebruiken. Deze module is speciaal gemaakt om deze type hints te ondersteunen.
-
-#### Nieuwere versies van Python
-
-De syntax met `typing` is **verenigbaar** met alle versies, van Python 3.6 tot aan de nieuwste, inclusief Python 3.9, Python 3.10, enz.
-
-Naarmate Python zich ontwikkelt, worden **nieuwere versies**, met verbeterde ondersteuning voor deze type annotaties, beschikbaar. In veel gevallen hoef je niet eens de `typing` module te importeren en te gebruiken om de type annotaties te declareren.
-
-Als je een recentere versie van Python kunt kiezen voor je project, kun je profiteren van die extra eenvoud.
-
-In alle documentatie staan voorbeelden die compatibel zijn met elke versie van Python (als er een verschil is).
-
-Bijvoorbeeld “**Python 3.6+**” betekent dat het compatibel is met Python 3.6 of hoger (inclusief 3.7, 3.8, 3.9, 3.10, etc). En “**Python 3.9+**” betekent dat het compatibel is met Python 3.9 of hoger (inclusief 3.10, etc).
-
-Als je de **laatste versies van Python** kunt gebruiken, gebruik dan de voorbeelden voor de laatste versie, die hebben de **beste en eenvoudigste syntax**, bijvoorbeeld “**Python 3.10+**”.
-
-#### List
-
-Laten we bijvoorbeeld een variabele definiëren als een `list` van `str`.
-
-//// tab | Python 3.9+
-
-Declareer de variabele met dezelfde dubbele punt (`:`) syntax.
-
-Als type, vul `list` in.
-
-Doordat de list een type is dat enkele interne types bevat, zet je ze tussen vierkante haakjes:
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-Van `typing`, importeer `List` (met een hoofdletter `L`):
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-Declareer de variabele met dezelfde dubbele punt (`:`) syntax.
-
-Zet als type de `List` die je hebt geïmporteerd uit `typing`.
-
-Doordat de list een type is dat enkele interne types bevat, zet je ze tussen vierkante haakjes:
-
-```Python hl_lines="4"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
-
-////
-
-/// info
-
-De interne types tussen vierkante haakjes worden “typeparameters” genoemd.
-
-In dit geval is `str` de typeparameter die wordt doorgegeven aan `List` (of `list` in Python 3.9 en hoger).
-
-///
-
-Dat betekent: “de variabele `items` is een `list`, en elk van de items in deze list is een `str`”.
-
-/// tip
-
-Als je Python 3.9 of hoger gebruikt, hoef je `List` niet te importeren uit `typing`, je kunt in plaats daarvan hetzelfde reguliere `list` type gebruiken.
-
-///
-
-Door dat te doen, kan je editor ondersteuning bieden, zelfs tijdens het verwerken van items uit de list:
-
-
-
-Zonder types is dat bijna onmogelijk om te bereiken.
-
-Merk op dat de variabele `item` een van de elementen is in de lijst `items`.
-
-Toch weet de editor dat het een `str` is, en biedt daar vervolgens ondersteuning voor aan.
-
-#### Tuple en Set
-
-Je kunt hetzelfde doen om `tuple`s en `set`s te declareren:
-
-//// 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!}
-```
-
-////
-
-Dit betekent:
-
-* De variabele `items_t` is een `tuple` met 3 items, een `int`, nog een `int`, en een `str`.
-* De variabele `items_s` is een `set`, en elk van de items is van het type `bytes`.
-
-#### Dict
-
-Om een `dict` te definiëren, geef je 2 typeparameters door, gescheiden door komma's.
-
-De eerste typeparameter is voor de sleutels (keys) van de `dict`.
-
-De tweede typeparameter is voor de waarden (values) van het `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!}
-```
-
-////
-
-Dit betekent:
-
-* De variabele `prices` is een `dict`:
- * De sleutels van dit `dict` zijn van het type `str` (bijvoorbeeld de naam van elk item).
- * De waarden van dit `dict` zijn van het type `float` (bijvoorbeeld de prijs van elk item).
-
-#### Union
-
-Je kunt een variable declareren die van **verschillende types** kan zijn, bijvoorbeeld een `int` of een `str`.
-
-In Python 3.6 en hoger (inclusief Python 3.10) kun je het `Union`-type van `typing` gebruiken en de mogelijke types die je wilt accepteren, tussen de vierkante haakjes zetten.
-
-In Python 3.10 is er ook een **nieuwe syntax** waarin je de mogelijke types kunt scheiden door een verticale balk (`|`).
-
-//// 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!}
-```
-
-////
-
-In beide gevallen betekent dit dat `item` een `int` of een `str` kan zijn.
-
-#### Mogelijk `None`
-
-Je kunt declareren dat een waarde een type kan hebben, zoals `str`, maar dat het ook `None` kan zijn.
-
-In Python 3.6 en hoger (inclusief Python 3.10) kun je het declareren door `Optional` te importeren en te gebruiken vanuit de `typing`-module.
-
-```Python hl_lines="1 4"
-{!../../docs_src/python_types/tutorial009.py!}
-```
-
-Door `Optional[str]` te gebruiken in plaats van alleen `str`, kan de editor je helpen fouten te detecteren waarbij je ervan uit zou kunnen gaan dat een waarde altijd een `str` is, terwijl het in werkelijkheid ook `None` zou kunnen zijn.
-
-`Optional[EenType]` is eigenlijk een snelkoppeling voor `Union[EenType, None]`, ze zijn equivalent.
-
-Dit betekent ook dat je in Python 3.10 `EenType | None` kunt gebruiken:
-
-//// 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!}
-```
-
-////
-
-#### Gebruik van `Union` of `Optional`
-
-Als je een Python versie lager dan 3.10 gebruikt, is dit een tip vanuit mijn **subjectieve** standpunt:
-
-* 🚨 Vermijd het gebruik van `Optional[EenType]`.
-* Gebruik in plaats daarvan **`Union[EenType, None]`** ✨.
-
-Beide zijn gelijkwaardig en onderliggend zijn ze hetzelfde, maar ik zou `Union` aanraden in plaats van `Optional` omdat het woord “**optional**” lijkt te impliceren dat de waarde optioneel is, en het eigenlijk betekent “het kan `None` zijn”, zelfs als het niet optioneel is en nog steeds vereist is.
-
-Ik denk dat `Union[SomeType, None]` explicieter is over wat het betekent.
-
-Het gaat alleen om de woorden en naamgeving. Maar die naamgeving kan invloed hebben op hoe jij en je teamgenoten over de code denken.
-
-Laten we als voorbeeld deze functie nemen:
-
-{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *}
-
-
-De parameter `name` is gedefinieerd als `Optional[str]`, maar is **niet optioneel**, je kunt de functie niet aanroepen zonder de parameter:
-
-```Python
-say_hi() # Oh, nee, dit geeft een foutmelding! 😱
-```
-
-De `name` parameter is **nog steeds vereist** (niet *optioneel*) omdat het geen standaardwaarde heeft. Toch accepteert `name` `None` als waarde:
-
-```Python
-say_hi(name=None) # Dit werkt, None is geldig 🎉
-```
-
-Het goede nieuws is dat als je eenmaal Python 3.10 gebruikt, je je daar geen zorgen meer over hoeft te maken, omdat je dan gewoon `|` kunt gebruiken om unions van types te definiëren:
-
-{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
-
-
-Dan hoef je je geen zorgen te maken over namen als `Optional` en `Union`. 😎
-
-#### Generieke typen
-
-De types die typeparameters in vierkante haakjes gebruiken, worden **Generieke types** of **Generics** genoemd, bijvoorbeeld:
-
-//// tab | Python 3.10+
-
-Je kunt dezelfde ingebouwde types gebruiken als generics (met vierkante haakjes en types erin):
-
-* `list`
-* `tuple`
-* `set`
-* `dict`
-
-Hetzelfde als bij Python 3.8, uit de `typing`-module:
-
-* `Union`
-* `Optional` (hetzelfde als bij Python 3.8)
-* ...en anderen.
-
-In Python 3.10 kun je , als alternatief voor de generieke `Union` en `Optional`, de verticale lijn (`|`) gebruiken om unions van typen te voorzien, dat is veel beter en eenvoudiger.
-
-////
-
-//// tab | Python 3.9+
-
-Je kunt dezelfde ingebouwde types gebruiken als generieke types (met vierkante haakjes en types erin):
-
-* `list`
-* `tuple`
-* `set`
-* `dict`
-
-En hetzelfde als met Python 3.8, vanuit de `typing`-module:
-
-* `Union`
-* `Optional`
-* ...en anderen.
-
-////
-
-//// tab | Python 3.8+
-
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Union`
-* `Optional`
-* ...en anderen.
-
-////
-
-### Klassen als types
-
-Je kunt een klasse ook declareren als het type van een variabele.
-
-Stel dat je een klasse `Person` hebt, met een naam:
-
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
-
-
-Vervolgens kun je een variabele van het type `Persoon` declareren:
-
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
-
-
-Dan krijg je ook nog eens volledige editorondersteuning:
-
-
-
-Merk op dat dit betekent dat "`one_person` een **instantie** is van de klasse `Person`".
-
-Dit betekent niet dat `one_person` de **klasse** is met de naam `Person`.
-
-## Pydantic modellen
-
-Pydantic is een Python-pakket voor het uitvoeren van datavalidatie.
-
-Je declareert de "vorm" van de data als klassen met attributen.
-
-Elk attribuut heeft een type.
-
-Vervolgens maak je een instantie van die klasse met een aantal waarden en het valideert de waarden, converteert ze naar het juiste type (als dat het geval is) en geeft je een object met alle data terug.
-
-Daarnaast krijg je volledige editorondersteuning met dat resulterende object.
-
-Een voorbeeld uit de officiële Pydantic-documentatie:
-
-//// 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
-
-Om meer te leren over Pydantic, bekijk de documentatie.
-
-///
-
-**FastAPI** is volledig gebaseerd op Pydantic.
-
-Je zult veel meer van dit alles in de praktijk zien in de [Tutorial - Gebruikershandleiding](tutorial/index.md){.internal-link target=_blank}.
-
-/// tip
-
-Pydantic heeft een speciaal gedrag wanneer je `Optional` of `Union[EenType, None]` gebruikt zonder een standaardwaarde, je kunt er meer over lezen in de Pydantic-documentatie over Verplichte optionele velden.
-
-///
-
-## Type Hints met Metadata Annotaties
-
-Python heeft ook een functie waarmee je **extra metadata** in deze type hints kunt toevoegen met behulp van `Annotated`.
-
-//// tab | Python 3.9+
-
-In Python 3.9 is `Annotated` onderdeel van de standaardpakket, dus je kunt het importeren vanuit `typing`.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013_py39.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-In versies lager dan Python 3.9 importeer je `Annotated` vanuit `typing_extensions`.
-
-Het wordt al geïnstalleerd met **FastAPI**.
-
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial013.py!}
-```
-
-////
-
-Python zelf doet niets met deze `Annotated` en voor editors en andere hulpmiddelen is het type nog steeds een `str`.
-
-Maar je kunt deze ruimte in `Annotated` gebruiken om **FastAPI** te voorzien van extra metadata over hoe je wilt dat je applicatie zich gedraagt.
-
-Het belangrijkste om te onthouden is dat **de eerste *typeparameter*** die je doorgeeft aan `Annotated` het **werkelijke type** is. De rest is gewoon metadata voor andere hulpmiddelen.
-
-Voor nu hoef je alleen te weten dat `Annotated` bestaat en dat het standaard Python is. 😎
-
-Later zul je zien hoe **krachtig** het kan zijn.
-
-/// tip
-
-Het feit dat dit **standaard Python** is, betekent dat je nog steeds de **best mogelijke ontwikkelaarservaring** krijgt in je editor, met de hulpmiddelen die je gebruikt om je code te analyseren en te refactoren, enz. ✨
-
-Daarnaast betekent het ook dat je code zeer verenigbaar zal zijn met veel andere Python-hulpmiddelen en -pakketten. 🚀
-
-///
-
-## Type hints in **FastAPI**
-
-**FastAPI** maakt gebruik van type hints om verschillende dingen te doen.
-
-Met **FastAPI** declareer je parameters met type hints en krijg je:
-
-* **Editor ondersteuning**.
-* **Type checks**.
-
-...en **FastAPI** gebruikt dezelfde declaraties om:
-
-* **Vereisten te definïeren **: van request pad parameters, query parameters, headers, bodies, dependencies, enz.
-* **Data te converteren**: van de request naar het vereiste type.
-* **Data te valideren**: afkomstig van elke request:
- * **Automatische foutmeldingen** te genereren die naar de client worden geretourneerd wanneer de data ongeldig is.
-* De API met OpenAPI te **documenteren**:
- * die vervolgens wordt gebruikt door de automatische interactieve documentatie gebruikersinterfaces.
-
-Dit klinkt misschien allemaal abstract. Maak je geen zorgen. Je ziet dit allemaal in actie in de [Tutorial - Gebruikershandleiding](tutorial/index.md){.internal-link target=_blank}.
-
-Het belangrijkste is dat door standaard Python types te gebruiken, op één plek (in plaats van meer klassen, decorators, enz. toe te voegen), **FastAPI** een groot deel van het werk voor je doet.
-
-/// info
-
-Als je de hele tutorial al hebt doorgenomen en terug bent gekomen om meer te weten te komen over types, is een goede bron het "cheat sheet" van `mypy`.
-
-///
diff --git a/docs/nl/mkdocs.yml b/docs/nl/mkdocs.yml
deleted file mode 100644
index de18856f4..000000000
--- a/docs/nl/mkdocs.yml
+++ /dev/null
@@ -1 +0,0 @@
-INHERIT: ../en/mkdocs.yml
diff --git a/docs/pl/docs/features.md b/docs/pl/docs/features.md
deleted file mode 100644
index 80d3bdece..000000000
--- a/docs/pl/docs/features.md
+++ /dev/null
@@ -1,201 +0,0 @@
-# Cechy
-
-## Cechy FastAPI
-
-**FastAPI** zapewnia Ci następujące korzyści:
-
-### Oparcie o standardy open
-
-* OpenAPI do tworzenia API, w tym deklaracji ścieżek operacji, parametrów, ciał zapytań, bezpieczeństwa, itp.
-* Automatyczna dokumentacja modelu danych za pomocą JSON Schema (ponieważ OpenAPI bazuje na JSON Schema).
-* Zaprojektowane z myślą o zgodności z powyższymi standardami zamiast dodawania ich obsługi po fakcie.
-* Możliwość automatycznego **generowania kodu klienta** w wielu językach.
-
-### Automatyczna dokumentacja
-
-Interaktywna dokumentacja i webowe interfejsy do eksploracji API. Z racji tego, że framework bazuje na OpenAPI, istnieje wiele opcji, z czego 2 są domyślnie dołączone.
-
-* Swagger UI, z interaktywnym interfejsem - odpytuj i testuj swoje API bezpośrednio z przeglądarki.
-
-
-
-* Alternatywna dokumentacja API z ReDoc.
-
-
-
-### Nowoczesny Python
-
-Wszystko opiera się na standardowych deklaracjach typu **Python 3.8** (dzięki Pydantic). Brak nowej składni do uczenia. Po prostu standardowy, współczesny Python.
-
-Jeśli potrzebujesz szybkiego przypomnienia jak używać deklaracji typów w Pythonie (nawet jeśli nie używasz FastAPI), sprawdź krótki samouczek: [Python Types](python-types.md){.internal-link target=_blank}.
-
-Wystarczy, że napiszesz standardowe deklaracje typów Pythona:
-
-```Python
-from datetime import date
-
-from pydantic import BaseModel
-
-# Zadeklaruj parametr jako str
-# i uzyskaj wsparcie edytora wewnątrz funkcji
-def main(user_id: str):
- return user_id
-
-
-# Model Pydantic
-class User(BaseModel):
- id: int
- name: str
- joined: date
-```
-
-A one będą mogły zostać później użyte w następujący sposób:
-
-```Python
-my_user: User = User(id=3, name="John Doe", joined="2018-07-19")
-
-second_user_data = {
- "id": 4,
- "name": "Mary",
- "joined": "2018-11-30",
-}
-
-my_second_user: User = User(**second_user_data)
-```
-
-/// info
-
-`**second_user_data` oznacza:
-
-Przekaż klucze i wartości słownika `second_user_data` bezpośrednio jako argumenty klucz-wartość, co jest równoznaczne z: `User(id=4, name="Mary", joined="2018-11-30")`
-
-///
-
-### Wsparcie edytora
-
-Cały framework został zaprojektowany tak, aby był łatwy i intuicyjny w użyciu. Wszystkie pomysły zostały przetestowane na wielu edytorach jeszcze przed rozpoczęciem procesu tworzenia, aby zapewnić najlepsze wrażenia programistyczne.
-
-Ostatnia ankieta Python developer survey jasno wskazuje, że najczęściej używaną funkcjonalnością jest autouzupełnianie w edytorze.
-
-Cała struktura frameworku **FastAPI** jest na tym oparta. Autouzupełnianie działa wszędzie.
-
-Rzadko będziesz musiał wracać do dokumentacji.
-
-Oto, jak twój edytor może Ci pomóc:
-
-* Visual Studio Code:
-
-
-
-* PyCharm:
-
-
-
-Otrzymasz uzupełnienie nawet w miejscach, w których normalnie uzupełnienia nie ma. Na przykład klucz "price" w treści JSON (który mógł być zagnieżdżony), który pochodzi z zapytania.
-
-Koniec z wpisywaniem błędnych nazw kluczy, przechodzeniem tam i z powrotem w dokumentacji lub przewijaniem w górę i w dół, aby sprawdzić, czy w końcu użyłeś nazwy `username` czy `user_name`.
-
-### Zwięzłość
-
-Wszystko posiada sensowne **domyślne wartości**. Wszędzie znajdziesz opcjonalne konfiguracje. Wszystkie parametry możesz dostroić, aby zrobić to co potrzebujesz do zdefiniowania API.
-
-Ale domyślnie wszystko **"po prostu działa"**.
-
-### Walidacja
-
-* Walidacja większości (lub wszystkich?) **typów danych** Pythona, w tym:
- * Obiektów JSON (`dict`).
- * Tablic JSON (`list`) ze zdefiniowanym typem elementów.
- * Pól tekstowych (`str`) z określeniem minimalnej i maksymalnej długości.
- * Liczb (`int`, `float`) z wartościami minimalnymi, maksymalnymi, itp.
-
-* Walidacja bardziej egzotycznych typów danych, takich jak:
- * URL.
- * Email.
- * UUID.
- * ...i inne.
-
-Cała walidacja jest obsługiwana przez ugruntowaną i solidną bibliotekę **Pydantic**.
-
-### Bezpieczeństwo i uwierzytelnianie
-
-Bezpieczeństwo i uwierzytelnianie jest zintegrowane. Bez żadnych kompromisów z bazami czy modelami danych.
-
-Wszystkie schematy bezpieczeństwa zdefiniowane w OpenAPI, w tym:
-
-* Podstawowy protokół HTTP.
-* **OAuth2** (również z **tokenami JWT**). Sprawdź samouczek [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}.
-* Klucze API w:
- * Nagłówkach.
- * Parametrach zapytań.
- * Ciasteczkach, itp.
-
-Plus wszystkie funkcje bezpieczeństwa Starlette (włączając w to **ciasteczka sesyjne**).
-
-Wszystko zbudowane jako narzędzia i komponenty wielokrotnego użytku, które można łatwo zintegrować z systemami, magazynami oraz bazami danych - relacyjnymi, NoSQL, itp.
-
-### Wstrzykiwanie Zależności
-
-FastAPI zawiera niezwykle łatwy w użyciu, ale niezwykle potężny system Wstrzykiwania Zależności.
-
-* Nawet zależności mogą mieć zależności, tworząc hierarchię lub **"graf" zależności**.
-* Wszystko jest **obsługiwane automatycznie** przez framework.
-* Wszystkie zależności mogą wymagać danych w żądaniach oraz rozszerzać ograniczenia i automatyczną dokumentację **operacji na ścieżce**.
-* **Automatyczna walidacja** parametrów *operacji na ścieżce* zdefiniowanych w zależnościach.
-* Obsługa złożonych systemów uwierzytelniania użytkowników, **połączeń z bazami danych**, itp.
-* Bazy danych, front end, itp. **bez kompromisów**, ale wciąż łatwe do integracji.
-
-### Nieograniczone "wtyczki"
-
-Lub ujmując to inaczej - brak potrzeby wtyczek. Importuj i używaj kod, który potrzebujesz.
-
-Każda integracja została zaprojektowana tak, aby była tak prosta w użyciu (z zależnościami), że możesz utworzyć "wtyczkę" dla swojej aplikacji w 2 liniach kodu, używając tej samej struktury i składni, które są używane w *operacjach na ścieżce*.
-
-### Testy
-
-* 100% pokrycia kodu testami.
-* 100% adnotacji typów.
-* Używany w aplikacjach produkcyjnych.
-
-## Cechy Starlette
-
-**FastAPI** jest w pełni kompatybilny z (oraz bazuje na) Starlette. Tak więc każdy dodatkowy kod Starlette, który posiadasz, również będzie działał.
-
-`FastAPI` jest w rzeczywistości podklasą `Starlette`, więc jeśli już znasz lub używasz Starlette, większość funkcji będzie działać w ten sam sposób.
-
-Dzięki **FastAPI** otrzymujesz wszystkie funkcje **Starlette** (ponieważ FastAPI to po prostu Starlette na sterydach):
-
-* Bardzo imponująca wydajność. Jest to jeden z najszybszych dostępnych frameworków Pythona, na równi z **NodeJS** i **Go**.
-* Wsparcie dla **WebSocket**.
-* Zadania w tle.
-* Eventy startup i shutdown.
-* Klient testowy zbudowany na bazie biblioteki `requests`.
-* **CORS**, GZip, pliki statyczne, streamy.
-* Obsługa **sesji i ciasteczek**.
-* 100% pokrycie testami.
-* 100% adnotacji typów.
-
-## Cechy Pydantic
-
-**FastAPI** jest w pełni kompatybilny z (oraz bazuje na) Pydantic. Tak więc każdy dodatkowy kod Pydantic, który posiadasz, również będzie działał.
-
-Wliczając w to zewnętrzne biblioteki, również oparte o Pydantic, takie jak ORM, ODM dla baz danych.
-
-Oznacza to, że w wielu przypadkach możesz przekazać ten sam obiekt, który otrzymasz z żądania **bezpośrednio do bazy danych**, ponieważ wszystko jest walidowane automatycznie.
-
-Działa to również w drugą stronę, w wielu przypadkach możesz po prostu przekazać obiekt otrzymany z bazy danych **bezpośrednio do klienta**.
-
-Dzięki **FastAPI** otrzymujesz wszystkie funkcje **Pydantic** (ponieważ FastAPI bazuje na Pydantic do obsługi wszystkich danych):
-
-* **Bez prania mózgu**:
- * Brak nowego mikrojęzyka do definiowania schematu, którego trzeba się nauczyć.
- * Jeśli znasz adnotacje typów Pythona to wiesz jak używać Pydantic.
-* Dobrze współpracuje z Twoim **IDE/linterem/mózgiem**:
- * Ponieważ struktury danych Pydantic to po prostu instancje klas, które definiujesz; autouzupełnianie, linting, mypy i twoja intuicja powinny działać poprawnie z Twoimi zwalidowanymi danymi.
-* Walidacja **złożonych struktur**:
- * Wykorzystanie hierarchicznych modeli Pydantic, Pythonowego modułu `typing` zawierającego `List`, `Dict`, itp.
- * Walidatory umożliwiają jasne i łatwe definiowanie, sprawdzanie złożonych struktur danych oraz dokumentowanie ich jako JSON Schema.
- * Możesz mieć głęboko **zagnieżdżone obiekty JSON** i wszystkie je poddać walidacji i adnotować.
-* **Rozszerzalność**:
- * Pydantic umożliwia zdefiniowanie niestandardowych typów danych lub rozszerzenie walidacji o metody na modelu, na których użyty jest dekorator walidatora.
-* 100% pokrycie testami.
diff --git a/docs/pl/docs/help-fastapi.md b/docs/pl/docs/help-fastapi.md
deleted file mode 100644
index 05e491d55..000000000
--- a/docs/pl/docs/help-fastapi.md
+++ /dev/null
@@ -1,269 +0,0 @@
-# Pomóż FastAPI - Uzyskaj pomoc
-
-Czy podoba Ci się **FastAPI**?
-
-Czy chciałbyś pomóc FastAPI, jego użytkownikom i autorowi?
-
-Może napotkałeś na trudności z **FastAPI** i potrzebujesz pomocy?
-
-Istnieje kilka bardzo łatwych sposobów, aby pomóc (czasami wystarczy jedno lub dwa kliknięcia).
-
-Istnieje również kilka sposobów uzyskania pomocy.
-
-## Zapisz się do newslettera
-
-Możesz zapisać się do rzadkiego [newslettera o **FastAPI i jego przyjaciołach**](newsletter.md){.internal-link target=_blank}, aby być na bieżąco z:
-
-* Aktualnościami o FastAPI i przyjaciołach 🚀
-* Przewodnikami 📝
-* Funkcjami ✨
-* Przełomowymi zmianami 🚨
-* Poradami i sztuczkami ✅
-
-## Śledź FastAPI na X (Twitter)
-
-Śledź @fastapi na **X (Twitter)** aby być na bieżąco z najnowszymi wiadomościami o **FastAPI**. 🐦
-
-## Dodaj gwiazdkę **FastAPI** na GitHubie
-
-Możesz "dodać gwiazdkę" FastAPI na GitHubie (klikając przycisk gwiazdki w prawym górnym rogu): https://github.com/fastapi/fastapi. ⭐️
-
-Dodając gwiazdkę, inni użytkownicy będą mogli łatwiej znaleźć projekt i zobaczyć, że był już przydatny dla innych.
-
-## Obserwuj repozytorium GitHub w poszukiwaniu nowych wydań
-
-Możesz "obserwować" FastAPI na GitHubie (klikając przycisk "obserwuj" w prawym górnym rogu): https://github.com/fastapi/fastapi. 👀
-
-Wybierz opcję "Tylko wydania".
-
-Dzięki temu będziesz otrzymywać powiadomienia (na swój adres e-mail) za każdym razem, gdy pojawi się nowe wydanie (nowa wersja) **FastAPI** z poprawkami błędów i nowymi funkcjami.
-
-## Skontaktuj się z autorem
-
-Możesz skontaktować się ze mną (Sebastián Ramírez / `tiangolo`), autorem.
-
-Możesz:
-
-* Śledzić mnie na **GitHubie**.
- * Zobacz inne projekty open source, które stworzyłem, a mogą być dla Ciebie pomocne.
- * Śledź mnie, aby dostać powiadomienie, gdy utworzę nowy projekt open source.
-* Śledzić mnie na **X (Twitter)** lub na Mastodonie.
- * Napisz mi, w jaki sposób korzystasz z FastAPI (uwielbiam o tym czytać).
- * Dowiedz się, gdy ogłoszę coś nowego lub wypuszczę nowe narzędzia.
- * Możesz także śledzić @fastapi na X (Twitter) (to oddzielne konto).
-* Nawiąż ze mną kontakt na **Linkedinie**.
- * Dowiedz się, gdy ogłoszę coś nowego lub wypuszczę nowe narzędzia (chociaż częściej korzystam z Twittera 🤷♂).
-* Czytaj moje posty (lub śledź mnie) na **Dev.to** lub na **Medium**.
- * Czytaj o innych pomysłach, artykułach i dowiedz się o narzędziach, które stworzyłem.
- * Śledź mnie, by wiedzieć gdy opublikuję coś nowego.
-
-## Napisz tweeta o **FastAPI**
-
-Napisz tweeta o **FastAPI** i powiedz czemu Ci się podoba. 🎉
-
-Uwielbiam czytać w jaki sposób **FastAPI** jest używane, co Ci się w nim podobało, w jakim projekcie/firmie go używasz itp.
-
-## Głosuj na FastAPI
-
-* Głosuj na **FastAPI** w Slant.
-* Głosuj na **FastAPI** w AlternativeTo.
-* Powiedz, że używasz **FastAPI** na StackShare.
-
-## Pomagaj innym, odpowiadając na ich pytania na GitHubie
-
-Możesz spróbować pomóc innym, odpowiadając w:
-
-* Dyskusjach na GitHubie
-* Problemach na GitHubie
-
-W wielu przypadkach możesz już znać odpowiedź na te pytania. 🤓
-
-Jeśli pomożesz wielu ludziom, możesz zostać oficjalnym [Ekspertem FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. 🎉
-
-Pamiętaj tylko o najważniejszym: bądź życzliwy. Ludzie przychodzą sfrustrowani i w wielu przypadkach nie zadają pytań w najlepszy sposób, ale mimo to postaraj się być dla nich jak najbardziej życzliwy. 🤗
-
-Chciałbym, by społeczność **FastAPI** była życzliwa i przyjazna. Nie akceptuj prześladowania ani braku szacunku wobec innych. Dbajmy o siebie nawzajem.
-
----
-
-Oto, jak pomóc innym z pytaniami (w dyskusjach lub problemach):
-
-### Zrozum pytanie
-
-* Upewnij się, czy rozumiesz **cel** i przypadek użycia osoby pytającej.
-
-* Następnie sprawdź, czy pytanie (większość to pytania) jest **jasne**.
-
-* W wielu przypadkach zadane pytanie dotyczy rozwiązania wymyślonego przez użytkownika, ale może istnieć **lepsze** rozwiązanie. Jeśli dokładnie zrozumiesz problem i przypadek użycia, być może będziesz mógł zaproponować lepsze **alternatywne rozwiązanie**.
-
-* Jeśli nie rozumiesz pytania, poproś o więcej **szczegółów**.
-
-### Odtwórz problem
-
-W większości przypadków problem wynika z **autorskiego kodu** osoby pytającej.
-
-Często pytający umieszczają tylko fragment kodu, niewystarczający do **odtworzenia problemu**.
-
-* Możesz poprosić ich o dostarczenie minimalnego, odtwarzalnego przykładu, który możesz **skopiować i wkleić** i uruchomić lokalnie, aby zobaczyć ten sam błąd lub zachowanie, które widzą, lub lepiej zrozumieć ich przypadki użycia.
-
-* Jeśli jesteś wyjątkowo pomocny, możesz spróbować **stworzyć taki przykład** samodzielnie, opierając się tylko na opisie problemu. Miej na uwadze, że może to zająć dużo czasu i lepiej może być najpierw poprosić ich o wyjaśnienie problemu.
-
-### Proponuj rozwiązania
-
-* Po zrozumieniu pytania możesz podać im możliwą **odpowiedź**.
-
-* W wielu przypadkach lepiej zrozumieć ich **podstawowy problem lub przypadek użycia**, ponieważ może istnieć lepszy sposób rozwiązania niż to, co próbują zrobić.
-
-### Poproś o zamknięcie
-
-Jeśli odpowiedzą, jest duża szansa, że rozwiązałeś ich problem, gratulacje, **jesteś bohaterem**! 🦸
-
-* Jeśli Twoja odpowiedź rozwiązała problem, możesz poprosić o:
-
- * W Dyskusjach na GitHubie: oznaczenie komentarza jako **odpowiedź**.
- * W Problemach na GitHubie: **zamknięcie** problemu.
-
-## Obserwuj repozytorium na GitHubie
-
-Możesz "obserwować" FastAPI na GitHubie (klikając przycisk "obserwuj" w prawym górnym rogu): https://github.com/fastapi/fastapi. 👀
-
-Jeśli wybierzesz "Obserwuj" zamiast "Tylko wydania", otrzymasz powiadomienia, gdy ktoś utworzy nowy problem lub pytanie. Możesz również określić, że chcesz być powiadamiany tylko o nowych problemach, dyskusjach, PR-ach itp.
-
-Następnie możesz spróbować pomóc rozwiązać te problemy.
-
-## Zadawaj pytania
-
-Możesz utworzyć nowe pytanie w repozytorium na GitHubie, na przykład aby:
-
-* Zadać **pytanie** lub zapytać o **problem**.
-* Zaproponować nową **funkcję**.
-
-**Uwaga**: jeśli to zrobisz, poproszę Cię również o pomoc innym. 😉
-
-## Przeglądaj Pull Requesty
-
-Możesz pomóc mi w przeglądaniu pull requestów autorstwa innych osób.
-
-Jak wcześniej wspomniałem, postaraj się być jak najbardziej życzliwy. 🤗
-
----
-
-Oto, co warto mieć na uwadze podczas oceny pull requestu:
-
-### Zrozum problem
-
-* Najpierw upewnij się, że **rozumiesz problem**, który próbuje rozwiązać pull request. Może być osadzony w większym kontekście w GitHubowej dyskusji lub problemie.
-
-* Jest też duża szansa, że pull request nie jest konieczny, ponieważ problem można rozwiązać w **inny sposób**. Wtedy możesz to zasugerować lub o to zapytać.
-
-### Nie martw się stylem
-
-* Nie przejmuj się zbytnio rzeczami takimi jak style wiadomości commitów, przy wcielaniu pull requesta łączę commity i modyfikuję opis sumarycznego commita ręcznie.
-
-* Nie przejmuj się również stylem kodu, automatyczne narzędzia w repozytorium sprawdzają to samodzielnie.
-
-A jeśli istnieje jakaś konkretna potrzeba dotycząca stylu lub spójności, sam poproszę o zmiany lub dodam commity z takimi zmianami.
-
-### Sprawdź kod
-
-* Przeczytaj kod, zastanów się czy ma sens, **uruchom go lokalnie** i potwierdź czy faktycznie rozwiązuje problem.
-
-* Następnie dodaj **komentarz** z informacją o tym, że sprawdziłeś kod, dzięki temu będę miał pewność, że faktycznie go sprawdziłeś.
-
-/// info
-
-Niestety, nie mogę ślepo ufać PR-om, nawet jeśli mają kilka zatwierdzeń.
-
-Kilka razy zdarzyło się, że PR-y miały 3, 5 lub więcej zatwierdzeń (prawdopodobnie dlatego, że opis obiecuje rozwiązanie ważnego problemu), ale gdy sam sprawdziłem danego PR-a, okazał się być zbugowany lub nie rozwiązywał problemu, który rzekomo miał rozwiązywać. 😅
-
-Dlatego tak ważne jest, abyś faktycznie przeczytał i uruchomił kod oraz napisał w komentarzu, że to zrobiłeś. 🤓
-
-///
-
-* Jeśli PR można uprościć w jakiś sposób, możesz o to poprosić, ale nie ma potrzeby być zbyt wybrednym, może być wiele subiektywnych punktów widzenia (a ja też będę miał swój 🙈), więc lepiej żebyś skupił się na kluczowych rzeczach.
-
-### Testy
-
-* Pomóż mi sprawdzić, czy PR ma **testy**.
-
-* Sprawdź, czy testy **nie przechodzą** przed PR. 🚨
-
-* Następnie sprawdź, czy testy **przechodzą** po PR. ✅
-
-* Wiele PR-ów nie ma testów, możesz **przypomnieć** im o dodaniu testów, a nawet **zaproponować** samemu jakieś testy. To jedna z rzeczy, które pochłaniają najwięcej czasu i możesz w tym bardzo pomóc.
-
-* Następnie skomentuj również to, czego spróbowałeś, wtedy będę wiedział, że to sprawdziłeś. 🤓
-
-## Utwórz Pull Request
-
-Możesz [wnieść wkład](contributing.md){.internal-link target=_blank} do kodu źródłowego za pomocą Pull Requestu, na przykład:
-
-* Naprawić literówkę, którą znalazłeś w dokumentacji.
-* Podzielić się artykułem, filmem lub podcastem, który stworzyłeś lub znalazłeś na temat FastAPI, edytując ten plik.
- * Upewnij się, że dodajesz swój link na początku odpowiedniej sekcji.
-* Pomóc w [tłumaczeniu dokumentacji](contributing.md#translations){.internal-link target=_blank} na Twój język.
- * Możesz również pomóc w weryfikacji tłumaczeń stworzonych przez innych.
-* Zaproponować nowe sekcje dokumentacji.
-* Naprawić istniejący problem/błąd.
- * Upewnij się, że dodajesz testy.
-* Dodać nową funkcję.
- * Upewnij się, że dodajesz testy.
- * Upewnij się, że dodajesz dokumentację, jeśli jest to istotne.
-
-## Pomóż w utrzymaniu FastAPI
-
-Pomóż mi utrzymać **FastAPI**! 🤓
-
-Jest wiele pracy do zrobienia, a w większości przypadków **TY** możesz to zrobić.
-
-Główne zadania, które możesz wykonać teraz to:
-
-* [Pomóc innym z pytaniami na GitHubie](#pomagaj-innym-odpowiadajac-na-ich-pytania-na-githubie){.internal-link target=_blank} (zobacz sekcję powyżej).
-* [Oceniać Pull Requesty](#przegladaj-pull-requesty){.internal-link target=_blank} (zobacz sekcję powyżej).
-
-Te dwie czynności **zajmują najwięcej czasu**. To główna praca związana z utrzymaniem FastAPI.
-
-Jeśli możesz mi w tym pomóc, **pomożesz mi utrzymać FastAPI** i zapewnisz że będzie **rozwijać się szybciej i lepiej**. 🚀
-
-## Dołącz do czatu
-
-Dołącz do 👥 serwera czatu na Discordzie 👥 i spędzaj czas z innymi w społeczności FastAPI.
-
-/// tip | Wskazówka
-
-Jeśli masz pytania, zadaj je w Dyskusjach na GitHubie, jest dużo większa szansa, że otrzymasz pomoc od [Ekspertów FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}.
-
-Używaj czatu tylko do innych ogólnych rozmów.
-
-///
-
-### Nie zadawaj pytań na czacie
-
-Miej na uwadze, że ponieważ czaty pozwalają na bardziej "swobodną rozmowę", łatwo jest zadawać pytania, które są zbyt ogólne i trudniejsze do odpowiedzi, więc możesz nie otrzymać odpowiedzi.
-
-Na GitHubie szablon poprowadzi Cię do napisania odpowiedniego pytania, dzięki czemu łatwiej uzyskasz dobrą odpowiedź, a nawet rozwiążesz problem samodzielnie, zanim zapytasz. Ponadto na GitHubie mogę się upewnić, że zawsze odpowiadam na wszystko, nawet jeśli zajmuje to trochę czasu. Osobiście nie mogę tego zrobić z systemami czatu. 😅
-
-Rozmów w systemach czatu nie można tak łatwo przeszukiwać, jak na GitHubie, więc pytania i odpowiedzi mogą zaginąć w rozmowie. A tylko te na GitHubie liczą się do zostania [Ekspertem FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}, więc najprawdopodobniej otrzymasz więcej uwagi na GitHubie.
-
-Z drugiej strony w systemach czatu są tysiące użytkowników, więc jest duża szansa, że znajdziesz tam kogoś do rozmowy, prawie w każdej chwili. 😄
-
-## Wspieraj autora
-
-Możesz również finansowo wesprzeć autora (mnie) poprzez sponsoring na GitHubie.
-
-Tam możesz postawić mi kawę ☕️ aby podziękować. 😄
-
-Możesz także zostać srebrnym lub złotym sponsorem FastAPI. 🏅🎉
-
-## Wspieraj narzędzia, które napędzają FastAPI
-
-Jak widziałeś w dokumentacji, FastAPI stoi na ramionach gigantów, Starlette i Pydantic.
-
-Możesz również wesprzeć:
-
-* Samuel Colvin (Pydantic)
-* Encode (Starlette, Uvicorn)
-
----
-
-Dziękuję! 🚀
diff --git a/docs/pl/docs/index.md b/docs/pl/docs/index.md
deleted file mode 100644
index 976d610d4..000000000
--- a/docs/pl/docs/index.md
+++ /dev/null
@@ -1,467 +0,0 @@
-# FastAPI
-
-
-
-
-- FastAPI to szybki, prosty w nauce i gotowy do użycia w produkcji framework -
- - ---- - -**Dokumentacja**: https://fastapi.tiangolo.com - -**Kod żródłowy**: https://github.com/fastapi/fastapi - ---- - -FastAPI to nowoczesny, wydajny framework webowy do budowania API z użyciem Pythona bazujący na standardowym typowaniu Pythona. - -Kluczowe cechy: - -* **Wydajność**: FastAPI jest bardzo wydajny, na równi z **NodeJS** oraz **Go** (dzięki Starlette i Pydantic). [Jeden z najszybszych dostępnych frameworków Pythonowych](#wydajnosc). -* **Szybkość kodowania**: Przyśpiesza szybkość pisania nowych funkcjonalności o około 200% do 300%. * -* **Mniejsza ilość błędów**: Zmniejsza ilość ludzkich (dewelopera) błędy o około 40%. * -* **Intuicyjność**: Wspaniałe wsparcie dla edytorów kodu. Dostępne wszędzie automatyczne uzupełnianie kodu. Krótszy czas debugowania. -* **Łatwość**: Zaprojektowany by być prosty i łatwy do nauczenia. Mniej czasu spędzonego na czytanie dokumentacji. -* **Kompaktowość**: Minimalizacja powtarzającego się kodu. Wiele funkcjonalności dla każdej deklaracji parametru. Mniej błędów. -* **Solidność**: Kod gotowy dla środowiska produkcyjnego. Wraz z automatyczną interaktywną dokumentacją. -* **Bazujący na standardach**: Oparty na (i w pełni kompatybilny z) otwartych standardach API: OpenAPI (wcześniej znane jako Swagger) oraz JSON Schema. - -* oszacowania bazowane na testach wykonanych przez wewnętrzny zespół deweloperów, budujących aplikacie używane na środowisku produkcyjnym. - -## Sponsorzy - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} -async def...uvicorn main:app --reload...email-validator - dla walidacji adresów email.
-
-Używane przez Starlette:
-
-* httpx - Wymagane jeżeli chcesz korzystać z `TestClient`.
-* aiofiles - Wymagane jeżeli chcesz korzystać z `FileResponse` albo `StaticFiles`.
-* jinja2 - Wymagane jeżeli chcesz używać domyślnej konfiguracji szablonów.
-* python-multipart - Wymagane jeżelich chcesz wsparcie "parsowania" formularzy, używając `request.form()`.
-* itsdangerous - Wymagany dla wsparcia `SessionMiddleware`.
-* pyyaml - Wymagane dla wsparcia `SchemaGenerator` z Starlette (z FastAPI prawdopodobnie tego nie potrzebujesz).
-* graphene - Wymagane dla wsparcia `GraphQLApp`.
-
-Używane przez FastAPI / Starlette:
-
-* uvicorn - jako serwer, który ładuje i obsługuje Twoją aplikację.
-* orjson - Wymagane jeżeli chcesz używać `ORJSONResponse`.
-* ujson - Wymagane jeżeli chcesz korzystać z `UJSONResponse`.
-
-Możesz zainstalować wszystkie te aplikacje przy pomocy `pip install fastapi[all]`.
-
-## Licencja
-
-Ten projekt jest na licencji MIT.
diff --git a/docs/pl/docs/tutorial/first-steps.md b/docs/pl/docs/tutorial/first-steps.md
deleted file mode 100644
index 8fa4c75ad..000000000
--- a/docs/pl/docs/tutorial/first-steps.md
+++ /dev/null
@@ -1,335 +0,0 @@
-# Pierwsze kroki
-
-Najprostszy plik FastAPI może wyglądać tak:
-
-{* ../../docs_src/first_steps/tutorial001.py *}
-
-Skopiuj to do pliku `main.py`.
-
-Uruchom serwer:
-
-get
-
-/// info | `@decorator` Info
-
-Składnia `@something` jest w Pythonie nazywana "dekoratorem".
-
-Umieszczasz to na szczycie funkcji. Jak ładną ozdobną czapkę (chyba stąd wzięła się nazwa).
-
-"Dekorator" przyjmuje funkcję znajdującą się poniżej jego i coś z nią robi.
-
-W naszym przypadku dekorator mówi **FastAPI**, że poniższa funkcja odpowiada **ścieżce** `/` z **operacją** `get`.
-
-Jest to "**dekorator operacji na ścieżce**".
-
-///
-
-Możesz również użyć innej operacji:
-
-* `@app.post()`
-* `@app.put()`
-* `@app.delete()`
-
-Oraz tych bardziej egzotycznych:
-
-* `@app.options()`
-* `@app.head()`
-* `@app.patch()`
-* `@app.trace()`
-
-/// tip
-
-Możesz dowolnie używać każdej operacji (metody HTTP).
-
-**FastAPI** nie narzuca żadnego konkretnego znaczenia.
-
-Informacje tutaj są przedstawione jako wskazówka, a nie wymóg.
-
-Na przykład, używając GraphQL, normalnie wykonujesz wszystkie akcje używając tylko operacji `POST`.
-
-///
-
-### Krok 4: zdefiniuj **funkcję obsługującą ścieżkę**
-
-To jest nasza "**funkcja obsługująca ścieżkę**":
-
-* **ścieżka**: to `/`.
-* **operacja**: to `get`.
-* **funkcja**: to funkcja poniżej "dekoratora" (poniżej `@app.get("/")`).
-
-{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
-
-Jest to funkcja Python.
-
-Zostanie ona wywołana przez **FastAPI** za każdym razem, gdy otrzyma żądanie do adresu URL "`/`" przy użyciu operacji `GET`.
-
-W tym przypadku jest to funkcja "asynchroniczna".
-
----
-
-Możesz również zdefiniować to jako normalną funkcję zamiast `async def`:
-
-{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
-
-/// note
-
-Jeśli nie znasz różnicy, sprawdź [Async: *"In a hurry?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
-
-///
-
-### Krok 5: zwróć zawartość
-
-{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
-
-Możesz zwrócić `dict`, `list`, pojedynczą wartość jako `str`, `int`, itp.
-
-Możesz również zwrócić modele Pydantic (więcej o tym później).
-
-Istnieje wiele innych obiektów i modeli, które zostaną automatycznie skonwertowane do formatu JSON (w tym ORM itp.). Spróbuj użyć swoich ulubionych, jest bardzo prawdopodobne, że są już obsługiwane.
-
-## Podsumowanie
-
-* Zaimportuj `FastAPI`.
-* Stwórz instancję `app`.
-* Dodaj **dekorator operacji na ścieżce** (taki jak `@app.get("/")`).
-* Napisz **funkcję obsługującą ścieżkę** (taką jak `def root(): ...` powyżej).
-* Uruchom serwer deweloperski (`uvicorn main:app --reload`).
diff --git a/docs/pl/docs/tutorial/index.md b/docs/pl/docs/tutorial/index.md
deleted file mode 100644
index 66f7c6d62..000000000
--- a/docs/pl/docs/tutorial/index.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# Samouczek
-
-Ten samouczek pokaże Ci, krok po kroku, jak używać większości funkcji **FastAPI**.
-
-Każda część korzysta z poprzednich, ale jest jednocześnie osobnym tematem. Możesz przejść bezpośrednio do każdego rozdziału, jeśli szukasz rozwiązania konkretnego problemu.
-
-Samouczek jest tak zbudowany, żeby służył jako punkt odniesienia w przyszłości.
-
-Możesz wracać i sprawdzać dokładnie to czego potrzebujesz.
-
-## Wykonywanie kodu
-
-Wszystkie fragmenty kodu mogą być skopiowane bezpośrednio i użyte (są poprawnymi i przetestowanymi plikami).
-
-Żeby wykonać każdy przykład skopiuj kod to pliku `main.py` i uruchom `uvicorn` za pomocą:
-
-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ção
+
+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 webapp - 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ção
+
+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ção
+
+Esta é uma lista não completa e não normativa de termos (principalmente) técnicos vistos na documentação. Pode ser útil para o designer 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 1f42e8831..39e160741 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 1060d18af..688bc1696 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 06d619151..fd90b1795 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 f57abba61..1ad9ea617 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 a2b79426c..953ebedd9 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 504b6db57..8cdc35828 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 dc6b29511..5134bc7cb 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
+
+O FastAPI gera automaticamente especificações **OpenAPI 3.1**, então qualquer ferramenta que você usar deve suportar essa versão.
+
+///
+
+## 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.
-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**.
+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**.
-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. 🙇
+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
-
-Agora que temos o app com os modelos, podemos gerar o código do cliente para o frontend.
-
-#### 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
-
-Agora, como o resultado final está em um arquivo `openapi.json`, você modificaria o `package.json` para usar esse arquivo local, por exemplo:
-
-```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"
- }
-}
+### Gere um cliente TypeScript com o OpenAPI pré-processado { #generate-a-typescript-client-with-the-preprocessed-openapi }
+
+Como o resultado final está agora em um arquivo `openapi.json`, você precisa atualizar o local de entrada:
+
+```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 22ba2bf4a..23e551074 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 8167f7d27..30c183479 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.
@@ -93,4 +94,4 @@ Por exemplo:
* Uvicorn's `ProxyHeadersMiddleware`
* MessagePack
-Para checar outros middlewares disponíveis, confira Documentação de Middlewares do Starlette e a Lista Incrível do ASGI.
+Para checar outros middlewares disponíveis, confira Documentação de Middlewares do Starlette e a Lista Incrível do ASGI.
diff --git a/docs/pt/docs/advanced/openapi-callbacks.md b/docs/pt/docs/advanced/openapi-callbacks.md
index b0659d3d6..57c8c5e81 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 f35922234..011898e8c 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 411d0f9a7..b3af116a2 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.
@@ -10,25 +10,25 @@ Se você não é um "especialista" no OpenAPI, você provavelmente não precisa
Você pode definir o `operationId` do OpenAPI que será utilizado na sua *operação de rota* com o parâmetro `operation_id`.
-Você precisa ter certeza que ele é único para cada operação.
+Você deveria 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*.
+Você deveria 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
-Se você chamar `app.openapi()` manualmente, os `operationId`s devem ser atualizados antes dessa chamada.
+Se você chamar `app.openapi()` manualmente, você deveria atualizar os `operationId`s antes dessa chamada.
///
-/// 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,23 +36,23 @@ 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.
+Você pode limitar as linhas utilizadas a partir da docstring de uma *função de operação de rota* para o OpenAPI.
-Adicionar um `\f` (um caractere de escape para alimentação de formulário) faz com que o **FastAPI** restrinja a saída utilizada pelo OpenAPI até esse ponto.
+Adicionar um `\f` (um caractere de escape para "form feed") faz com que o **FastAPI** trunque a saída usada para o OpenAPI até esse ponto.
-Ele não será mostrado na documentação, mas outras ferramentas (como o Sphinx) serão capazes de utilizar o resto do texto.
+Ele não será mostrado na documentação, mas outras ferramentas (como o Sphinx) serão capazes de utilizar o resto.
-{* ../../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,73 +129,41 @@ 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*.
+O dicionário em `openapi_extra` vai ser mesclado profundamente com o esquema OpenAPI gerado automaticamente para a *operação de rota*.
-Então, você pode adicionar dados extras para o esquema gerado automaticamente.
+Então, você pode adicionar dados extras ao esquema gerado automaticamente.
-Por exemplo, você poderia optar por ler e validar a requisição com seu próprio código, sem utilizar funcionalidades automatizadas do FastAPI com o Pydantic, mas você ainda pode quere definir a requisição no esquema OpenAPI.
+Por exemplo, você poderia decidir ler e validar a requisição com seu próprio código, sem usar as funcionalidades automáticas do FastAPI com o Pydantic, mas ainda assim querer definir a requisição no esquema OpenAPI.
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.
+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 usar 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.
+E você pode fazer isso até mesmo quando o tipo de dados na requisição não é 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 para JSON. Na verdade, estamos declarando o tipo de conteúdo da requisição como YAML, em vez de 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] *}
+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 em 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 payload da requisição como JSON.
-//// tab | Pydantic v1
+E então no nosso código, nós analisamos o conteúdo YAML diretamente e, em seguida, estamos usando novamente o mesmo modelo Pydantic para validar o conteúdo YAML:
-{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22,24] *}
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
-////
-
-/// 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()`
-
-///
-
-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.
-
-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.
-
-E então no nosso código, nós analisamos o conteúdo YAML diretamente, e estamos utilizando o mesmo modelo Pydantic para validar o conteúdo 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 | Informação
-
-Na versão 1 do Pydantic, o método para analisar e validar um objeto era `Item.parse_obj()`, na versão 2 do Pydantic, o método é chamado de `Item.model_validate()`.
-
-///
-
-///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 358c12d54..ee81f0bfc 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 eed69f222..67820b433 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
@@ -48,4 +48,4 @@ E como o `Response` pode ser usado frequentemente para definir cabeçalhos e coo
///
-Para ver todos os parâmetros e opções disponíveis, verifique a documentação no Starlette.
+Para ver todos os parâmetros e opções disponíveis, verifique a documentação no Starlette.
diff --git a/docs/pt/docs/advanced/response-directly.md b/docs/pt/docs/advanced/response-directly.md
index ea717be00..bbbef2f91 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 a8034a7a4..14c3fb186 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.
+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 331513927..bd572217b 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 6c7becb67..70fb999d0 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 07b345945..591ac9b4a 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 cdc6400ad..28411269b 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).
-
-Você pode criar e utilizar variáveis de ambiente no terminal, sem precisar utilizar Python:
+## Tipagem e validação { #types-and-validation }
-//// tab | Linux, macOS, Windows Bash
+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).
-
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 4d22bfbbf..eb64e72bb 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,15 +172,15 @@ 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:
-* A classe `WebSocket`.
-* Manipulação de WebSockets baseada em classes.
+* A classe `WebSocket`.
+* Manipulação de WebSockets baseada em classes.
diff --git a/docs/pt/docs/advanced/wsgi.md b/docs/pt/docs/advanced/wsgi.md
index a36261e5e..c3c21d430 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 29c9693bb..fd992ec95 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 é outro framework Python de alta performance, e é projetado para ser minimalista, e funciona como fundação de outros frameworks como Hug.
+### Falcon { #falcon }
-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.
+Falcon é outro framework Python de alta performance, projetado para ser minimalista, e servir como base para outros frameworks como Hug.
-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.
+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.
-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 c70924ea5..f01ff2315 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 07461ce46..c0b0c4c46 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 fc490db4d..419fd7626 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 014ca3797..6af4b177a 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 cf18bb153..b26a69b54 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 b7de6c8bd..adda9eca5 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 8f432f6fe..b4ea1c282 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.
@@ -66,7 +66,7 @@ O dicionário `scope` e a função `receive` são ambos parte da especificação
E essas duas coisas, `scope` e `receive`, são o que é necessário para criar uma nova instância de `Request`.
-Para aprender mais sobre o `Request` confira a documentação do Starlette sobre Requests.
+Para aprender mais sobre o `Request` confira a documentação do Starlette sobre Requests.
///
@@ -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 b4785edc1..b1b50ce65 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 4f21463b2..7f9146862 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 ef0bad7f6..98266cc28 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 6747b01c7..a3d16380f 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 000000000..0995e1028
--- /dev/null
+++ b/docs/pt/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
@@ -0,0 +1,135 @@
+# 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 versão 0.100.0 tinha suporte ao Pydantic v1 ou v2. Ele usaria aquele que você tivesse instalado.
+
+O FastAPI versão 0.119.0 introduziu suporte parcial ao Pydantic v1 a partir de dentro do Pydantic v2 (como `pydantic.v1`), para facilitar a migração para o v2.
+
+O FastAPI 0.126.0 removeu o suporte ao Pydantic v1, enquanto ainda oferece suporte a `pydantic.v1` por mais algum tempo.
+
+/// warning | Atenção
+
+A equipe do Pydantic interrompeu o suporte ao Pydantic v1 para as versões mais recentes do Python, a partir do **Python 3.14**.
+
+Isso inclui `pydantic.v1`, que não é mais suportado no Python 3.14 e superiores.
+
+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 **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`. Mas isso não é mais suportado em versões acima do Python 3.13.
+
+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
+```
+
+...mas, você pode ter modelos separados usando Pydantic v1 e v2 na mesma aplicação.
+
+```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 291b0e163..f757025a0 100644
--- a/docs/pt/docs/how-to/separate-openapi-schemas.md
+++ b/docs/pt/docs/how-to/separate-openapi-schemas.md
@@ -1,26 +1,26 @@
-# 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. 😎
+Desde que o **Pydantic v2** foi lançado, o OpenAPI gerado é um pouco mais exato e **correto** do que antes. 😎
-Inclusive, em alguns casos, ele terá até **dois JSON Schemas** no OpenAPI para o mesmo modelo Pydantic, para entrada e saída, dependendo se eles possuem **valores padrão**.
+De fato, em alguns casos, ele terá até **dois JSON Schemas** no OpenAPI para o mesmo modelo Pydantic, para entrada e saída, dependendo se eles possuem **valores padrão**.
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,34 +40,40 @@ 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.
-* **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.
+* **Intuitivo**: Grande suporte a editores. Completação em todos os lugares. Menos tempo debugando.
+* **Fácil**: Projetado para ser fácil de aprender e usar. Menos tempo lendo docs.
+* **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**.*"
+"_[...] Estou usando **FastAPI** muito esses dias. [...] Estou na verdade planejando utilizar ele em todos os times de **serviços ML na Microsoft**. Alguns deles estão sendo integrados no _core_ do produto **Windows** e alguns produtos **Office**._"
+
+## **Typer**, o FastAPI das interfaces de linhas de comando { #typer-the-fastapi-of-clis }
fastapi dev main.py...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`.
+* 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[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.
Dependências opcionais adicionais do Pydantic:
* pydantic-settings - para gerenciamento de configurações.
-* pydantic-extra-types - tipos extras para serem utilizados com o Pydantic.
+* pydantic-extra-types - para tipos extras a serem utilizados com o Pydantic.
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 b9a7f5972..1f04929f7 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 e337ad762..419a39879 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 90a361f40..fc983d1df 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 uma variável com a mesma sintaxe com dois pontos (`:`)
-
-Como tipo, coloque `list`.
-
-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!}
-```
-
-////
+Declare a variável, com a mesma sintaxe com dois pontos (`:`).
-//// tab | Python 3.8+
+Como o tipo, coloque `list`.
-De `typing`, importe `List` (com o `L` maiúsculo):
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
+Como a lista é um tipo que contém tipos internos, você os coloca entre colchetes:
-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`:
-
-* `Union`
-* `Optional`
-* ...entro outros.
-
-////
-
-//// tab | Python 3.8+
+E genéricos do módulo `typing`:
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
* `Union`
* `Optional`
-* ...entro outros.
+* ...entre 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:
-
-//// 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!}
-```
+Um exemplo da documentação oficial do Pydantic:
-////
+{* ../../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+
-
-No Python 3.9, `Annotated` é parte da biblioteca padrão, então você pode importá-lo de `typing`.
+Desde o Python 3.9, `Annotated` faz 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 6eff8f9e7..24cea9564 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 0f3796371..34805364b 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`.
-A classe `BackgroundTasks` vem diretamente de `starlette.background`.
+## Detalhes técnicos { #technical-details }
-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`.
+A classe `BackgroundTasks` vem diretamente 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.
+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`.
-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.
+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.
-Você pode ver mais detalhes na documentação oficiais da Starlette para tarefas em segundo plano .
+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.
-## Ressalva
+Você pode ver mais detalhes na documentação oficial da Starlette para tarefas em segundo plano.
-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 .
+## Ressalva { #caveat }
-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.
+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.
-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`.
+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.
-## Recapitulando
+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`.
-Importe e use `BackgroundTasks` com parâmetros em _funções de operação de caminho_ e dependências para adicionar tarefas em segundo plano.
+## Recapitulando { #recap }
+
+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 b621f3c72..87bd13375 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:
@@ -31,7 +31,7 @@ Digamos que você tenha uma estrutura de arquivos como esta:
/// tip | Dica
-Existem vários arquivos `__init__.py` presentes em cada diretório ou subdiretório.
+Existem vários arquivos `__init__.py`: um em cada diretório ou subdiretório.
Isso permite a importação de código de um arquivo para outro.
@@ -43,35 +43,35 @@ from app.routers import items
///
-* O diretório `app` contém todo o código da aplicação. Ele possui um arquivo `app/__init__.py` vazio, o que o torna um "pacote Python" (uma coleção de "módulos Python"): `app`.
-* Dentro dele, o arquivo `app/main.py` está localizado em um pacote Python (diretório com `__init__.py`). Portanto, ele é um "módulo" desse pacote: `app.main`.
-* Existem também um arquivo `app/dependencies.py`, assim como o `app/main.py`, ele é um "módulo": `app.dependencies`.
+* O diretório `app` contém tudo. E possui um arquivo vazio `app/__init__.py`, então ele é um "pacote Python" (uma coleção de "módulos Python"): `app`.
+* Ele contém um arquivo `app/main.py`. Como está dentro de um pacote Python (um diretório com um arquivo `__init__.py`), ele é um "módulo" desse pacote: `app.main`.
+* Existe também um arquivo `app/dependencies.py`, assim como `app/main.py`, ele é um "módulo": `app.dependencies`.
* Há um subdiretório `app/routers/` com outro arquivo `__init__.py`, então ele é um "subpacote Python": `app.routers`.
-* O arquivo `app/routers/items.py` está dentro de um pacote, `app/routers/`, portanto, é um "submódulo": `app.routers.items`.
-* O mesmo com `app/routers/users.py`, ele é outro submódulo: `app.routers.users`.
-* Há também um subdiretório `app/internal/` com outro arquivo `__init__.py`, então ele é outro "subpacote Python":`app.internal`.
+* O arquivo `app/routers/items.py` está dentro de um pacote, `app/routers/`, portanto é um submódulo: `app.routers.items`.
+* O mesmo com `app/routers/users.py`, ele é outro submódulo: `app.routers.users`.
+* Há também um subdiretório `app/internal/` com outro arquivo `__init__.py`, então ele é outro "subpacote Python": `app.internal`.
* E o arquivo `app/internal/admin.py` é outro submódulo: `app.internal.admin`.
-## Incluir o mesmo roteador várias vezes com `prefixos` diferentes
+## Inclua o mesmo router 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.
+Você também pode usar `.include_router()` várias vezes com o *mesmo* router usando prefixos diferentes.
Isso pode ser útil, por exemplo, para expor a mesma API sob prefixos diferentes, por exemplo, `/api/v1` e `/api/latest`.
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:
+Da mesma forma que você pode incluir um `APIRouter` em uma aplicação `FastAPI`, você pode incluir um `APIRouter` em outro `APIRouter` usando:
```Python
router.include_router(other_router)
```
-Certifique-se de fazer isso antes de incluir `router` no aplicativo `FastAPI`, para que as *operações de rota* de `other_router` também sejam incluídas.
+Certifique-se de fazer isso antes de incluir `router` na aplicação `FastAPI`, para que as *operações de rota* de `other_router` também sejam incluídas.
diff --git a/docs/pt/docs/tutorial/body-fields.md b/docs/pt/docs/tutorial/body-fields.md
index e7dfb07f2..25e11189e 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 eda9b4dff..3cba04912 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 2954ae3db..f2bec19a2 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 bf38aeb9e..95f89c8d2 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 substituindo 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:
@@ -22,13 +22,13 @@ Isso significa que, se você quiser atualizar o item `bar` usando `PUT` com um c
}
```
-Como ele não inclui o atributo já armazenado `"tax": 20.2`, o modelo de entrada assumiria o valor padrão de `"tax": 10.5`.
+como ele não inclui o atributo já armazenado `"tax": 20.2`, o modelo de entrada assumiria o valor padrão de `"tax": 10.5`.
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 dados *parcialmente*.
Isso significa que você pode enviar apenas os dados que deseja atualizar, deixando o restante intacto.
@@ -40,59 +40,43 @@ E muitas equipes usam apenas `PUT`, mesmo para atualizações parciais.
Você é **livre** para usá-los como preferir, **FastAPI** não impõe restrições.
-Mas este guia te dá uma ideia de como eles são destinados a serem usados.
+Mas este guia mostra, mais ou menos, 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.
+Se você quiser receber atualizações parciais, é muito útil usar o parâmetro `exclude_unset` no `.model_dump()` do modelo do Pydantic.
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()`.
-
-Os exemplos aqui usam `.dict()` para compatibilidade com o Pydantic v1, mas você deve usar `.model_dump()` a partir do Pydantic v2.
-
-///
-
-Isso gera um `dict` com apenas os dados definidos ao criar o modelo `item`, excluindo os valores padrão.
+Isso geraria um `dict` com apenas os dados que foram definidos ao criar o modelo `item`, excluindo os valores padrão.
Então, você pode usar isso para gerar um `dict` com apenas os dados definidos (enviados na solicitação), omitindo valores padrão:
{* ../../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()`.
-
-Os exemplos aqui usam `.copy()` para compatibilidade com o Pydantic v1, mas você deve usar `.model_copy()` com o Pydantic v2.
-
-///
-
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:
+Resumindo, para aplicar atualizações parciais você deveria:
* (Opcionalmente) usar `PATCH` em vez de `PUT`.
* Recuperar os dados armazenados.
* Colocar esses dados em um modelo do Pydantic.
* Gerar um `dict` sem valores padrão a partir do modelo de entrada (usando `exclude_unset`).
- * Dessa forma, você pode atualizar apenas os valores definidos pelo usuário, em vez de substituir os valores já armazenados com valores padrão em seu modelo.
+ * Dessa forma, você pode atualizar apenas os valores realmente definidos pelo usuário, em vez de substituir valores já armazenados por valores padrão do modelo.
* Criar uma cópia do modelo armazenado, atualizando seus atributos com as atualizações parciais recebidas (usando o parâmetro `update`).
-* Converter o modelo copiado em algo que possa ser armazenado no seu banco de dados (por exemplo, usando o `jsonable_encoder`).
- * Isso é comparável ao uso do método `.model_dump()`, mas garante (e converte) os valores para tipos de dados que possam ser convertidos em JSON, por exemplo, `datetime` para `str`.
-* Salvar os dados no seu banco de dados.
+* Converter o modelo copiado em algo que possa ser armazenado no seu BD (por exemplo, usando o `jsonable_encoder`).
+ * Isso é comparável a usar o método `.model_dump()` do modelo novamente, mas garante (e converte) os valores para tipos de dados que possam ser convertidos em JSON, por exemplo, `datetime` para `str`.
+* Salvar os dados no seu BD.
* Retornar o modelo atualizado.
{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *}
@@ -109,8 +93,8 @@ Mas o exemplo aqui usa `PATCH` porque foi criado para esses casos de uso.
Observe que o modelo de entrada ainda é validado.
-Portanto, se você quiser receber atualizações parciais que possam omitir todos os atributos, precisará ter um modelo com todos os atributos marcados como opcionais (com valores padrão ou `None`).
+Portanto, se você quiser receber atualizações parciais que possam omitir todos os atributos, você precisa ter um modelo com todos os atributos marcados como opcionais (com valores padrão ou `None`).
-Para distinguir os modelos com todos os valores opcionais para **atualizações** e modelos com valores obrigatórios para **criação**, você pode usar as ideias descritas em [Modelos Adicionais](extra-models.md){.internal-link target=_blank}.
+Para distinguir entre os modelos com todos os valores opcionais para **atualizações** e modelos com valores obrigatórios para **criação**, você pode usar as ideias descritas em [Modelos Adicionais](extra-models.md){.internal-link target=_blank}.
///
diff --git a/docs/pt/docs/tutorial/body.md b/docs/pt/docs/tutorial/body.md
index 2508d7981..669334439 100644
--- a/docs/pt/docs/tutorial/body.md
+++ b/docs/pt/docs/tutorial/body.md
@@ -1,51 +1,52 @@
-# 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ê deveria 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.
-Como é desencorajado, a documentação interativa com Swagger UI não irá mostrar a documentação para o corpo da requisição para um `GET`, e proxies que intermediarem podem não suportar o corpo da requisição.
+Como é desencorajado, a documentação interativa com Swagger UI não irá mostrar a documentação para o corpo da requisição ao usar `GET`, e proxies intermediários podem não suportá-lo.
///
-## 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.
+
+Assim como quando declaramos parâmetros de consulta, quando um atributo do modelo possui um valor padrão, ele não é obrigatório. Caso contrário, é obrigatório. Use `None` para torná-lo apenas opcional.
Por exemplo, o modelo acima declara um JSON "`object`" (ou `dict` no Python) como esse:
```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 +55,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.
+* Ler o corpo da requisição como 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 +112,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 +122,45 @@ 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
+## 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_py310.py hl[15:16] *}
-{* ../../docs_src/body/tutorial003.py hl[17:18] *}
-## 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 3d46ba44c..73c94050f 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 da85d796e..5540a67d2 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 0ab07a3c2..0f99db888 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 67b764457..e39c7d128 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 d7d31bb45..ee8a58dc2 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 eaf711197..367873013 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+
-
-```python hl_lines="18-22 31"
-{!> ../../docs_src/dependencies/tutorial008b_an_py39.py!}
-```
-
-////
-
-//// 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!}
-```
+{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *}
-////
+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}.
-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` { #dependencies-with-yield-and-except }
-## 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
-
-/// warning | Aviso
+## Saída antecipada e `scope` { #early-exit-and-scope }
-Você provavelmente não precisa desses detalhes técnicos, você pode pular essa seção e continuar na próxima seção abaixo.
+Normalmente, o código de saída das dependências com `yield` é executado **após a resposta** ser enviada ao cliente.
-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.
+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**.
-///
+{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *}
-### Dependências com `yield` e `except`, Detalhes Técnicos
+`Depends()` recebe um parâmetro `scope` que pode ser:
-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.
+* `"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.
-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.
+Se não for especificado e a dependência tiver `yield`, ela terá `scope` igual a `"request"` por padrão.
-### Tarefas de Background e Dependências com `yield`, Detalhes Técnicos
+### `scope` para subdependências { #scope-for-sub-dependencies }
-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.
+Quando você declara uma dependência com `scope="request"` (o padrão), qualquer subdependência também precisa ter `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.
+Mas uma dependência com `scope` igual a `"function"` pode ter dependências com `scope` igual a `"function"` e com `scope` igual a `"request"`.
-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.
+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.
-/// tip | Dica
-
-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).
+```mermaid
+sequenceDiagram
-Então, dessa forma você provavelmente terá um código mais limpo.
+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
-///
+ 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
+```
-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`.
+## Dependências com `yield`, `HTTPException`, `except` e Tarefas de Background { #dependencies-with-yield-httpexception-except-and-background-tasks }
-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.
+Dependências com `yield` evoluíram ao longo do tempo para cobrir diferentes casos de uso e corrigir alguns problemas.
-## Gerenciadores de contexto
+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 }
-### 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 a9a7e3b89..4d6e8a69a 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 1500b715a..bdfe1ac39 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 3975ce182..e2dc9bbf9 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 09c838be0..97e4cc475 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 cccef16e3..24eafce01 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,19 +16,19 @@ 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()`
+### Sobre `**user_in.model_dump()` { #about-user-in-model-dump }
-#### O `.dict()` do Pydantic
+#### O `.model_dump()` do Pydantic { #pydantics-model-dump }
`user_in` é um modelo Pydantic da classe `UserIn`.
-Os modelos Pydantic possuem um método `.dict()` que retorna um `dict` com os dados do modelo.
+Os modelos Pydantic possuem um método `.model_dump()` que retorna um `dict` com os dados do modelo.
Então, se criarmos um objeto Pydantic `user_in` como:
@@ -39,7 +39,7 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com
e depois chamarmos:
```Python
-user_dict = user_in.dict()
+user_dict = user_in.model_dump()
```
agora temos um `dict` com os dados na variável `user_dict` (é um `dict` em vez de um objeto de modelo Pydantic).
@@ -61,7 +61,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,31 +93,31 @@ 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:
+Como no exemplo acima, obtivemos o `user_dict` a partir do `user_in.model_dump()`, este código:
```Python
-user_dict = user_in.dict()
+user_dict = user_in.model_dump()
UserInDB(**user_dict)
```
seria equivalente a:
```Python
-UserInDB(**user_in.dict())
+UserInDB(**user_in.model_dump())
```
-...porque `user_in.dict()` é um `dict`, e depois fazemos o Python "desembrulhá-lo" passando-o para UserInDB precedido por `**`.
+...porque `user_in.model_dump()` é 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:
```Python
-UserInDB(**user_in.dict(), hashed_password=hashed_password)
+UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
```
...acaba sendo como:
@@ -132,13 +132,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 +154,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 +184,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 5184d2d5f..86cddde5d 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,35 +143,71 @@ 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 098195db7..1c162c205 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`
-
-/// 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.
+### Sobrescreva o manipulador de erro `HTTPException` { #override-the-httpexception-error-handler }
-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 9a88dbfec..046c99c29 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 32b81c646..b49c1eaa1 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.
+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 f183c9d23..84eebc128 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 3aea1188d..9f12ba38f 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] *}
+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`.
-/// note | Nota
+///
+
+## Declare metadados { #declare-metadata }
+
+Você pode declarar todos os mesmos parâmetros que em `Query`.
+
+Por exemplo, para declarar um valor de metadado `title` para o parâmetro de path `item_id` você pode digitar:
-Um parâmetro de rota é sempre obrigatório, como se fizesse parte da rota.
+{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *}
-Então, você deve declará-lo com `...` para marcá-lo como obrigatório.
+/// note | Nota
-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.
+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.
///
-## Ordene os parâmetros de acordo com sua necessidade
+## 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`.
-Suponha que você queira declarar o parâmetro de consulta `q` como uma `str` obrigatória.
+///
+
+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 em relação a este parâmetro, então você não precisa necessariamente usar `Query`.
+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 rota `item_id`.
+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 irá acusar se você colocar um elemento com um valor padrão definido antes de outro que não tenha um valor padrão.
+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, colocando primeiro o elemento sem o valor padrão (o parâmetro de consulta `q`).
+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 definições padrão (`Query`, `Path`, etc), sem se importar com a ordem.
+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] *}
+
+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()`.
-## Ordene os parâmetros de a acordo com sua necessidade, truques
+{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
-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.
+## 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_py39.py hl[7] *}
+
+### Melhor com `Annotated` { #better-with-annotated }
+
+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 `*`.
-{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
-## Validações numéricas: maior que ou igual
+## Validações numéricas: maior que ou igual { #number-validations-greater-than-or-equal }
-Com `Query` e `Path` (e outras que você verá mais tarde) você pode declarar restrições numéricas.
+Com `Query` e `Path` (e outras que você verá depois) você pode declarar restrições numéricas.
-Aqui, com `ge=1`, `item_id` precisará ser um número inteiro maior que ("`g`reater than") ou igual ("`e`qual") a 1.
+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.py hl[8] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
-## Validações numéricas: maior que e menor que ou igual
+## Validações numéricas: maior que e menor que ou igual { #number-validations-greater-than-and-less-than-or-equal }
-O mesmo se aplica para:
+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 ecf77d676..1f47ca6e5 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.
-
-## Conversão de dados
+## Dados conversão { #data-conversion }
-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"`.
-
- Então, com essa declaração de tipo, o **FastAPI** dá pra você um "parsing" automático no request .
-
-## Validação de dados
+## Validação de dados { #data-validation }
-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.
-
- 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
+## Documentação { #documentation }
-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).
-
- Veja que o parâmetro de rota está declarado como sendo um inteiro (int).
+## Benefícios baseados em padrões, documentação alternativa { #standards-based-benefits-alternative-documentation }
-## Beneficios baseados em padrões, documentação alternativa
+E como o schema gerado é do padrão OpenAPI, existem muitas ferramentas compatíveis.
-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 rota*
+### Declare um parâmetro de path { #declare-a-path-parameter }
-Logo, crie um *parâmetro de rota* com anotações de tipo usando a classe enum que você criou (`ModelName`):
+Em seguida, crie um *parâmetro de path* com anotação de tipo usando a classe enum que você criou (`ModelName`):
-{* ../../docs_src/path_params/tutorial005.py hl[16] *}
+{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
-### Revise a documentação
+### Verifique a documentação { #check-the-docs }
-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 enumeration*
+#### Retorne membros de enumeração { #return-enumeration-members }
-Você pode retornar *membros de enum* da sua *rota de operação*, em um corpo JSON aninhado (por exemplo um `dict`).
+Você pode retornar *membros de enum* da sua *operação de rota*, até mesmo aninhados em um corpo JSON (por exemplo, um `dict`).
-Eles serão convertidos para o seus valores correspondentes (strings nesse caso) antes de serem retornados ao cliente:
+Eles serão convertidos para seus valores correspondentes (strings neste caso) antes de serem retornados ao cliente:
-{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
+{* ../../docs_src/path_params/tutorial005_py39.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 (`/`).
-
- Neste caso, a URL deveria ser: `/files//home/johndoe/myfile.txt`, com barra dupla (`//`) entre `files` e `home`.
-
-
-## Recapitulando
+## Recapitulação { #recap }
-Com o **FastAPI**, usando as declarações de tipo do Python, você obtém:
+Com o **FastAPI**, ao usar declarações de tipo do Python curtas, intuitivas e padrão, 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
+- 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
-Você apenas tem que declará-los uma vez.
+E você só precisa 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 01a6e462f..42d2604cd 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 +301,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 +312,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 a funcionalidade ausente já esteja planejada 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á deprecated.
-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`:
+
+{* ../../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
-Validações genéricas e metadados:
+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ê deveria 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ê estiver curioso sobre este exemplo de código 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`
+
+Validações personalizadas usando `AfterValidator`.
-Nesses exemplos você viu como declarar validações em valores do tipo `str`.
+Nestes exemplos você viu como declarar validações para valores `str`.
-Leia os próximos capítulos para ver como declarar validação de outros tipos, como números.
+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 8199de5af..8826602a2 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 c22c1c513..5d0891163 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 ea0e63d38..8eeffac2a 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 15c1ad825..000000000
--- 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 6726a20a7..8a7a71248 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 sua API.
+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.
-Pode ser usado pelo time de frontend (que pode ser você no caso).
+Pode ser usada pelo time de frontend (que pode ser você mesmo).
-Pode ser usado por aplicações e sistemas third party (de terceiros).
+Pode ser usada por aplicações e sistemas de terceiros.
-E também pode ser usada por você mesmo, para debugar, checar e testar a mesma aplicação.
+E também pode ser usada por você mesmo, para depurar, verificar 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.
-///
-
- Um token "bearer" não é a única opção.
+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.
- Mas é a melhor no nosso caso.
+Nesse caso, o **FastAPI** também fornece as ferramentas para construí-la.
- 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.
+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.
-{* ../../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`.
-///
-
- Esse `tokenUrl="token"` se refere a uma URL relativa que nós não criamos ainda. Como é uma URL relativa, é equivalente a `./token`.
-
- 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`.
+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}.
- 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.
+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.
-Em breve também criaremos o atual path operation.
+Em breve também criaremos a operação de rota real.
-/// 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`.
-
- 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".
+A variável `oauth2_scheme` é uma instância de `OAuth2PasswordBearer`, mas também é "chamável" (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`.
-
- 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.
-
-## O que ele faz
+## O que ele faz { #what-it-does }
-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`
+Ele irá procurar na requisição pelo header `Authorization`, verificar se o valor é `Bearer ` mais algum token e 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.
+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`).
-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 1a2badb83..2135ae236 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 2ebb87fcd..d3de3e050 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 7d80d12fa..f68b8c39e 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 1cf05785e..902ae2d22 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 3d76a532c..543e164e9 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
-## Criar o App com um Único Modelo
+## Criar o App com um Único Modelo { #create-the-app-with-a-single-model }
Vamos criar a primeira versão mais simples do app com um único modelo **SQLModel**.
Depois, vamos melhorá-lo aumentando a segurança e versatilidade com **múltiplos modelos** abaixo. 🤓
-### Criar Modelos
+### Criar Modelos { #create-models }
Importe o `SQLModel` e crie um modelo de banco de dados:
@@ -65,13 +65,13 @@ Existem algumas diferenças:
* `Field(primary_key=True)` informa ao SQLModel que o `id` é a **chave primária** no banco de dados SQL (você pode aprender mais sobre chaves primárias SQL na documentação do SQLModel).
- Ao ter o tipo como `int | None`, o SQLModel saberá que essa coluna deve ser um `INTEGER` no banco de dados SQL e que ela deve ser `NULLABLE`.
+ **Nota:** Usamos `int | None` para o campo de chave primária para que, no código Python, possamos *criar um objeto sem um `id`* (`id=None`), assumindo que o banco de dados irá *gerá-lo ao salvar*. O SQLModel entende que o banco de dados fornecerá o `id` e *define a coluna como um `INTEGER` não nulo* no esquema do banco de dados. Veja a documentação do SQLModel sobre chaves primárias para detalhes.
* `Field(index=True)` informa ao SQLModel que ele deve criar um **índice SQL** para essa coluna, o que permitirá buscas mais rápidas no banco de dados ao ler dados filtrados por essa coluna.
O SQLModel saberá que algo declarado como `str` será uma coluna SQL do tipo `TEXT` (ou `VARCHAR`, dependendo do banco de dados).
-### Criar um Engine
+### Criar um Engine { #create-an-engine }
Um `engine` SQLModel (por baixo dos panos, ele é na verdade um `engine` do SQLAlchemy) é o que **mantém as conexões** com o banco de dados.
Você teria **um único objeto `engine`** para todo o seu código se conectar ao mesmo banco de dados.
@@ -82,13 +82,13 @@ Usar `check_same_thread=False` permite que o FastAPI use o mesmo banco de dados
Não se preocupe, com a forma como o código está estruturado, garantiremos que usamos **uma única *sessão* SQLModel por requisição** mais tarde, isso é realmente o que o `check_same_thread` está tentando conseguir.
-### Criar as Tabelas
+### Criar as Tabelas { #create-the-tables }
Em seguida, adicionamos uma função que usa `SQLModel.metadata.create_all(engine)` para **criar as tabelas** para todos os *modelos de tabela*.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *}
-### Criar uma Dependência de Sessão
+### Criar uma Dependência de Sessão { #create-a-session-dependency }
Uma **`Session`** é o que armazena os **objetos na memória** e acompanha as alterações necessárias nos dados, para então **usar o `engine`** para se comunicar com o banco de dados.
@@ -98,7 +98,7 @@ Então, criamos uma dependência `Annotated` chamada `SessionDep` para simplific
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *}
-### Criar Tabelas de Banco de Dados na Inicialização
+### Criar Tabelas de Banco de Dados na Inicialização { #create-database-tables-on-startup }
Vamos criar as tabelas do banco de dados quando o aplicativo for iniciado.
@@ -114,7 +114,7 @@ O SQLModel terá utilitários de migração envolvendo o Alembic, mas por enquan
///
-### Criar um Hero
+### Criar um Hero { #create-a-hero }
Como cada modelo SQLModel também é um modelo Pydantic, você pode usá-lo nas mesmas **anotações de tipo** que usaria para modelos Pydantic.
@@ -124,29 +124,27 @@ Da mesma forma, você pode declará-lo como o **tipo de retorno** da função, e
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *}
-
-
Aqui, usamos a dependência `SessionDep` (uma `Session`) para adicionar o novo `Hero` à instância `Session`, fazer commit das alterações no banco de dados, atualizar os dados no `hero` e então retorná-lo.
-### Ler Heroes
+### Ler Heroes { #read-heroes }
Podemos **ler** `Hero`s do banco de dados usando um `select()`. Podemos incluir um `limit` e `offset` para paginar os resultados.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}
-### Ler um Único Hero
+### Ler um Único Hero { #read-one-hero }
Podemos **ler** um único `Hero`.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}
-### Deletar um Hero
+### Deletar um Hero { #delete-a-hero }
Também podemos **deletar** um `Hero`.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *}
-### Executar o App
+### Executar o App { #run-the-app }
Você pode executar o app:
@@ -166,7 +164,7 @@ Então, vá para a interface `/docs`, você verá que o **FastAPI** está usando
-## Atualizar o App com Múltiplos Modelos
+## Atualizar o App com Múltiplos Modelos { #update-the-app-with-multiple-models }
Agora vamos **refatorar** este app um pouco para aumentar a **segurança** e **versatilidade**.
@@ -178,7 +176,7 @@ Além disso, criamos um `secret_name` para o hero, mas até agora estamos retorn
Vamos corrigir essas coisas adicionando alguns **modelos extras**. Aqui é onde o SQLModel vai brilhar. ✨
-### Criar Múltiplos Modelos
+### Criar Múltiplos Modelos { #create-multiple-models }
No **SQLModel**, qualquer classe de modelo que tenha `table=True` é um **modelo de tabela**.
@@ -186,7 +184,7 @@ E qualquer classe de modelo que não tenha `table=True` é um **modelo de dados*
Com o SQLModel, podemos usar a **herança** para **evitar duplicação** de todos os campos em todos os casos.
-#### `HeroBase` - a classe base
+#### `HeroBase` - a classe base { #herobase-the-base-class }
Vamos começar com um modelo `HeroBase` que tem todos os **campos compartilhados** por todos os modelos:
@@ -195,7 +193,7 @@ Vamos começar com um modelo `HeroBase` que tem todos os **campos compartilhados
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *}
-#### `Hero` - o *modelo de tabela*
+#### `Hero` - o *modelo de tabela* { #hero-the-table-model }
Em seguida, vamos criar `Hero`, o verdadeiro *modelo de tabela*, com os **campos extras** que nem sempre estão nos outros modelos:
@@ -211,7 +209,7 @@ Como `Hero` herda de `HeroBase`, ele **também** tem os **campos** declarados em
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *}
-#### `HeroPublic` - o *modelo de dados* público
+#### `HeroPublic` - o *modelo de dados* público { #heropublic-the-public-data-model }
Em seguida, criamos um modelo `HeroPublic`, que será **retornado** para os clientes da API.
@@ -234,11 +232,10 @@ Todos os campos em `HeroPublic` são os mesmos que em `HeroBase`, com `id` decla
* `id`
* `name`
* `age`
-* `secret_name`
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *}
-#### `HeroCreate` - o *modelo de dados* para criar um hero
+#### `HeroCreate` - o *modelo de dados* para criar um hero { #herocreate-the-data-model-to-create-a-hero }
Agora criamos um modelo `HeroCreate`, este é o que **validará** os dados dos clientes.
@@ -262,7 +259,7 @@ Os campos de `HeroCreate` são:
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *}
-#### `HeroUpdate` - o *modelo de dados* para atualizar um hero
+#### `HeroUpdate` - o *modelo de dados* para atualizar um hero { #heroupdate-the-data-model-to-update-a-hero }
Não tínhamos uma maneira de **atualizar um hero** na versão anterior do app, mas agora com **múltiplos modelos**, podemos fazer isso. 🎉
@@ -280,7 +277,7 @@ Os campos de `HeroUpdate` são:
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *}
-### Criar com `HeroCreate` e retornar um `HeroPublic`
+### Criar com `HeroCreate` e retornar um `HeroPublic` { #create-with-herocreate-and-return-a-heropublic }
Agora que temos **múltiplos modelos**, podemos atualizar as partes do app que os utilizam.
@@ -302,19 +299,19 @@ Ao declará-lo no `response_model`, estamos dizendo ao **FastAPI** para fazer o
///
-### Ler Heroes com `HeroPublic`
+### Ler Heroes com `HeroPublic` { #read-heroes-with-heropublic }
Podemos fazer o mesmo que antes para **ler** `Hero`s, novamente, usamos `response_model=list[HeroPublic]` para garantir que os dados sejam validados e serializados corretamente.
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *}
-### Ler Um Hero com `HeroPublic`
+### Ler Um Hero com `HeroPublic` { #read-one-hero-with-heropublic }
Podemos **ler** um único herói:
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *}
-### Atualizar um Hero com `HeroUpdate`
+### Atualizar um Hero com `HeroUpdate` { #update-a-hero-with-heroupdate }
Podemos **atualizar um hero**. Para isso, usamos uma operação HTTP `PATCH`.
@@ -324,7 +321,7 @@ Em seguida, usamos `hero_db.sqlmodel_update(hero_data)` para atualizar o `hero_d
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *}
-### Deletar um Hero Novamente
+### Deletar um Hero Novamente { #delete-a-hero-again }
**Deletar** um hero permanece praticamente o mesmo.
@@ -332,7 +329,7 @@ Não vamos satisfazer o desejo de refatorar tudo neste aqui. 😅
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *}
-### Executar o App Novamente
+### Executar o App Novamente { #run-the-app-again }
Você pode executar o app novamente:
@@ -346,13 +343,13 @@ $ fastapi dev main.py
-If you go to the `/docs` API UI, you will see that it is now updated, and it won't expect to receive the `id` from the client when creating a hero, etc.
+Se você for para a interface `/docs` da API, verá que agora ela está atualizada e não esperará receber o `id` do cliente ao criar um hero, etc.
lt
+* XWT
+* PSGI
+
+### abbr даёт объяснение { #the-abbr-gives-an-explanation }
+
+* кластер
+* Глубокое обучение
+
+### abbr даёт полную расшифровку и объяснение { #the-abbr-gives-a-full-phrase-and-an-explanation }
+
+* MDN
+* I/O.
+
+////
+
+//// tab | Информация
+
+Атрибуты "title" элементов "abbr" переводятся по определённым правилам.
+
+Переводы могут добавлять свои собственные элементы "abbr", которые LLM не должна удалять. Например, чтобы объяснить английские слова.
+
+См. раздел `### HTML abbr elements` в общем промпте в `scripts/translate.py`.
+
+////
+
+## Заголовки { #headings }
+
+//// tab | Тест
+
+### Разработка веб‑приложения — руководство { #develop-a-webapp-a-tutorial }
+
+Привет.
+
+### Аннотации типов и -аннотации { #type-hints-and-annotations }
+
+Снова привет.
+
+### Супер- и подклассы { #super-and-subclasses }
+
+Снова привет.
+
+////
+
+//// tab | Информация
+
+Единственное жёсткое правило для заголовков — LLM должна оставить часть хеша в фигурных скобках без изменений, чтобы ссылки не ломались.
+
+См. раздел `### Headings` в общем промпте в `scripts/translate.py`.
+
+Для некоторых языковых инструкций см., например, раздел `### Headings` в `docs/de/llm-prompt.md`.
+
+////
+
+## Термины, используемые в документации { #terms-used-in-the-docs }
+
+//// tab | Тест
+
+* вы
+* ваш
+
+* например
+* и т.д.
+
+* `foo` как `int`
+* `bar` как `str`
+* `baz` как `list`
+
+* Учебник — Руководство пользователя
+* Расширенное руководство пользователя
+* Документация по SQLModel
+* Документация API
+* Автоматическая документация
+
+* Наука о данных
+* Глубокое обучение
+* Машинное обучение
+* Внедрение зависимостей
+* Аутентификация HTTP Basic
+* HTTP Digest
+* формат ISO
+* стандарт JSON Schema
+* JSON-схема
+* определение схемы
+* password flow
+* Мобильный
+
+* устаревший
+* спроектированный
+* некорректный
+* на лету
+* стандарт
+* по умолчанию
+* чувствительный к регистру
+* нечувствительный к регистру
+
+* обслуживать приложение
+* отдавать страницу
+
+* приложение
+* приложение
+
+* HTTP-запрос
+* HTTP-ответ
+* ответ с ошибкой
+
+* операция пути
+* декоратор операции пути
+* функция-обработчик пути
+
+* тело
+* тело запроса
+* тело ответа
+* JSON-тело
+* тело формы
+* тело файла
+* тело функции
+
+* параметр
+* body-параметр
+* path-параметр
+* query-параметр
+* cookie-параметр
+* параметр заголовка
+* параметр формы
+* параметр функции
+
+* событие
+* событие запуска
+* запуск сервера
+* событие остановки
+* событие lifespan
+
+* обработчик
+* обработчик события
+* обработчик исключений
+* обрабатывать
+
+* модель
+* Pydantic-модель
+* модель данных
+* модель базы данных
+* модель формы
+* объект модели
+
+* класс
+* базовый класс
+* родительский класс
+* подкласс
+* дочерний класс
+* родственный класс
+* метод класса
+
+* заголовок
+* HTTP-заголовки
+* заголовок авторизации
+* заголовок `Authorization`
+* заголовок `Forwarded`
+
+* система внедрения зависимостей
+* зависимость
+* зависимый объект
+* зависимый
+
+* ограниченный вводом/выводом
+* ограниченный процессором
+* конкурентность
+* параллелизм
+* многопроцессность
+
+* переменная окружения
+* переменная окружения
+* `PATH`
+* переменная `PATH`
+
+* аутентификация
+* провайдер аутентификации
+* авторизация
+* форма авторизации
+* провайдер авторизации
+* пользователь аутентифицируется
+* система аутентифицирует пользователя
+
+* CLI
+* интерфейс командной строки
+
+* сервер
+* клиент
+
+* облачный провайдер
+* облачный сервис
+
+* разработка
+* этапы разработки
+
+* dict
+* словарь
+* перечисление
+* enum
+* член перечисления
+
+* кодировщик
+* декодировщик
+* кодировать
+* декодировать
+
+* исключение
+* вызвать
+
+* выражение
+* оператор
+
+* фронтенд
+* бэкенд
+
+* обсуждение на GitHub
+* Issue на GitHub (тикет/обращение)
+
+* производительность
+* оптимизация производительности
+
+* тип возвращаемого значения
+* возвращаемое значение
+
+* безопасность
+* схема безопасности
+
+* задача
+* фоновая задача
+* функция задачи
+
+* шаблон
+* шаблонизатор
+
+* аннотация типов
+* аннотация типов
+
+* воркер сервера
+* воркер Uvicorn
+* воркер Gunicorn
+* воркер-процесс
+* класс воркера
+* рабочая нагрузка
+
+* деплой
+* развернуть
+
+* SDK
+* набор средств разработки ПО
+
+* `APIRouter`
+* `requirements.txt`
+* токен Bearer
+* несовместимое изменение
+* баг
+* кнопка
+* вызываемый объект
+* код
+* коммит
+* менеджер контекста
+* корутина
+* сессия базы данных
+* диск
+* домен
+* движок
+* фиктивный X
+* метод HTTP GET
+* элемент
+* библиотека
+* lifespan
+* блокировка
+* middleware (Промежуточный слой)
+* мобильное приложение
+* модуль
+* монтирование
+* сеть
+* origin (источник)
+* переопределение
+* полезная нагрузка
+* процессор
+* свойство
+* прокси
+* пулл-реквест (запрос на изменение)
+* запрос
+* ОЗУ
+* удалённая машина
+* статус-код
+* строка
+* тег
+* веб‑фреймворк
+* подстановочный знак
+* вернуть
+* валидировать
+
+////
+
+//// tab | Информация
+
+Это неполный и ненормативный список (в основном) технических терминов, встречающихся в документации. Он может помочь автору промпта понять, по каким терминам LLM нужна подсказка. Например, когда она продолжает возвращать действительно хороший перевод к неоптимальному. Или когда у неё возникают проблемы со склонением/спряжением термина на вашем языке.
+
+См., например, раздел `### List of English terms and their preferred German translations` в `docs/de/llm-prompt.md`.
+
+////
diff --git a/docs/ru/docs/about/index.md b/docs/ru/docs/about/index.md
index 1015b667a..4f48266a7 100644
--- a/docs/ru/docs/about/index.md
+++ b/docs/ru/docs/about/index.md
@@ -1,3 +1,3 @@
-# О проекте
+# О проекте { #about }
-FastAPI: внутреннее устройство, повлиявшие технологии и всё такое прочее. 🤓
+О FastAPI, его дизайне, источниках вдохновения и многом другом. 🤓
diff --git a/docs/ru/docs/advanced/additional-responses.md b/docs/ru/docs/advanced/additional-responses.md
new file mode 100644
index 000000000..fca4f072d
--- /dev/null
+++ b/docs/ru/docs/advanced/additional-responses.md
@@ -0,0 +1,247 @@
+# Дополнительные ответы в OpenAPI { #additional-responses-in-openapi }
+
+/// warning | Предупреждение
+
+Это довольно продвинутая тема.
+
+Если вы только начинаете работать с **FastAPI**, возможно, вам это пока не нужно.
+
+///
+
+Вы можете объявлять дополнительные ответы с дополнительными статус-кодами, типами содержимого, описаниями и т.д.
+
+Эти дополнительные ответы будут включены в схему OpenAPI, и поэтому появятся в документации API.
+
+Но для таких дополнительных ответов убедитесь, что вы возвращаете `Response`, например `JSONResponse`, напрямую, со своим статус-кодом и содержимым.
+
+## Дополнительный ответ с `model` { #additional-response-with-model }
+
+Вы можете передать вашим декораторам операции пути параметр `responses`.
+
+Он принимает `dict`: ключи — это статус-коды для каждого ответа (например, `200`), а значения — другие `dict` с информацией для каждого из них.
+
+Каждый из этих `dict` для ответа может иметь ключ `model`, содержащий Pydantic-модель, аналогично `response_model`.
+
+**FastAPI** возьмёт эту модель, сгенерирует для неё JSON‑схему и включит её в нужное место в OpenAPI.
+
+Например, чтобы объявить ещё один ответ со статус-кодом `404` и Pydantic-моделью `Message`, можно написать:
+
+{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *}
+
+/// note | Примечание
+
+Имейте в виду, что необходимо возвращать `JSONResponse` напрямую.
+
+///
+
+/// info | Информация
+
+Ключ `model` не является частью OpenAPI.
+
+**FastAPI** возьмёт Pydantic-модель оттуда, сгенерирует JSON‑схему и поместит её в нужное место.
+
+Нужное место:
+
+* В ключе `content`, значением которого является другой JSON‑объект (`dict`), содержащий:
+ * Ключ с типом содержимого, например `application/json`, значением которого является другой JSON‑объект, содержащий:
+ * Ключ `schema`, значением которого является JSON‑схема из модели — вот нужное место.
+ * **FastAPI** добавляет здесь ссылку на глобальные JSON‑схемы в другом месте вашего OpenAPI вместо того, чтобы включать схему напрямую. Так другие приложения и клиенты смогут использовать эти JSON‑схемы напрямую, предоставлять лучшие инструменты генерации кода и т.д.
+
+///
+
+Сгенерированные в OpenAPI ответы для этой операции пути будут такими:
+
+```JSON hl_lines="3-12"
+{
+ "responses": {
+ "404": {
+ "description": "Additional Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Message"
+ }
+ }
+ }
+ },
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+Схемы даны как ссылки на другое место внутри схемы OpenAPI:
+
+```JSON hl_lines="4-16"
+{
+ "components": {
+ "schemas": {
+ "Message": {
+ "title": "Message",
+ "required": [
+ "message"
+ ],
+ "type": "object",
+ "properties": {
+ "message": {
+ "title": "Message",
+ "type": "string"
+ }
+ }
+ },
+ "Item": {
+ "title": "Item",
+ "required": [
+ "id",
+ "value"
+ ],
+ "type": "object",
+ "properties": {
+ "id": {
+ "title": "Id",
+ "type": "string"
+ },
+ "value": {
+ "title": "Value",
+ "type": "string"
+ }
+ }
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": [
+ "loc",
+ "msg",
+ "type"
+ ],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "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"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+## Дополнительные типы содержимого для основного ответа { #additional-media-types-for-the-main-response }
+
+Вы можете использовать этот же параметр `responses`, чтобы добавить разные типы содержимого для того же основного ответа.
+
+Например, вы можете добавить дополнительный тип содержимого `image/png`, объявив, что ваша операция пути может возвращать JSON‑объект (с типом содержимого `application/json`) или PNG‑изображение:
+
+{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
+
+/// note | Примечание
+
+Учтите, что изображение нужно возвращать напрямую, используя `FileResponse`.
+
+///
+
+/// info | Информация
+
+Если вы явно не укажете другой тип содержимого в параметре `responses`, FastAPI будет считать, что ответ имеет тот же тип содержимого, что и основной класс ответа (по умолчанию `application/json`).
+
+Но если вы указали пользовательский класс ответа с `None` в качестве его типа содержимого, FastAPI использует `application/json` для любого дополнительного ответа, у которого есть связанная модель.
+
+///
+
+## Комбинирование информации { #combining-information }
+
+Вы также можете комбинировать информацию об ответах из нескольких мест, включая параметры `response_model`, `status_code` и `responses`.
+
+Вы можете объявить `response_model`, используя статус-код по умолчанию `200` (или свой, если нужно), а затем объявить дополнительную информацию для этого же ответа в `responses`, напрямую в схеме OpenAPI.
+
+**FastAPI** сохранит дополнительную информацию из `responses` и объединит её с JSON‑схемой из вашей модели.
+
+Например, вы можете объявить ответ со статус-кодом `404`, который использует Pydantic-модель и имеет пользовательское `description`.
+
+А также ответ со статус-кодом `200`, который использует ваш `response_model`, но включает пользовательский `example`:
+
+{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *}
+
+Всё это будет объединено и включено в ваш OpenAPI и отображено в документации API:
+
+
+
+## Комбинирование предопределённых и пользовательских ответов { #combine-predefined-responses-and-custom-ones }
+
+Возможно, вы хотите иметь некоторые предопределённые ответы, применимые ко многим операциям пути, но при этом комбинировать их с пользовательскими ответами, необходимыми для каждой конкретной операции пути.
+
+В таких случаях вы можете использовать приём Python «распаковки» `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_py310.py hl[11:15,24] *}
+
+## Дополнительная информация об ответах OpenAPI { #more-information-about-openapi-responses }
+
+Чтобы увидеть, что именно можно включать в ответы, посмотрите эти разделы спецификации OpenAPI:
+
+* Объект Responses OpenAPI, он включает `Response Object`.
+* Объект Response OpenAPI, вы можете включить всё из этого объекта напрямую в каждый ответ внутри вашего параметра `responses`. Включая `description`, `headers`, `content` (внутри него вы объявляете разные типы содержимого и JSON‑схемы) и `links`.
diff --git a/docs/ru/docs/advanced/additional-status-codes.md b/docs/ru/docs/advanced/additional-status-codes.md
index aab1f8ee3..7c73cf5d5 100644
--- a/docs/ru/docs/advanced/additional-status-codes.md
+++ b/docs/ru/docs/advanced/additional-status-codes.md
@@ -1,28 +1,28 @@
-# Дополнительные статус коды
+# Дополнительные статус-коды { #additional-status-codes }
-По умолчанию **FastAPI** возвращает ответы, используя `JSONResponse`, помещая содержимое, которое вы возвращаете из вашей *операции пути*, внутрь этого `JSONResponse`.
+По умолчанию **FastAPI** будет возвращать ответы, используя `JSONResponse`, помещая содержимое, которое вы возвращаете из вашей *операции пути*, внутрь этого `JSONResponse`.
-Он будет использовать код статуса по умолчанию или тот, который вы укажете в вашей *операции пути*.
+Он будет использовать статус-код по умолчанию или тот, который вы укажете в вашей *операции пути*.
-## Дополнительные статус коды
+## Дополнительные статус-коды { #additional-status-codes_1 }
-Если вы хотите возвращать дополнительный статус код помимо основного, вы можете сделать это, возвращая объект `Response` напрямую, как `JSONResponse`, и устанавливая нужный статус код напрямую.
+Если вы хотите возвращать дополнительные статус-коды помимо основного, вы можете сделать это, возвращая `Response` напрямую, например `JSONResponse`, и устанавливая дополнительный статус-код напрямую.
-Например, скажем, вы хотите создать *операцию пути*, которая позволяет обновлять элементы и возвращает HTTP-код 200 "OK" при успешном выполнении.
+Например, предположим, что вы хотите иметь *операцию пути*, которая позволяет обновлять элементы и возвращает HTTP статус-код 200 «OK» при успешном выполнении.
-Но вы также хотите, чтобы она принимала новые элементы. И если элемент ранее не существовал, он создаётся, и возвращался HTTP-код 201 "Created".
+Но вы также хотите, чтобы она принимала новые элементы. И если элементы ранее не существовали, она создаёт их и возвращает HTTP статус-код 201 «Created».
-Чтобы реализовать это, импортируйте `JSONResponse` и возвращайте ваш контент напрямую, устанавливая нужный `status_code`:
+Чтобы добиться этого, импортируйте `JSONResponse` и верните туда свой контент напрямую, установив нужный вам `status_code`:
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
/// warning | Внимание
-Когда вы возвращаете объект `Response` напрямую, как в примере выше, он будет возвращён как есть.
+Когда вы возвращаете `Response` напрямую, как в примере выше, он будет возвращён как есть.
-Он не будет сериализован при помощи модели и т.д.
+Он не будет сериализован с помощью модели и т.п.
-Убедитесь, что в нём содержатся именно те данные, которые вы хотите, и что значения являются валидным JSON (если вы используете `JSONResponse`).
+Убедитесь, что в нём именно те данные, которые вы хотите, и что значения являются валидным JSON (если вы используете `JSONResponse`).
///
@@ -30,12 +30,12 @@
Вы также можете использовать `from starlette.responses import JSONResponse`.
-**FastAPI** предоставляет тот же `starlette.responses` через `fastapi.responses` просто для вашего удобства, как разработчика. Но большинство доступных Response-классов поступают напрямую из Starlette. То же самое касается и `status`.
+**FastAPI** предоставляет тот же `starlette.responses` через `fastapi.responses` просто для вашего удобства как разработчика. Но большинство доступных Response-классов приходят напрямую из Starlette. То же самое со `status`.
///
-## OpenAPI и документация API
+## OpenAPI и документация API { #openapi-and-api-docs }
-Если вы возвращаете дополнительные коды статусов и ответы напрямую, они не будут включены в схему OpenAPI (документацию API), потому что FastAPI не может заранее знать, что вы собираетесь вернуть.
+Если вы возвращаете дополнительные статус-коды и ответы напрямую, они не будут включены в схему OpenAPI (документацию API), потому что у FastAPI нет способа заранее знать, что вы собираетесь вернуть.
-Но вы можете задокументировать это в вашем коде, используя: [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
+Но вы можете задокументировать это в своём коде, используя: [Дополнительные ответы](additional-responses.md){.internal-link target=_blank}.
diff --git a/docs/ru/docs/advanced/advanced-dependencies.md b/docs/ru/docs/advanced/advanced-dependencies.md
new file mode 100644
index 000000000..cc6691b30
--- /dev/null
+++ b/docs/ru/docs/advanced/advanced-dependencies.md
@@ -0,0 +1,163 @@
+# Продвинутые зависимости { #advanced-dependencies }
+
+## Параметризованные зависимости { #parameterized-dependencies }
+
+Все зависимости, которые мы видели, — это конкретная функция или класс.
+
+Но бывают случаи, когда нужно задавать параметры зависимости, не объявляя много разных функций или классов.
+
+Представим, что нам нужна зависимость, которая проверяет, содержит ли query-параметр `q` некоторое фиксированное содержимое.
+
+Но при этом мы хотим иметь возможность параметризовать это фиксированное содержимое.
+
+## «Вызываемый» экземпляр { #a-callable-instance }
+
+В Python есть способ сделать экземпляр класса «вызываемым» объектом.
+
+Не сам класс (он уже является вызываемым), а экземпляр этого класса.
+
+Для этого объявляем метод `__call__`:
+
+{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *}
+
+В этом случае именно `__call__` **FastAPI** использует для проверки дополнительных параметров и подзависимостей, и именно он будет вызван, чтобы позже передать значение параметру в вашей *функции-обработчике пути*.
+
+## Параметризуем экземпляр { #parameterize-the-instance }
+
+Теперь мы можем использовать `__init__`, чтобы объявить параметры экземпляра, с помощью которых будем «параметризовать» зависимость:
+
+{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *}
+
+В этом случае **FastAPI** вовсе не трогает `__init__` и не зависит от него — мы используем его напрямую в нашем коде.
+
+## Создаём экземпляр { #create-an-instance }
+
+Мы можем создать экземпляр этого класса так:
+
+{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *}
+
+Так мы «параметризуем» нашу зависимость: теперь внутри неё хранится "bar" в атрибуте `checker.fixed_content`.
+
+## Используем экземпляр как зависимость { #use-the-instance-as-a-dependency }
+
+Затем мы можем использовать этот `checker` в `Depends(checker)` вместо `Depends(FixedContentQueryChecker)`, потому что зависимостью является экземпляр `checker`, а не сам класс.
+
+И при разрешении зависимости **FastAPI** вызовет `checker` примерно так:
+
+```Python
+checker(q="somequery")
+```
+
+…и передаст возвращённое значение как значение зависимости в нашу *функцию-обработчике пути* в параметр `fixed_content_included`:
+
+{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *}
+
+/// tip | Совет
+
+Все это может показаться притянутым за уши. И пока может быть не совсем понятно, чем это полезно.
+
+Эти примеры намеренно простые, но они показывают, как всё устроено.
+
+В главах про безопасность есть вспомогательные функции, реализованные тем же способом.
+
+Если вы поняли всё выше, вы уже знаете, как «под капотом» работают эти утилиты для безопасности.
+
+///
+
+## Зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами { #dependencies-with-yield-httpexception-except-and-background-tasks }
+
+/// warning | Предупреждение
+
+Скорее всего, вам не понадобятся эти технические детали.
+
+Они полезны главным образом, если у вас было приложение FastAPI версии ниже 0.121.0 и вы столкнулись с проблемами зависимостей с `yield`.
+
+///
+
+Зависимости с `yield` со временем изменялись, чтобы учитывать разные случаи применения и исправлять проблемы. Ниже — краткое резюме изменений.
+
+### Зависимости с `yield` и `scope` { #dependencies-with-yield-and-scope }
+
+В версии 0.121.0 FastAPI добавил поддержку `Depends(scope="function")` для зависимостей с `yield`.
+
+При использовании `Depends(scope="function")` код после `yield` выполняется сразу после завершения *функции-обработчика пути*, до отправки ответа клиенту.
+
+А при использовании `Depends(scope="request")` (значение по умолчанию) код после `yield` выполняется после отправки ответа.
+
+Подробнее читайте в документации: [Зависимости с `yield` — раннее завершение и `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, но с несколькими улучшениями и исправлениями краевых случаев.
+
+///
+
+#### Сценарии с ранним выполнением кода после `yield` { #use-cases-with-early-exit-code }
+
+Есть некоторые сценарии со специфическими условиями, которым могло бы помочь старое поведение — выполнение кода после `yield` перед отправкой ответа.
+
+Например, представьте, что вы используете сессию базы данных в зависимости с `yield` только для проверки пользователя, а в самой *функции-обработчике пути* эта сессия больше не используется, и при этом ответ отправляется долго, например, это `StreamingResponse`, который медленно отправляет данные и по какой-то причине не использует базу данных.
+
+В таком случае сессия базы данных будет удерживаться до завершения отправки ответа, хотя если вы её не используете, удерживать её не требуется.
+
+Это могло бы выглядеть так:
+
+{* ../../docs_src/dependencies/tutorial013_an_py310.py *}
+
+Код после `yield`, автоматическое закрытие `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`, пожалуйста, создайте вопрос в GitHub Discussions с описанием конкретного кейса и почему вам было бы полезно иметь раннее закрытие для зависимостей с `yield`.
+
+Если появятся веские причины для раннего закрытия в зависимостях с `yield`, я рассмотрю добавление нового способа опционально включать раннее закрытие.
+
+### Зависимости с `yield` и `except`, технические детали { #dependencies-with-yield-and-except-technical-details }
+
+До FastAPI 0.110.0, если вы использовали зависимость с `yield`, затем перехватывали исключение с `except` в этой зависимости и не пробрасывали исключение снова, исключение автоматически пробрасывалось дальше к обработчикам исключений или к обработчику внутренней ошибки сервера.
+
+В версии 0.110.0 это было изменено, чтобы исправить неконтролируемое потребление памяти из‑за проброшенных исключений без обработчика (внутренние ошибки сервера) и привести поведение в соответствие с обычным поведением Python-кода.
+
+### Фоновые задачи и зависимости с `yield`, технические детали { #background-tasks-and-dependencies-with-yield-technical-details }
+
+До FastAPI 0.106.0 вызывать исключения после `yield` было невозможно: код после `yield` в зависимостях выполнялся уже после отправки ответа, поэтому [Обработчики исключений](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} к тому моменту уже отработали.
+
+Так было сделано в основном для того, чтобы можно было использовать те же объекты, «отданные» зависимостями через `yield`, внутри фоновых задач, потому что код после `yield` выполнялся после завершения фоновых задач.
+
+В FastAPI 0.106.0 это изменили, чтобы не удерживать ресурсы, пока ответ передаётся по сети.
+
+/// tip | Совет
+
+Кроме того, фоновая задача обычно — это самостоятельный фрагмент логики, который следует обрабатывать отдельно, со своими ресурсами (например, со своим подключением к базе данных).
+
+Так код, скорее всего, будет чище.
+
+///
+
+Если вы полагались на прежнее поведение, теперь ресурсы для фоновых задач следует создавать внутри самой фоновой задачи и использовать внутри неё только данные, которые не зависят от ресурсов зависимостей с `yield`.
+
+Например, вместо использования той же сессии базы данных, создайте новую сессию в фоновой задаче и получите объекты из базы данных с помощью этой новой сессии. И затем, вместо передачи объекта из базы данных параметром в функцию фоновой задачи, передавайте идентификатор этого объекта и заново получайте объект внутри функции фоновой задачи.
diff --git a/docs/ru/docs/advanced/async-tests.md b/docs/ru/docs/advanced/async-tests.md
index 7849ad109..e68970406 100644
--- a/docs/ru/docs/advanced/async-tests.md
+++ b/docs/ru/docs/advanced/async-tests.md
@@ -1,4 +1,4 @@
-# Асинхронное тестирование
+# Асинхронное тестирование { #async-tests }
Вы уже видели как тестировать **FastAPI** приложение, используя имеющийся класс `TestClient`. К этому моменту вы видели только как писать тесты в синхронном стиле без использования `async` функций.
@@ -6,11 +6,11 @@
Давайте рассмотрим, как мы можем это реализовать.
-## pytest.mark.anyio
+## pytest.mark.anyio { #pytest-mark-anyio }
Если мы хотим вызывать асинхронные функции в наших тестах, то наши тестовые функции должны быть асинхронными. AnyIO предоставляет для этого отличный плагин, который позволяет нам указывать, какие тестовые функции должны вызываться асинхронно.
-## HTTPX
+## HTTPX { #httpx }
Даже если **FastAPI** приложение использует обычные функции `def` вместо `async def`, это все равно `async` приложение 'под капотом'.
@@ -18,7 +18,7 @@
`TestClient` основан на HTTPX, и, к счастью, мы можем использовать его (`HTTPX`) напрямую для тестирования API.
-## Пример
+## Пример { #example }
В качестве простого примера, давайте рассмотрим файловую структуру, схожую с описанной в [Большие приложения](../tutorial/bigger-applications.md){.internal-link target=_blank} и [Тестирование](../tutorial/testing.md){.internal-link target=_blank}:
@@ -32,13 +32,13 @@
Файл `main.py`:
-{* ../../docs_src/async_tests/main.py *}
+{* ../../docs_src/async_tests/app_a_py39/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 }
Вы можете запустить свои тесты как обычно:
@@ -52,11 +52,11 @@ $ pytest
-## Подробнее
+## Подробнее { #in-detail }
Маркер `@pytest.mark.anyio` говорит pytest, что тестовая функция должна быть вызвана асинхронно:
-{* ../../docs_src/async_tests/test_main.py hl[7] *}
+{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *}
/// tip | Подсказка
@@ -66,7 +66,7 @@ $ pytest
Затем мы можем создать `AsyncClient` со ссылкой на приложение и посылать асинхронные запросы, используя `await`.
-{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
+{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *}
Это эквивалентно следующему:
@@ -88,7 +88,7 @@ response = client.get('/')
///
-## Вызов других асинхронных функций
+## Вызов других асинхронных функций { #other-asynchronous-function-calls }
Теперь тестовая функция стала асинхронной, поэтому внутри нее вы можете вызывать также и другие `async` функции, не связанные с отправлением запросов в ваше FastAPI приложение. Как если бы вы вызывали их в любом другом месте вашего кода.
diff --git a/docs/ru/docs/advanced/behind-a-proxy.md b/docs/ru/docs/advanced/behind-a-proxy.md
new file mode 100644
index 000000000..f78da01a0
--- /dev/null
+++ b/docs/ru/docs/advanced/behind-a-proxy.md
@@ -0,0 +1,466 @@
+# За прокси‑сервером { #behind-a-proxy }
+
+Во многих случаях перед приложением FastAPI используется прокси‑сервер, например Traefik или Nginx.
+
+Такие прокси могут обрабатывать HTTPS‑сертификаты и многое другое.
+
+## Пересылаемые заголовки прокси { #proxy-forwarded-headers }
+
+Прокси перед вашим приложением обычно на лету добавляет некоторые HTTP‑заголовки перед отправкой запроса на ваш сервер, чтобы сообщить ему, что запрос был переслан прокси, а также передать исходный (публичный) URL (включая домен), информацию об использовании HTTPS и т.д.
+
+Программа сервера (например, Uvicorn, запущенный через FastAPI CLI) умеет интерпретировать эти заголовки и передавать соответствующую информацию вашему приложению.
+
+Но из соображений безопасности, пока сервер не уверен, что находится за доверенным прокси, он не будет интерпретировать эти заголовки.
+
+/// note | Технические детали
+
+Заголовки прокси:
+
+* X-Forwarded-For
+* X-Forwarded-Proto
+* X-Forwarded-Host
+
+///
+
+### Включить пересылаемые заголовки прокси { #enable-proxy-forwarded-headers }
+
+Вы можете запустить FastAPI CLI с опцией командной строки `--forwarded-allow-ips` и передать IP‑адреса, которым следует доверять при чтении этих пересылаемых заголовков.
+
+Если указать `--forwarded-allow-ips="*"`, приложение будет доверять всем входящим IP.
+
+Если ваш сервер находится за доверенным прокси и только прокси обращается к нему, этого достаточно, чтобы он принимал IP этого прокси.
+
+
+
+А вот если открыть интерфейс документации по «официальному» URL через прокси на порту `9999`, по `/api/v1/docs`, всё работает корректно! 🎉
+
+Проверьте по адресу http://127.0.0.1:9999/api/v1/docs:
+
+
+
+Именно как и хотелось. ✔️
+
+Это потому, что FastAPI использует `root_path`, чтобы создать в OpenAPI сервер по умолчанию с URL из `root_path`.
+
+## Дополнительные серверы { #additional-servers }
+
+/// warning | Предупреждение
+
+Это более продвинутый сценарий. Можно пропустить.
+
+///
+
+По умолчанию FastAPI создаёт в схеме OpenAPI `server` с URL из `root_path`.
+
+Но вы также можете указать дополнительные `servers`, например, если хотите, чтобы один и тот же интерфейс документации работал и со стейджингом, и с продакшн.
+
+Если вы передадите свой список `servers` и при этом задан `root_path` (потому что ваш API работает за прокси), FastAPI вставит «server» с этим `root_path` в начало списка.
+
+Например:
+
+{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *}
+
+Будет сгенерирована схема OpenAPI примерно такая:
+
+```JSON hl_lines="5-7"
+{
+ "openapi": "3.1.0",
+ // Здесь ещё что-то
+ "servers": [
+ {
+ "url": "/api/v1"
+ },
+ {
+ "url": "https://stag.example.com",
+ "description": "Staging environment"
+ },
+ {
+ "url": "https://prod.example.com",
+ "description": "Production environment"
+ }
+ ],
+ "paths": {
+ // Здесь ещё что-то
+ }
+}
+```
+
+/// tip | Совет
+
+Обратите внимание на автоматически добавленный сервер с `url` равным `/api/v1`, взятым из `root_path`.
+
+///
+
+В интерфейсе документации по адресу http://127.0.0.1:9999/api/v1/docs это будет выглядеть так:
+
+
+
+/// tip | Совет
+
+Интерфейс документации будет взаимодействовать с сервером, который вы выберете.
+
+///
+
+/// note | Технические детали
+
+Свойство `servers` в спецификации OpenAPI является необязательным.
+
+Если вы не укажете параметр `servers`, а `root_path` равен `/`, то свойство `servers` в сгенерированной схеме OpenAPI по умолчанию будет опущено. Это эквивалентно серверу со значением `url` равным `/`.
+
+///
+
+### Отключить автоматическое добавление сервера из `root_path` { #disable-automatic-server-from-root-path }
+
+Если вы не хотите, чтобы FastAPI добавлял автоматический сервер, используя `root_path`, укажите параметр `root_path_in_servers=False`:
+
+{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *}
+
+и тогда этот сервер не будет добавлен в схему OpenAPI.
+
+## Монтирование вложенного приложения { #mounting-a-sub-application }
+
+Если вам нужно смонтировать вложенное приложение (как описано в [Вложенные приложения — монтирование](sub-applications.md){.internal-link target=_blank}), и при этом вы используете прокси с `root_path`, делайте это обычным образом — всё будет работать, как ожидается.
+
+FastAPI умно использует `root_path` внутри, так что всё просто работает. ✨
diff --git a/docs/ru/docs/advanced/custom-response.md b/docs/ru/docs/advanced/custom-response.md
new file mode 100644
index 000000000..49550b49f
--- /dev/null
+++ b/docs/ru/docs/advanced/custom-response.md
@@ -0,0 +1,312 @@
+# Кастомные ответы — HTML, поток, файл и другие { #custom-response-html-stream-file-others }
+
+По умолчанию **FastAPI** возвращает ответы с помощью `JSONResponse`.
+
+Вы можете переопределить это, вернув `Response` напрямую, как показано в разделе [Вернуть Response напрямую](response-directly.md){.internal-link target=_blank}.
+
+Но если вы возвращаете `Response` напрямую (или любой его подкласс, например `JSONResponse`), данные не будут автоматически преобразованы (даже если вы объявили `response_model`), и документация не будет автоматически сгенерирована (например, со специфичным «типом содержимого» в HTTP-заголовке `Content-Type` как частью сгенерированного OpenAPI).
+
+Но вы можете также объявить `Response`, который хотите использовать (например, любой подкласс `Response`), в декораторе операции пути, используя параметр `response_class`.
+
+Содержимое, которое вы возвращаете из своей функции-обработчика пути, будет помещено внутрь этого `Response`.
+
+И если у этого `Response` тип содержимого JSON (`application/json`), как в случае с `JSONResponse` и `UJSONResponse`, данные, которые вы возвращаете, будут автоматически преобразованы (и отфильтрованы) любым объявленным вами в декораторе операции пути Pydantic `response_model`.
+
+/// note | Примечание
+
+Если вы используете класс ответа без типа содержимого, FastAPI будет ожидать, что у вашего ответа нет содержимого, поэтому он не будет документировать формат ответа в сгенерированной документации OpenAPI.
+
+///
+
+## Используйте `ORJSONResponse` { #use-orjsonresponse }
+
+Например, если вы выжимаете максимум производительности, вы можете установить и использовать `orjson` и задать ответ как `ORJSONResponse`.
+
+Импортируйте класс (подкласс) `Response`, который вы хотите использовать, и объявите его в декораторе операции пути.
+
+Для больших ответов возвращать `Response` напрямую значительно быстрее, чем возвращать словарь.
+
+Это потому, что по умолчанию FastAPI проверяет каждый элемент внутри и убеждается, что он сериализуем в JSON, используя тот же [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}, объяснённый в руководстве. Это позволяет возвращать **произвольные объекты**, например модели из базы данных.
+
+Но если вы уверены, что содержимое, которое вы возвращаете, **сериализуемо в JSON**, вы можете передать его напрямую в класс ответа и избежать дополнительных накладных расходов, которые FastAPI понёс бы, пропуская возвращаемое содержимое через `jsonable_encoder` перед передачей в класс ответа.
+
+{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *}
+
+/// info | Информация
+
+Параметр `response_class` также используется для указания «типа содержимого» ответа.
+
+В этом случае HTTP-заголовок `Content-Type` будет установлен в `application/json`.
+
+И это будет задокументировано как таковое в OpenAPI.
+
+///
+
+/// tip | Совет
+
+`ORJSONResponse` доступен только в FastAPI, а не в Starlette.
+
+///
+
+## HTML-ответ { #html-response }
+
+Чтобы вернуть ответ с HTML напрямую из **FastAPI**, используйте `HTMLResponse`.
+
+- Импортируйте `HTMLResponse`.
+- Передайте `HTMLResponse` в параметр `response_class` вашего декоратора операции пути.
+
+{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *}
+
+/// info | Информация
+
+Параметр `response_class` также используется для указания «типа содержимого» ответа.
+
+В этом случае HTTP-заголовок `Content-Type` будет установлен в `text/html`.
+
+И это будет задокументировано как таковое в OpenAPI.
+
+///
+
+### Вернуть `Response` { #return-a-response }
+
+Как показано в разделе [Вернуть Response напрямую](response-directly.md){.internal-link target=_blank}, вы также можете переопределить ответ прямо в своей операции пути, просто вернув его.
+
+Тот же пример сверху, возвращающий `HTMLResponse`, может выглядеть так:
+
+{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *}
+
+/// warning | Предупреждение
+
+`Response`, возвращённый напрямую вашей функцией-обработчиком пути, не будет задокументирован в OpenAPI (например, `Content-Type` нне будет задокументирова) и не будет виден в автоматически сгенерированной интерактивной документации.
+
+///
+
+/// info | Информация
+
+Разумеется, фактические заголовок `Content-Type`, статус-код и т.д. возьмутся из объекта `Response`, который вы вернули.
+
+///
+
+### Задокументировать в OpenAPI и переопределить `Response` { #document-in-openapi-and-override-response }
+
+Если вы хотите переопределить ответ внутри функции, но при этом задокументировать «тип содержимого» в OpenAPI, вы можете использовать параметр `response_class` И вернуть объект `Response`.
+
+Тогда `response_class` будет использоваться только для документирования *операции пути* в OpenAPI, а ваш `Response` будет использован как есть.
+
+#### Вернуть `HTMLResponse` напрямую { #return-an-htmlresponse-directly }
+
+Например, это может быть что-то вроде:
+
+{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *}
+
+В этом примере функция `generate_html_response()` уже генерирует и возвращает `Response` вместо возврата HTML в `str`.
+
+Возвращая результат вызова `generate_html_response()`, вы уже возвращаете `Response`, который переопределит поведение **FastAPI** по умолчанию.
+
+Но поскольку вы также передали `HTMLResponse` в `response_class`, **FastAPI** будет знать, как задокументировать это в OpenAPI и интерактивной документации как HTML с `text/html`:
+
+
+
+## Доступные ответы { #available-responses }
+
+Ниже перечислены некоторые доступные классы ответов.
+
+Учтите, что вы можете использовать `Response`, чтобы вернуть что угодно ещё, или даже создать собственный подкласс.
+
+/// note | Технические детали
+
+Вы также могли бы использовать `from starlette.responses import HTMLResponse`.
+
+**FastAPI** предоставляет те же `starlette.responses` как `fastapi.responses` для вашего удобства как разработчика. Но большинство доступных классов ответов приходят непосредственно из Starlette.
+
+///
+
+### `Response` { #response }
+
+Базовый класс `Response`, от него наследуются все остальные ответы.
+
+Его можно возвращать напрямую.
+
+Он принимает следующие параметры:
+
+- `content` — `str` или `bytes`.
+- `status_code` — целое число, HTTP статус-код.
+- `headers` — словарь строк.
+- `media_type` — строка, задающая тип содержимого. Например, `"text/html"`.
+
+FastAPI (фактически Starlette) автоматически добавит заголовок Content-Length. Также будет добавлен заголовок Content-Type, основанный на `media_type` и с добавлением charset для текстовых типов.
+
+{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
+
+### `HTMLResponse` { #htmlresponse }
+
+Принимает текст или байты и возвращает HTML-ответ, как описано выше.
+
+### `PlainTextResponse` { #plaintextresponse }
+
+Принимает текст или байты и возвращает ответ в виде простого текста.
+
+{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *}
+
+### `JSONResponse` { #jsonresponse }
+
+Принимает данные и возвращает ответ, кодированный как `application/json`.
+
+Это ответ по умолчанию, используемый в **FastAPI**, как было сказано выше.
+
+### `ORJSONResponse` { #orjsonresponse }
+
+Быстрая альтернативная реализация JSON-ответа с использованием `orjson`, как было сказано выше.
+
+/// info | Информация
+
+Требуется установка `orjson`, например командой `pip install orjson`.
+
+///
+
+### `UJSONResponse` { #ujsonresponse }
+
+Альтернативная реализация JSON-ответа с использованием `ujson`.
+
+/// info | Информация
+
+Требуется установка `ujson`, например командой `pip install ujson`.
+
+///
+
+/// warning | Предупреждение
+
+`ujson` менее аккуратен, чем встроенная реализация Python, в обработке некоторых крайних случаев.
+
+///
+
+{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *}
+
+/// tip | Совет
+
+Возможно, `ORJSONResponse` окажется более быстрым вариантом.
+
+///
+
+### `RedirectResponse` { #redirectresponse }
+
+Возвращает HTTP-редирект. По умолчанию использует статус-код 307 (Temporary Redirect — временное перенаправление).
+
+Вы можете вернуть `RedirectResponse` напрямую:
+
+{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *}
+
+---
+
+Или можно использовать его в параметре `response_class`:
+
+{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *}
+
+Если вы сделаете так, то сможете возвращать URL напрямую из своей функции-обработчика пути.
+
+В этом случае будет использован статус-код по умолчанию для `RedirectResponse`, то есть `307`.
+
+---
+
+Также вы можете использовать параметр `status_code` в сочетании с параметром `response_class`:
+
+{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *}
+
+### `StreamingResponse` { #streamingresponse }
+
+Принимает асинхронный генератор или обычный генератор/итератор и отправляет тело ответа потоково.
+
+{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *}
+
+#### Использование `StreamingResponse` с файлоподобными объектами { #using-streamingresponse-with-file-like-objects }
+
+Если у вас есть файлоподобный объект (например, объект, возвращаемый `open()`), вы можете создать функцию-генератор для итерации по этому файлоподобному объекту.
+
+Таким образом, вам не нужно сначала читать всё в память, вы можете передать эту функцию-генератор в `StreamingResponse` и вернуть его.
+
+Это включает многие библиотеки для работы с облачным хранилищем, обработки видео и т.д.
+
+{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *}
+
+1. Это функция-генератор. Она является «функцией-генератором», потому что содержит оператор(ы) `yield` внутри.
+2. Используя блок `with`, мы гарантируем, что файлоподобный объект будет закрыт после завершения работы функции-генератора. То есть после того, как она закончит отправку ответа.
+3. Этот `yield from` говорит функции итерироваться по объекту с именем `file_like`. И затем, для каждой итерации, отдавать эту часть как исходящую из этой функции-генератора (`iterfile`).
+
+ Таким образом, это функция-генератор, которая внутренне передаёт работу по «генерации» чему-то другому.
+
+ Делая это таким образом, мы можем поместить её в блок `with` и тем самым гарантировать, что файлоподобный объект будет закрыт после завершения.
+
+/// tip | Совет
+
+Заметьте, что здесь мы используем стандартный `open()`, который не поддерживает `async` и `await`, поэтому объявляем операцию пути обычной `def`.
+
+///
+
+### `FileResponse` { #fileresponse }
+
+Асинхронно отправляет файл как ответ.
+
+Для создания экземпляра принимает иной набор аргументов, чем другие типы ответов:
+
+- `path` — путь к файлу, который будет отправлен.
+- `headers` — любые дополнительные заголовки для включения, в виде словаря.
+- `media_type` — строка, задающая тип содержимого. Если не задан, для определения типа содержимого будет использовано имя файла или путь.
+- `filename` — если задан, будет включён в заголовок ответа `Content-Disposition`.
+
+Файловые ответы будут содержать соответствующие заголовки `Content-Length`, `Last-Modified` и `ETag`.
+
+{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *}
+
+Вы также можете использовать параметр `response_class`:
+
+{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *}
+
+В этом случае вы можете возвращать путь к файлу напрямую из своей функции-обработчика пути.
+
+## Пользовательский класс ответа { #custom-response-class }
+
+Вы можете создать собственный класс ответа, унаследовавшись от `Response`, и использовать его.
+
+Например, предположим, что вы хотите использовать `orjson`, но с некоторыми пользовательскими настройками, которые не используются во встроенном классе `ORJSONResponse`.
+
+Скажем, вы хотите, чтобы возвращался отформатированный JSON с отступами, то есть хотите использовать опцию orjson `orjson.OPT_INDENT_2`.
+
+Вы могли бы создать `CustomORJSONResponse`. Главное, что вам нужно сделать — реализовать метод `Response.render(content)`, который возвращает содержимое как `bytes`:
+
+{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *}
+
+Теперь вместо того, чтобы возвращать:
+
+```json
+{"message": "Hello World"}
+```
+
+...этот ответ вернёт:
+
+```json
+{
+ "message": "Hello World"
+}
+```
+
+Разумеется, вы наверняка найдёте гораздо более полезные способы воспользоваться этим, чем просто форматирование JSON. 😉
+
+## Класс ответа по умолчанию { #default-response-class }
+
+При создании экземпляра класса **FastAPI** или `APIRouter` вы можете указать, какой класс ответа использовать по умолчанию.
+
+Параметр, который это определяет, — `default_response_class`.
+
+В примере ниже **FastAPI** будет использовать `ORJSONResponse` по умолчанию во всех операциях пути вместо `JSONResponse`.
+
+{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *}
+
+/// tip | Совет
+
+Вы по-прежнему можете переопределять `response_class` в операциях пути, как и раньше.
+
+///
+
+## Дополнительная документация { #additional-documentation }
+
+Вы также можете объявить тип содержимого и многие другие детали в OpenAPI с помощью `responses`: [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
diff --git a/docs/ru/docs/advanced/dataclasses.md b/docs/ru/docs/advanced/dataclasses.md
new file mode 100644
index 000000000..b3ced37c1
--- /dev/null
+++ b/docs/ru/docs/advanced/dataclasses.md
@@ -0,0 +1,95 @@
+# Использование dataclasses { #using-dataclasses }
+
+FastAPI построен поверх **Pydantic**, и я показывал вам, как использовать Pydantic-модели для объявления HTTP-запросов и HTTP-ответов.
+
+Но FastAPI также поддерживает использование `dataclasses` тем же способом:
+
+{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
+
+Это по-прежнему поддерживается благодаря **Pydantic**, так как в нём есть встроенная поддержка `dataclasses`.
+
+Так что даже если в коде выше Pydantic не используется явно, FastAPI использует Pydantic, чтобы конвертировать стандартные dataclasses в собственный вариант dataclasses от Pydantic.
+
+И, конечно, поддерживаются те же возможности:
+
+- валидация данных
+- сериализация данных
+- документирование данных и т.д.
+
+Это работает так же, как с Pydantic-моделями. И на самом деле под капотом это достигается тем же образом, с использованием Pydantic.
+
+/// info | Информация
+
+Помните, что dataclasses не умеют всего того, что умеют Pydantic-модели.
+
+Поэтому вам всё ещё может потребоваться использовать Pydantic-модели.
+
+Но если у вас уже есть набор dataclasses, это полезный приём — задействовать их для веб-API на FastAPI. 🤓
+
+///
+
+## Dataclasses в `response_model` { #dataclasses-in-response-model }
+
+Вы также можете использовать `dataclasses` в параметре `response_model`:
+
+{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
+
+Этот dataclass будет автоматически преобразован в Pydantic dataclass.
+
+Таким образом, его схема появится в интерфейсе документации API:
+
+
+
+## Dataclasses во вложенных структурах данных { #dataclasses-in-nested-data-structures }
+
+Вы также можете комбинировать `dataclasses` с другими аннотациями типов, чтобы создавать вложенные структуры данных.
+
+В некоторых случаях вам всё же может понадобиться использовать версию `dataclasses` из Pydantic. Например, если у вас возникают ошибки с автоматически генерируемой документацией API.
+
+В таком случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, которая является полностью совместимой заменой (drop-in replacement):
+
+{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
+
+1. Мы по-прежнему импортируем `field` из стандартных `dataclasses`.
+
+2. `pydantic.dataclasses` — полностью совместимая замена (drop-in replacement) для `dataclasses`.
+
+3. Dataclass `Author` содержит список dataclass `Item`.
+
+4. Dataclass `Author` используется в параметре `response_model`.
+
+5. Вы можете использовать и другие стандартные аннотации типов вместе с dataclasses в качестве тела запроса.
+
+ В этом случае это список dataclass `Item`.
+
+6. Здесь мы возвращаем словарь, содержащий `items`, который является списком dataclass.
+
+ FastAPI по-прежнему способен сериализовать данные в JSON.
+
+7. Здесь `response_model` использует аннотацию типа — список dataclass `Author`.
+
+ Снова, вы можете комбинировать `dataclasses` со стандартными аннотациями типов.
+
+8. Обратите внимание, что эта *функция-обработчик пути* использует обычный `def` вместо `async def`.
+
+ Как и всегда в FastAPI, вы можете сочетать `def` и `async def` по необходимости.
+
+ Если хотите освежить в памяти, когда что использовать, посмотрите раздел _"Нет времени?"_ в документации про [`async` и `await`](../async.md#in-a-hurry){.internal-link target=_blank}.
+
+9. Эта *функция-обработчик пути* возвращает не dataclasses (хотя могла бы), а список словарей с внутренними данными.
+
+ FastAPI использует параметр `response_model` (в котором заданы dataclasses), чтобы преобразовать HTTP-ответ.
+
+Вы можете комбинировать `dataclasses` с другими аннотациями типов множеством способов, чтобы формировать сложные структуры данных.
+
+Смотрите подсказки в коде выше, чтобы увидеть более конкретные детали.
+
+## Узнать больше { #learn-more }
+
+Вы также можете комбинировать `dataclasses` с другими Pydantic-моделями, наследоваться от них, включать их в свои модели и т.д.
+
+Чтобы узнать больше, посмотрите документацию Pydantic о dataclasses.
+
+## Версия { #version }
+
+Доступно начиная с версии FastAPI `0.67.0`. 🔖
diff --git a/docs/ru/docs/advanced/events.md b/docs/ru/docs/advanced/events.md
new file mode 100644
index 000000000..db73d9094
--- /dev/null
+++ b/docs/ru/docs/advanced/events.md
@@ -0,0 +1,165 @@
+# События lifespan { #lifespan-events }
+
+Вы можете определить логику (код), которую нужно выполнить перед тем, как приложение начнет запускаться. Это означает, что этот код будет выполнен один раз, перед тем как приложение начнет получать HTTP-запросы.
+
+Аналогично, вы можете определить логику (код), которую нужно выполнить, когда приложение завершает работу. В этом случае код будет выполнен один раз, после обработки, возможно, многих запросов.
+
+Поскольку этот код выполняется до того, как приложение начинает принимать запросы, и сразу после того, как оно заканчивает их обрабатывать, он охватывает весь lifespan (жизненный цикл) приложения (слово «lifespan» станет важным через секунду 😉).
+
+Это может быть очень полезно для настройки ресурсов, которые нужны для всего приложения, которые разделяются между запросами и/или которые нужно затем очистить. Например, пул подключений к базе данных или загрузка общей модели Машинного обучения.
+
+## Вариант использования { #use-case }
+
+Начнем с примера варианта использования, а затем посмотрим, как это решить.
+
+Представим, что у вас есть несколько моделей Машинного обучения, которые вы хотите использовать для обработки запросов. 🤖
+
+Эти же модели разделяются между запросами, то есть это не одна модель на запрос, не одна на пользователя и т.п.
+
+Представим, что загрузка модели может занимать довольно много времени, потому что ей нужно прочитать много данных с диска. Поэтому вы не хотите делать это для каждого запроса.
+
+Вы могли бы загрузить её на верхнем уровне модуля/файла, но это означало бы, что модель загружается даже если вы просто запускаете простой автоматический тест; тогда этот тест будет медленным, так как ему придется ждать загрузки модели перед запуском независимой части кода.
+
+Именно это мы и решим: давайте загружать модель перед тем, как начнётся обработка запросов, но только непосредственно перед тем, как приложение начнет принимать запросы, а не во время загрузки кода.
+
+## Lifespan { #lifespan }
+
+Вы можете определить логику для startup и shutdown, используя параметр `lifespan` приложения `FastAPI` и «менеджер контекста» (через секунду покажу что это).
+
+Начнем с примера, а затем разберём его подробнее.
+
+Мы создаём асинхронную функцию `lifespan()` с `yield` примерно так:
+
+{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *}
+
+Здесь мы симулируем дорогую операцию startup по загрузке модели, помещая (фиктивную) функцию модели в словарь с моделями Машинного обучения до `yield`. Этот код будет выполнен до того, как приложение начнет принимать запросы, во время startup.
+
+А затем сразу после `yield` мы выгружаем модель. Этот код будет выполнен после того, как приложение закончит обрабатывать запросы, непосредственно перед shutdown. Это может, например, освободить ресурсы, такие как память или GPU.
+
+/// tip | Совет
+
+`shutdown` произойдёт, когда вы останавливаете приложение.
+
+Возможно, вам нужно запустить новую версию, или вы просто устали от него. 🤷
+
+///
+
+### Функция lifespan { #lifespan-function }
+
+Первое, на что стоит обратить внимание, — мы определяем асинхронную функцию с `yield`. Это очень похоже на Зависимости с `yield`.
+
+{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *}
+
+Первая часть функции, до `yield`, будет выполнена до запуска приложения.
+
+А часть после `yield` будет выполнена после завершения работы приложения.
+
+### Асинхронный менеджер контекста { #async-context-manager }
+
+Если присмотреться, функция декорирована `@asynccontextmanager`.
+
+Это превращает функцию в «асинхронный менеджер контекста».
+
+{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *}
+
+Менеджер контекста в Python — это то, что можно использовать в операторе `with`. Например, `open()` можно использовать как менеджер контекста:
+
+```Python
+with open("file.txt") as file:
+ file.read()
+```
+
+В последних версиях Python есть также асинхронный менеджер контекста. Его используют с `async with`:
+
+```Python
+async with lifespan(app):
+ await do_stuff()
+```
+
+Когда вы создаёте менеджер контекста или асинхронный менеджер контекста, как выше, он перед входом в блок `with` выполнит код до `yield`, а после выхода из блока `with` выполнит код после `yield`.
+
+В нашем примере выше мы не используем его напрямую, а передаём его в FastAPI, чтобы он использовал его сам.
+
+Параметр `lifespan` приложения `FastAPI` принимает асинхронный менеджер контекста, поэтому мы можем передать ему наш новый асинхронный менеджер контекста `lifespan`.
+
+{* ../../docs_src/events/tutorial003_py39.py hl[22] *}
+
+## Альтернативные события (устаревшие) { #alternative-events-deprecated }
+
+/// warning | Предупреждение
+
+Рекомендуемый способ обрабатывать startup и shutdown — использовать параметр `lifespan` приложения `FastAPI`, как описано выше. Если вы укажете параметр `lifespan`, обработчики событий `startup` и `shutdown` больше вызываться не будут. Либо всё через `lifespan`, либо всё через события — не одновременно.
+
+Эту часть, скорее всего, можно пропустить.
+
+///
+
+Есть альтернативный способ определить логику, которую нужно выполнить во время startup и во время shutdown.
+
+Вы можете определить обработчики событий (функции), которые нужно выполнить до старта приложения или при его завершении.
+
+Эти функции можно объявить с `async def` или обычным `def`.
+
+### Событие `startup` { #startup-event }
+
+Чтобы добавить функцию, которую нужно запустить до старта приложения, объявите её как обработчик события `"startup"`:
+
+{* ../../docs_src/events/tutorial001_py39.py hl[8] *}
+
+В этом случае функция-обработчик события `startup` инициализирует «базу данных» items (это просто `dict`) некоторыми значениями.
+
+Вы можете добавить более одного обработчика события.
+
+И ваше приложение не начнет принимать запросы, пока все обработчики события `startup` не завершатся.
+
+### Событие `shutdown` { #shutdown-event }
+
+Чтобы добавить функцию, которую нужно запустить при завершении работы приложения, объявите её как обработчик события `"shutdown"`:
+
+{* ../../docs_src/events/tutorial002_py39.py hl[6] *}
+
+Здесь функция-обработчик события `shutdown` запишет строку текста `"Application shutdown"` в файл `log.txt`.
+
+/// info | Информация
+
+В функции `open()` параметр `mode="a"` означает «добавление» (append), то есть строка будет добавлена в конец файла, без перезаписи предыдущего содержимого.
+
+///
+
+/// tip | Совет
+
+Обратите внимание, что в этом случае мы используем стандартную Python-функцию `open()`, которая взаимодействует с файлом.
+
+То есть это I/O (ввод/вывод), требующий «ожидания» записи на диск.
+
+Но `open()` не использует `async` и `await`.
+
+Поэтому мы объявляем обработчик события обычным `def` вместо `async def`.
+
+///
+
+### `startup` и `shutdown` вместе { #startup-and-shutdown-together }
+
+С высокой вероятностью логика для вашего startup и shutdown связана: вы можете хотеть что-то запустить, а затем завершить, получить ресурс, а затем освободить его и т.д.
+
+Делать это в отдельных функциях, которые не разделяют общую логику или переменные, сложнее, так как придётся хранить значения в глобальных переменных или использовать похожие приёмы.
+
+Поэтому теперь рекомендуется использовать `lifespan`, как описано выше.
+
+## Технические детали { #technical-details }
+
+Немного технических подробностей для любопытных умников. 🤓
+
+Под капотом, в ASGI-технической спецификации, это часть Протокола Lifespan, и он определяет события `startup` и `shutdown`.
+
+/// info | Информация
+
+Вы можете прочитать больше про обработчики `lifespan` в Starlette в документации Starlette по Lifespan.
+
+Включая то, как работать с состоянием lifespan, которое можно использовать в других частях вашего кода.
+
+///
+
+## Подприложения { #sub-applications }
+
+🚨 Имейте в виду, что эти события lifespan (startup и shutdown) будут выполнены только для основного приложения, а не для [Подприложения — Mounts](sub-applications.md){.internal-link target=_blank}.
diff --git a/docs/ru/docs/advanced/generate-clients.md b/docs/ru/docs/advanced/generate-clients.md
new file mode 100644
index 000000000..00bdd31fe
--- /dev/null
+++ b/docs/ru/docs/advanced/generate-clients.md
@@ -0,0 +1,208 @@
+# Генерация SDK { #generating-sdks }
+
+Поскольку **FastAPI** основан на спецификации **OpenAPI**, его API можно описать в стандартном формате, понятном множеству инструментов.
+
+Это упрощает генерацию актуальной **документации**, клиентских библиотек (**SDKs**) на разных языках, а также **тестирования** или **воркфлоу автоматизации**, которые остаются синхронизированными с вашим кодом.
+
+В этом руководстве вы узнаете, как сгенерировать **TypeScript SDK** для вашего бэкенда на FastAPI.
+
+## Генераторы SDK с открытым исходным кодом { #open-source-sdk-generators }
+
+Гибкий вариант — OpenAPI Generator, который поддерживает **многие языки программирования** и умеет генерировать SDK из вашей спецификации OpenAPI.
+
+Для **TypeScript‑клиентов** Hey API — специализированное решение, обеспечивающее оптимальный опыт для экосистемы TypeScript.
+
+Больше генераторов SDK можно найти на OpenAPI.Tools.
+
+/// tip | Совет
+
+FastAPI автоматически генерирует спецификации **OpenAPI 3.1**, поэтому любой используемый инструмент должен поддерживать эту версию.
+
+///
+
+## Генераторы SDK от спонсоров FastAPI { #sdk-generators-from-fastapi-sponsors }
+
+В этом разделе представлены решения с **венчурной поддержкой** и **поддержкой компаний** от компаний, которые спонсируют FastAPI. Эти продукты предоставляют **дополнительные возможности** и **интеграции** сверх высококачественно генерируемых SDK.
+
+Благодаря ✨ [**спонсорству FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ эти компании помогают обеспечивать, чтобы фреймворк и его **экосистема** оставались здоровыми и **устойчивыми**.
+
+Их спонсорство также демонстрирует серьёзную приверженность **сообществу** FastAPI (вам), показывая, что им важно не только предоставлять **отличный сервис**, но и поддерживать **надёжный и процветающий фреймворк** FastAPI. 🙇
+
+Например, вы можете попробовать:
+
+* Speakeasy
+* Stainless
+* liblab
+
+Некоторые из этих решений также могут быть open source или иметь бесплатные тарифы, так что вы сможете попробовать их без финансовых затрат. Другие коммерческие генераторы SDK доступны и их можно найти онлайн. 🤓
+
+## Создать TypeScript SDK { #create-a-typescript-sdk }
+
+Начнём с простого приложения FastAPI:
+
+{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
+
+Обратите внимание, что *операции пути (обработчики пути)* определяют модели, которые они используют для полезной нагрузки запроса и полезной нагрузки ответа, с помощью моделей `Item` и `ResponseMessage`.
+
+### Документация API { #api-docs }
+
+Если перейти на `/docs`, вы увидите **схемы** данных, отправляемых в запросах и принимаемых в ответах:
+
+
+
+Вы видите эти схемы, потому что они были объявлены с моделями в приложении.
+
+Эта информация доступна в **схеме OpenAPI** приложения и затем отображается в документации API.
+
+Та же информация из моделей, включённая в OpenAPI, может использоваться для **генерации клиентского кода**.
+
+### Hey API { #hey-api }
+
+Как только у нас есть приложение FastAPI с моделями, мы можем использовать Hey API для генерации TypeScript‑клиента. Самый быстрый способ сделать это — через npx.
+
+```sh
+npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client
+```
+
+Это сгенерирует TypeScript SDK в `./src/client`.
+
+Вы можете узнать, как установить `@hey-api/openapi-ts` и почитать о сгенерированном результате на их сайте.
+
+### Использование SDK { #using-the-sdk }
+
+Теперь вы можете импортировать и использовать клиентский код. Это может выглядеть так, обратите внимание, что вы получаете автозавершение для методoв:
+
+
+
+Вы также получите автозавершение для отправляемой полезной нагрузки:
+
+
+
+/// tip | Совет
+
+Обратите внимание на автозавершение для `name` и `price`, это было определено в приложении FastAPI, в модели `Item`.
+
+///
+
+Вы получите ошибки прямо в редакторе для отправляемых данных:
+
+
+
+Объект ответа также будет иметь автозавершение:
+
+
+
+## Приложение FastAPI с тегами { #fastapi-app-with-tags }
+
+Во многих случаях ваше приложение FastAPI будет больше, и вы, вероятно, будете использовать теги, чтобы разделять разные группы *операций пути*.
+
+Например, у вас может быть раздел для **items** и другой раздел для **users**, и они могут быть разделены тегами:
+
+{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *}
+
+### Генерация TypeScript‑клиента с тегами { #generate-a-typescript-client-with-tags }
+
+Если вы генерируете клиент для приложения FastAPI с использованием тегов, обычно клиентский код также будет разделён по тегам.
+
+Таким образом вы сможете иметь всё правильно упорядоченным и сгруппированным в клиентском коде:
+
+
+
+В этом случае у вас есть:
+
+* `ItemsService`
+* `UsersService`
+
+### Имена методов клиента { #client-method-names }
+
+Сейчас сгенерированные имена методов вроде `createItemItemsPost` выглядят не очень аккуратно:
+
+```TypeScript
+ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
+```
+
+...это потому, что генератор клиента использует внутренний **ID операции** OpenAPI для каждой *операции пути*.
+
+OpenAPI требует, чтобы каждый ID операции был уникален среди всех *операций пути*, поэтому FastAPI использует **имя функции**, **путь** и **HTTP‑метод/операцию** для генерации этого ID операции, так как таким образом можно гарантировать уникальность ID операций.
+
+Но далее я покажу, как это улучшить. 🤓
+
+## Пользовательские ID операций и лучшие имена методов { #custom-operation-ids-and-better-method-names }
+
+Вы можете **изменить** способ **генерации** этих ID операций, чтобы сделать их проще, а имена методов в клиентах — **более простыми**.
+
+В этом случае вам нужно будет обеспечить, чтобы каждый ID операции был **уникальным** другим способом.
+
+Например, вы можете гарантировать, что у каждой *операции пути* есть тег, и затем генерировать ID операции на основе **тега** и **имени** *операции пути* (имени функции).
+
+### Пользовательская функция генерации уникального ID { #custom-generate-unique-id-function }
+
+FastAPI использует **уникальный ID** для каждой *операции пути*, который применяется для **ID операции**, а также для имён любых необходимых пользовательских моделей запросов или ответов.
+
+Вы можете кастомизировать эту функцию. Она принимает `APIRoute` и возвращает строку.
+
+Например, здесь берётся первый тег (скорее всего у вас один тег) и имя *операции пути* (имя функции).
+
+Затем вы можете передать эту пользовательскую функцию в **FastAPI** через параметр `generate_unique_id_function`:
+
+{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *}
+
+### Генерация TypeScript‑клиента с пользовательскими ID операций { #generate-a-typescript-client-with-custom-operation-ids }
+
+Теперь, если снова сгенерировать клиент, вы увидите, что имена методов улучшились:
+
+
+
+Как видите, теперь имена методов содержат тег, а затем имя функции; больше они не включают информацию из URL‑пути и HTTP‑операции.
+
+### Предобработка спецификации OpenAPI для генератора клиента { #preprocess-the-openapi-specification-for-the-client-generator }
+
+Сгенерированном коде всё ещё есть **дублирующаяся информация**.
+
+Мы уже знаем, что этот метод относится к **items**, потому что это слово есть в `ItemsService` (взято из тега), но при этом имя тега всё ещё добавлено префиксом к имени метода. 😕
+
+Скорее всего мы захотим оставить это в OpenAPI в целом, так как это гарантирует, что ID операций будут **уникальны**.
+
+Но для сгенерированного клиента мы можем **модифицировать** ID операций OpenAPI непосредственно перед генерацией клиентов, чтобы сделать имена методов более приятными и **чистыми**.
+
+Мы можем скачать OpenAPI JSON в файл `openapi.json`, а затем **убрать этот префикс‑тег** таким скриптом:
+
+{* ../../docs_src/generate_clients/tutorial004_py39.py *}
+
+//// tab | Node.js
+
+```Javascript
+{!> ../../docs_src/generate_clients/tutorial004.js!}
+```
+
+////
+
+После этого ID операций будут переименованы с чего‑то вроде `items-get_items` просто в `get_items`, и генератор клиента сможет создавать более простые имена методов.
+
+### Генерация TypeScript‑клиента с предобработанным OpenAPI { #generate-a-typescript-client-with-the-preprocessed-openapi }
+
+Так как конечный результат теперь в файле `openapi.json`, нужно обновить входное расположение:
+
+```sh
+npx @hey-api/openapi-ts -i ./openapi.json -o src/client
+```
+
+После генерации нового клиента у вас будут **чистые имена методов**, со всем **автозавершением**, **ошибками прямо в редакторе** и т.д.:
+
+
+
+## Преимущества { #benefits }
+
+При использовании автоматически сгенерированных клиентов вы получите **автозавершение** для:
+
+* Методов.
+* Данных запроса — в теле запроса, query‑параметрах и т.д.
+* Данных ответа.
+
+У вас также будут **ошибки прямо в редакторе** для всего.
+
+И каждый раз, когда вы обновляете код бэкенда и **перегенерируете** фронтенд, в нём появятся новые *операции пути* как методы, старые будут удалены, а любые другие изменения отразятся в сгенерированном коде. 🤓
+
+Это также означает, что если что‑то изменилось, это будет **отражено** в клиентском коде автоматически. И если вы **соберёте** клиент, он завершится с ошибкой, если где‑то есть **несоответствие** в используемых данных.
+
+Таким образом, вы **обнаружите многие ошибки** очень рано в цикле разработки, вместо того чтобы ждать, когда ошибки проявятся у конечных пользователей в продакшн, и затем пытаться отладить, в чём проблема. ✨
diff --git a/docs/ru/docs/advanced/index.md b/docs/ru/docs/advanced/index.md
index b5cb733e7..c0a52c6c1 100644
--- a/docs/ru/docs/advanced/index.md
+++ b/docs/ru/docs/advanced/index.md
@@ -1,12 +1,12 @@
-# Расширенное руководство пользователя
+# Расширенное руководство пользователя { #advanced-user-guide }
-## Дополнительные возможности
+## Дополнительные возможности { #additional-features }
Основное [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} должно быть достаточно, чтобы познакомить вас со всеми основными функциями **FastAPI**.
В следующих разделах вы увидите другие варианты, конфигурации и дополнительные возможности.
-/// tip
+/// tip | Совет
Следующие разделы **не обязательно являются "продвинутыми"**.
@@ -14,7 +14,7 @@
///
-## Сначала прочитайте Учебник - Руководство пользователя
+## Сначала прочитайте Учебник - Руководство пользователя { #read-the-tutorial-first }
Вы все еще можете использовать большинство функций **FastAPI** со знаниями из [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank}.
diff --git a/docs/ru/docs/advanced/middleware.md b/docs/ru/docs/advanced/middleware.md
new file mode 100644
index 000000000..5ebe01078
--- /dev/null
+++ b/docs/ru/docs/advanced/middleware.md
@@ -0,0 +1,97 @@
+# Расширенное использование middleware { #advanced-middleware }
+
+В основном руководстве вы читали, как добавить [пользовательское middleware](../tutorial/middleware.md){.internal-link target=_blank} в ваше приложение.
+
+А затем — как работать с [CORS с помощью `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}.
+
+В этом разделе посмотрим, как использовать другие middleware.
+
+## Добавление ASGI middleware { #adding-asgi-middlewares }
+
+Так как **FastAPI** основан на Starlette и реализует спецификацию ASGI, вы можете использовать любое ASGI middleware.
+
+Middleware не обязательно должно быть сделано специально для FastAPI или Starlette — достаточно, чтобы оно соответствовало спецификации ASGI.
+
+В общем случае ASGI middleware — это классы, которые ожидают получить ASGI‑приложение первым аргументом.
+
+Поэтому в документации к сторонним ASGI middleware, скорее всего, вы увидите что‑то вроде:
+
+```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()` принимает класс middleware в качестве первого аргумента и любые дополнительные аргументы, которые будут переданы этому middleware.
+
+## Встроенные middleware { #integrated-middlewares }
+
+**FastAPI** включает несколько middleware для распространённых сценариев. Ниже рассмотрим, как их использовать.
+
+/// note | Технические детали
+
+В следующих примерах вы также можете использовать `from starlette.middleware.something import SomethingMiddleware`.
+
+**FastAPI** предоставляет несколько middleware в `fastapi.middleware` для удобства разработчика. Но большинство доступных middleware приходит напрямую из Starlette.
+
+///
+
+## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware }
+
+Гарантирует, что все входящие запросы должны использовать либо `https`, либо `wss`.
+
+Любой входящий запрос по `http` или `ws` будет перенаправлен на безопасную схему.
+
+{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *}
+
+## `TrustedHostMiddleware` { #trustedhostmiddleware }
+
+Гарантирует, что во всех входящих запросах корректно установлен `Host`‑заголовок, чтобы защититься от атак на HTTP‑заголовок Host.
+
+{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *}
+
+Поддерживаются следующие аргументы:
+
+- `allowed_hosts` — список доменных имён, которые следует разрешить как имена хостов. Подстановки вида `*.example.com` поддерживаются для сопоставления поддоменов. Чтобы разрешить любой хост, используйте либо `allowed_hosts=["*"]`, либо не добавляйте это middleware.
+- `www_redirect` — если установлено в True, запросы к не‑www версиям разрешённых хостов будут перенаправляться на их www‑аналоги. По умолчанию — `True`.
+
+Если входящий запрос не проходит валидацию, будет отправлен ответ `400`.
+
+## `GZipMiddleware` { #gzipmiddleware }
+
+Обрабатывает GZip‑ответы для любых запросов, которые включают `"gzip"` в заголовке `Accept-Encoding`.
+
+Это middleware обрабатывает как обычные, так и потоковые ответы.
+
+{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *}
+
+Поддерживаются следующие аргументы:
+
+- `minimum_size` — не сжимать GZip‑ом ответы, размер которых меньше этого минимального значения в байтах. По умолчанию — `500`.
+- `compresslevel` — уровень GZip‑сжатия. Целое число от 1 до 9. По умолчанию — `9`. Более низкое значение — быстреее сжатие, но больший размер файла; более высокое значение — более медленное сжатие, но меньший размер файла.
+
+## Другие middleware { #other-middlewares }
+
+Существует много других ASGI middleware.
+
+Например:
+
+- `ProxyHeadersMiddleware` от Uvicorn
+- MessagePack
+
+Чтобы увидеть другие доступные middleware, посмотрите документацию по middleware в Starlette и список ASGI Awesome.
diff --git a/docs/ru/docs/advanced/openapi-callbacks.md b/docs/ru/docs/advanced/openapi-callbacks.md
new file mode 100644
index 000000000..de7e28301
--- /dev/null
+++ b/docs/ru/docs/advanced/openapi-callbacks.md
@@ -0,0 +1,186 @@
+# Обратные вызовы в OpenAPI { #openapi-callbacks }
+
+Вы можете создать API с *операцией пути* (обработчиком пути), которая будет инициировать HTTP-запрос к *внешнему API*, созданному кем-то другим (скорее всего тем же разработчиком, который будет использовать ваш API).
+
+Процесс, происходящий, когда ваше приложение API обращается к *внешнему API*, называется «callback» (обратный вызов). Программное обеспечение, написанное внешним разработчиком, отправляет HTTP-запрос вашему API, а затем ваш API выполняет обратный вызов, отправляя HTTP-запрос во *внешний API* (который, вероятно, тоже создал тот же разработчик).
+
+В этом случае вам может понадобиться задокументировать, как должно выглядеть это внешнее API: какую *операцию пути* оно должно иметь, какое тело запроса ожидать, какой ответ возвращать и т.д.
+
+## Приложение с обратными вызовами { #an-app-with-callbacks }
+
+Давайте рассмотрим это на примере.
+
+Представьте, что вы разрабатываете приложение, позволяющее создавать счета.
+
+Эти счета будут иметь `id`, `title` (необязательный), `customer` и `total`.
+
+Пользователь вашего API (внешний разработчик) создаст счет в вашем API с помощью POST-запроса.
+
+Затем ваш API (предположим) сделает следующее:
+
+* Отправит счет клиенту внешнего разработчика.
+* Получит оплату.
+* Отправит уведомление обратно пользователю API (внешнему разработчику).
+ * Это будет сделано отправкой POST-запроса (из *вашего API*) в *внешний API*, предоставленный этим внешним разработчиком (это и есть «callback»).
+
+## Обычное приложение **FastAPI** { #the-normal-fastapi-app }
+
+Сначала посмотрим, как будет выглядеть обычное приложение API до добавления обратного вызова.
+
+В нём будет *операция пути*, которая получит тело запроса `Invoice`, и query-параметр `callback_url`, содержащий URL для обратного вызова.
+
+Эта часть вполне обычна, большая часть кода вам уже знакома:
+
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
+
+/// tip | Совет
+
+Query-параметр `callback_url` использует тип Pydantic Url.
+
+///
+
+Единственное новое — это `callbacks=invoices_callback_router.routes` в качестве аргумента *декоратора операции пути*. Далее разберёмся, что это такое.
+
+## Документирование обратного вызова { #documenting-the-callback }
+
+Реальный код обратного вызова будет сильно зависеть от вашего приложения API.
+
+И, вероятно, он будет заметно отличаться от одного приложения к другому.
+
+Это могут быть буквально одна-две строки кода, например:
+
+```Python
+callback_url = "https://example.com/api/v1/invoices/events/"
+httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
+```
+
+Но, возможно, самая важная часть обратного вызова — это убедиться, что пользователь вашего API (внешний разработчик) правильно реализует *внешний API* в соответствии с данными, которые *ваш API* будет отправлять в теле запроса обратного вызова и т.п.
+
+Поэтому далее мы добавим код, документирующий, как должен выглядеть этот *внешний API*, чтобы получать обратный вызов от *вашего API*.
+
+Эта документация отобразится в Swagger UI по адресу `/docs` в вашем API и позволит внешним разработчикам понять, как построить *внешний API*.
+
+В этом примере сам обратный вызов не реализуется (это может быть всего одна строка кода), реализуется только часть с документацией.
+
+/// tip | Совет
+
+Сам обратный вызов — это всего лишь HTTP-запрос.
+
+Реализуя обратный вызов, вы можете использовать, например, HTTPX или Requests.
+
+///
+
+## Напишите код документации обратного вызова { #write-the-callback-documentation-code }
+
+Этот код не будет выполняться в вашем приложении, он нужен только для *документирования* того, как должен выглядеть *внешний API*.
+
+Но вы уже знаете, как легко получить автоматическую документацию для API с **FastAPI**.
+
+Мы используем те же знания, чтобы задокументировать, как должен выглядеть *внешний API*... создав *операции пути*, которые внешний API должен реализовать (те, которые ваш API будет вызывать).
+
+/// tip | Совет
+
+Когда вы пишете код для документирования обратного вызова, полезно представить, что вы — тот самый *внешний разработчик*. И что вы сейчас реализуете *внешний API*, а не *свой API*.
+
+Временное принятие этой точки зрения (внешнего разработчика) поможет интуитивно понять, куда поместить параметры, какую Pydantic-модель использовать для тела запроса, для ответа и т.д. во *внешнем API*.
+
+///
+
+### Создайте `APIRouter` для обратного вызова { #create-a-callback-apirouter }
+
+Сначала создайте новый `APIRouter`, который будет содержать один или несколько обратных вызовов.
+
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
+
+### Создайте *операцию пути* для обратного вызова { #create-the-callback-path-operation }
+
+Чтобы создать *операцию пути* для обратного вызова, используйте тот же `APIRouter`, который вы создали выше.
+
+Она должна выглядеть как обычная *операция пути* FastAPI:
+
+* Вероятно, в ней должно быть объявление тела запроса, например `body: InvoiceEvent`.
+* А также может быть объявление модели ответа, например `response_model=InvoiceEventReceived`.
+
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *}
+
+Есть 2 основных отличия от обычной *операции пути*:
+
+* Ей не нужен реальный код, потому что ваше приложение никогда не будет вызывать эту функцию. Она используется только для документирования *внешнего API*. Поэтому в функции может быть просто `pass`.
+* *Путь* может содержать выражение OpenAPI 3 (подробнее ниже), где можно использовать переменные с параметрами и части исходного HTTP-запроса, отправленного *вашему API*.
+
+### Выражение пути для обратного вызова { #the-callback-path-expression }
+
+*Путь* обратного вызова может содержать выражение OpenAPI 3, которое может включать части исходного запроса, отправленного *вашему API*.
+
+В нашем случае это `str`:
+
+```Python
+"{$callback_url}/invoices/{$request.body.id}"
+```
+
+Итак, если пользователь вашего API (внешний разработчик) отправляет HTTP-запрос вашему API по адресу:
+
+```
+https://yourapi.com/invoices/?callback_url=https://www.external.org/events
+```
+
+с телом JSON:
+
+```JSON
+{
+ "id": "2expen51ve",
+ "customer": "Mr. Richie Rich",
+ "total": "9999"
+}
+```
+
+то *ваш API* обработает счёт и, в какой-то момент позже, отправит запрос обратного вызова на `callback_url` (*внешний API*):
+
+```
+https://www.external.org/events/invoices/2expen51ve
+```
+
+с телом JSON примерно такого вида:
+
+```JSON
+{
+ "description": "Payment celebration",
+ "paid": true
+}
+```
+
+и будет ожидать от *внешнего API* ответ с телом JSON вида:
+
+```JSON
+{
+ "ok": true
+}
+```
+
+/// tip | Совет
+
+Обратите внимание, что используемый URL обратного вызова содержит URL, полученный как query-параметр в `callback_url` (`https://www.external.org/events`), а также `id` счёта из тела JSON (`2expen51ve`).
+
+///
+
+### Подключите маршрутизатор обратного вызова { #add-the-callback-router }
+
+К этому моменту у вас есть необходимые *операции пути* обратного вызова (те, которые *внешний разработчик* должен реализовать во *внешнем API*) в созданном выше маршрутизаторе обратных вызовов.
+
+Теперь используйте параметр `callbacks` в *декораторе операции пути вашего API*, чтобы передать атрибут `.routes` (это, по сути, просто `list` маршрутов/*операций пути*) из этого маршрутизатора обратных вызовов:
+
+{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
+
+/// tip | Совет
+
+Обратите внимание, что вы передаёте не сам маршрутизатор (`invoices_callback_router`) в `callback=`, а его атрибут `.routes`, то есть `invoices_callback_router.routes`.
+
+///
+
+### Проверьте документацию { #check-the-docs }
+
+Теперь вы можете запустить приложение и перейти по адресу http://127.0.0.1:8000/docs.
+
+Вы увидите документацию, включающую раздел «Callbacks» для вашей *операции пути*, который показывает, как должен выглядеть *внешний API*:
+
+
diff --git a/docs/ru/docs/advanced/openapi-webhooks.md b/docs/ru/docs/advanced/openapi-webhooks.md
new file mode 100644
index 000000000..3a2b9fff7
--- /dev/null
+++ b/docs/ru/docs/advanced/openapi-webhooks.md
@@ -0,0 +1,55 @@
+# Вебхуки OpenAPI { #openapi-webhooks }
+
+Бывают случаи, когда вы хотите сообщить пользователям вашего API, что ваше приложение может вызвать их приложение (отправив HTTP-запрос) с некоторыми данными, обычно чтобы уведомить о каком-то событии.
+
+Это означает, что вместо обычного процесса, когда пользователи отправляют запросы вашему API, ваш API (или ваше приложение) может отправлять запросы в их систему (в их API, их приложение).
+
+Обычно это называется вебхуком.
+
+## Шаги вебхуков { #webhooks-steps }
+
+Обычно процесс таков: вы определяете в своем коде, какое сообщение вы будете отправлять, то есть тело запроса.
+
+Вы также определяете, в какие моменты (при каких событиях) ваше приложение будет отправлять эти запросы.
+
+А ваши пользователи каким-то образом (например, в веб‑панели) указывают URL-адрес, на который ваше приложение должно отправлять эти запросы.
+
+Вся логика регистрации URL-адресов для вебхуков и код, который реально отправляет эти запросы, целиком на вашей стороне. Вы пишете это так, как вам нужно, в своем собственном коде.
+
+## Документирование вебхуков с помощью FastAPI и OpenAPI { #documenting-webhooks-with-fastapi-and-openapi }
+
+С FastAPI, используя OpenAPI, вы можете определить имена этих вебхуков, типы HTTP-операций, которые ваше приложение может отправлять (например, `POST`, `PUT` и т.д.), а также тела запросов, которые ваше приложение будет отправлять.
+
+Это значительно упростит вашим пользователям реализацию их API для приема ваших вебхук-запросов; возможно, они даже смогут автоматически сгенерировать часть кода своего API.
+
+/// info | Информация
+
+Вебхуки доступны в OpenAPI 3.1.0 и выше, поддерживаются в FastAPI `0.99.0` и новее.
+
+///
+
+## Приложение с вебхуками { #an-app-with-webhooks }
+
+При создании приложения на **FastAPI** есть атрибут `webhooks`, с помощью которого можно объявлять вебхуки так же, как вы объявляете операции пути (обработчики пути), например с `@app.webhooks.post()`.
+
+{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *}
+
+Определенные вами вебхуки попадут в схему **OpenAPI** и в автоматический **интерфейс документации**.
+
+/// info | Информация
+
+Объект `app.webhooks` на самом деле — это обычный `APIRouter`, тот же тип, который вы используете при структурировании приложения по нескольким файлам.
+
+///
+
+Обратите внимание: в случае с вебхуками вы на самом деле не объявляете путь (например, `/items/`), передаваемый туда текст — это лишь идентификатор вебхука (имя события). Например, в `@app.webhooks.post("new-subscription")` имя вебхука — `new-subscription`.
+
+Это связано с тем, что предполагается: фактический URL‑путь, по которому они хотят получать запрос вебхука, ваши пользователи укажут каким-то другим образом (например, в веб‑панели).
+
+### Посмотрите документацию { #check-the-docs }
+
+Теперь вы можете запустить приложение и перейти по ссылке http://127.0.0.1:8000/docs.
+
+Вы увидите, что в документации есть обычные операции пути, а также появились вебхуки:
+
+
diff --git a/docs/ru/docs/advanced/path-operation-advanced-configuration.md b/docs/ru/docs/advanced/path-operation-advanced-configuration.md
new file mode 100644
index 000000000..86d3a5b63
--- /dev/null
+++ b/docs/ru/docs/advanced/path-operation-advanced-configuration.md
@@ -0,0 +1,172 @@
+# Расширенная конфигурация операций пути { #path-operation-advanced-configuration }
+
+## OpenAPI operationId { #openapi-operationid }
+
+/// warning | Предупреждение
+
+Если вы не «эксперт» по OpenAPI, скорее всего, это вам не нужно.
+
+///
+
+Вы можете задать OpenAPI `operationId`, который будет использоваться в вашей *операции пути*, с помощью параметра `operation_id`.
+
+Нужно убедиться, что он уникален для каждой операции.
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
+
+### Использование имени *функции-обработчика пути* как operationId { #using-the-path-operation-function-name-as-the-operationid }
+
+Если вы хотите использовать имена функций ваших API в качестве `operationId`, вы можете пройти по всем из них и переопределить `operation_id` каждой *операции пути* с помощью их `APIRoute.name`.
+
+Делать это следует после добавления всех *операций пути*.
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *}
+
+/// tip | Совет
+
+Если вы вызываете `app.openapi()` вручную, обновите `operationId` до этого.
+
+///
+
+/// warning | Предупреждение
+
+Если вы делаете это, убедитесь, что каждая из ваших *функций-обработчиков пути* имеет уникальное имя.
+
+Даже если они находятся в разных модулях (файлах Python).
+
+///
+
+## Исключить из OpenAPI { #exclude-from-openapi }
+
+Чтобы исключить *операцию пути* из генерируемой схемы OpenAPI (а значит, и из автоматических систем документации), используйте параметр `include_in_schema` и установите его в `False`:
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *}
+
+## Расширенное описание из docstring { #advanced-description-from-docstring }
+
+Вы можете ограничить количество строк из docstring *функции-обработчика пути*, используемых для OpenAPI.
+
+Добавление `\f` (экранированного символа «form feed») заставит **FastAPI** обрезать текст, используемый для OpenAPI, в этой точке.
+
+Это не отобразится в документации, но другие инструменты (например, Sphinx) смогут использовать остальное.
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
+
+## Дополнительные ответы { #additional-responses }
+
+Вы, вероятно, уже видели, как объявлять `response_model` и `status_code` для *операции пути*.
+
+Это определяет метаданные об основном HTTP-ответе *операции пути*.
+
+Также можно объявлять дополнительные ответы с их моделями, статус-кодами и т.д.
+
+В документации есть целая глава об этом — [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
+
+## Дополнительные данные OpenAPI { #openapi-extra }
+
+Когда вы объявляете *операцию пути* в своём приложении, **FastAPI** автоматически генерирует соответствующие метаданные об этой *операции пути* для включения в схему OpenAPI.
+
+/// note | Технические детали
+
+В спецификации OpenAPI это называется Объект операции.
+
+///
+
+Он содержит всю информацию об *операции пути* и используется для генерации автоматической документации.
+
+Там есть `tags`, `parameters`, `requestBody`, `responses` и т.д.
+
+Эта специфичная для *операции пути* схема OpenAPI обычно генерируется автоматически **FastAPI**, но вы также можете её расширить.
+
+/// tip | Совет
+
+Это низкоуровневая возможность расширения.
+
+Если вам нужно лишь объявить дополнительные ответы, удобнее сделать это через [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
+
+///
+
+Вы можете расширить схему OpenAPI для *операции пути* с помощью параметра `openapi_extra`.
+
+### Расширения OpenAPI { #openapi-extensions }
+
+`openapi_extra` может пригодиться, например, чтобы объявить [Расширения OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *}
+
+Если вы откроете автоматическую документацию API, ваше расширение появится внизу страницы конкретной *операции пути*.
+
+
+
+И если вы посмотрите на итоговый OpenAPI (по адресу `/openapi.json` вашего API), вы также увидите своё расширение в составе описания соответствующей *операции пути*:
+
+```JSON hl_lines="22"
+{
+ "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": {}
+ }
+ }
+ }
+ },
+ "x-aperture-labs-portal": "blue"
+ }
+ }
+ }
+}
+```
+
+### Пользовательская схема OpenAPI для *операции пути* { #custom-openapi-path-operation-schema }
+
+Словарь в `openapi_extra` будет глубоко объединён с автоматически сгенерированной схемой OpenAPI для *операции пути*.
+
+Таким образом, вы можете добавить дополнительные данные к автоматически сгенерированной схеме.
+
+Например, вы можете решить читать и валидировать HTTP-запрос своим кодом, не используя автоматические возможности FastAPI и Pydantic, но при этом захотите описать HTTP-запрос в схеме OpenAPI.
+
+Это можно сделать с помощью `openapi_extra`:
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *}
+
+В этом примере мы не объявляли никакую Pydantic-модель. Фактически тело запроса даже не распарсено как JSON, оно читается напрямую как `bytes`, а функция `magic_data_reader()` будет отвечать за его парсинг каким-то способом.
+
+Тем не менее, мы можем объявить ожидаемую схему для тела запроса.
+
+### Пользовательский тип содержимого в OpenAPI { #custom-openapi-content-type }
+
+Используя тот же приём, вы можете воспользоваться Pydantic-моделью, чтобы определить JSON Schema, которая затем будет включена в пользовательский раздел схемы OpenAPI для *операции пути*.
+
+И вы можете сделать это, даже если тип данных в HTTP-запросе — не JSON.
+
+Например, в этом приложении мы не используем встроенную функциональность FastAPI для извлечения JSON Schema из моделей Pydantic, равно как и автоматическую валидацию JSON. Мы объявляем тип содержимого HTTP-запроса как YAML, а не JSON:
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
+
+Тем не менее, хотя мы не используем встроенную функциональность по умолчанию, мы всё равно используем Pydantic-модель, чтобы вручную сгенерировать JSON Schema для данных, которые мы хотим получить в YAML.
+
+Затем мы работаем с HTTP-запросом напрямую и извлекаем тело как `bytes`. Это означает, что FastAPI даже не попытается распарсить полезную нагрузку HTTP-запроса как JSON.
+
+А затем в нашем коде мы напрямую парсим это содержимое YAML и снова используем ту же Pydantic-модель, чтобы валидировать YAML-содержимое:
+
+{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
+
+/// tip | Совет
+
+Здесь мы переиспользуем ту же Pydantic-модель.
+
+Но аналогично мы могли бы валидировать данные и каким-то другим способом.
+
+///
diff --git a/docs/ru/docs/advanced/response-change-status-code.md b/docs/ru/docs/advanced/response-change-status-code.md
index 37dade99f..85d9050ff 100644
--- a/docs/ru/docs/advanced/response-change-status-code.md
+++ b/docs/ru/docs/advanced/response-change-status-code.md
@@ -1,31 +1,31 @@
-# Response - Изменение cтатус кода
+# Response - Изменение статус-кода { #response-change-status-code }
-Вы, вероятно, уже читали о том, что можно установить [Состояние ответа по умолчанию](../tutorial/response-status-code.md){.internal-link target=_blank}.
+Вы, вероятно, уже читали о том, что можно установить [статус-код ответа по умолчанию](../tutorial/response-status-code.md){.internal-link target=_blank}.
-Но в некоторых случаях вам нужно вернуть код состояния, отличный от установленного по умолчанию.
+Но в некоторых случаях нужно вернуть другой статус-код, отличный от значения по умолчанию.
-## Пример использования
+## Пример использования { #use-case }
-Например, представьте, что вы хотите возвращать HTTP код состояния "OK" `200` по умолчанию.
+Например, представьте, что вы хотите по умолчанию возвращать HTTP статус-код «OK» `200`.
-Но если данные не существовали, вы хотите создать их и вернуть HTTP код состояния "CREATED" `201`.
+Но если данные не существовали, вы хотите создать их и вернуть HTTP статус-код «CREATED» `201`.
При этом вы всё ещё хотите иметь возможность фильтровать и преобразовывать возвращаемые данные с помощью `response_model`.
Для таких случаев вы можете использовать параметр `Response`.
-## Использование параметра `Response`
+## Использование параметра `Response` { #use-a-response-parameter }
-Вы можете объявить параметр типа `Response` в вашей *функции обработки пути* (так же как для cookies и headers).
+Вы можете объявить параметр типа `Response` в вашей *функции обработки пути* (как и для cookies и HTTP-заголовков).
И затем вы можете установить `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`, модель базы данных и т.д.).
И если вы объявили `response_model`, он всё равно будет использоваться для фильтрации и преобразования возвращаемого объекта.
-**FastAPI** будет использовать этот *временный* ответ для извлечения кода состояния (а также cookies и headers) и поместит их в финальный ответ, который содержит возвращаемое вами значение, отфильтрованное любым `response_model`.
+**FastAPI** будет использовать этот *временный* ответ для извлечения статус-кода (а также cookies и HTTP-заголовков) и поместит их в финальный ответ, который содержит возвращаемое вами значение, отфильтрованное любым `response_model`.
-Вы также можете объявить параметр `Response` в зависимостях и установить код состояния в них. Но помните, что последнее установленное значение будет иметь приоритет.
+Вы также можете объявить параметр `Response` в зависимостях и установить в них статус-код. Но помните, что последнее установленное значение будет иметь приоритет.
diff --git a/docs/ru/docs/advanced/response-cookies.md b/docs/ru/docs/advanced/response-cookies.md
index e04ff577c..2872d6c0a 100644
--- a/docs/ru/docs/advanced/response-cookies.md
+++ b/docs/ru/docs/advanced/response-cookies.md
@@ -1,48 +1,51 @@
+# Cookies в ответе { #response-cookies }
-# Cookies в ответе
+## Использование параметра `Response` { #use-a-response-parameter }
-## Использование параметра `Response`
-
-Вы можете объявить параметр типа `Response` в вашей функции эндпоинта.
+Вы можете объявить параметр типа `Response` в вашей функции-обработчике пути.
Затем установить cookies в этом временном объекте ответа.
-{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *}
+{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *}
После этого можно вернуть любой объект, как и раньше (например, `dict`, объект модели базы данных и так далее).
Если вы указали `response_model`, он всё равно будет использоваться для фильтрации и преобразования возвращаемого объекта.
-**FastAPI** извлечет cookies (а также заголовки и коды состояния) из временного ответа и включит их в окончательный ответ, содержащий ваше возвращаемое значение, отфильтрованное через `response_model`.
+**FastAPI** извлечет cookies (а также HTTP-заголовки и статус-код) из временного ответа и включит их в окончательный ответ, содержащий ваше возвращаемое значение, отфильтрованное через `response_model`.
-Вы также можете объявить параметр типа Response в зависимостях и устанавливать cookies (и заголовки) там.
+Вы также можете объявить параметр типа `Response` в зависимостях и устанавливать cookies (и HTTP-заголовки) там.
-## Возвращение `Response` напрямую
+## Возвращение `Response` напрямую { #return-a-response-directly }
-Вы также можете установить cookies, если возвращаете `Response` напрямую в вашем коде.
+Вы также можете установить Cookies, если возвращаете `Response` напрямую в вашем коде.
-Для этого создайте объект `Response`, как описано в разделе [Возвращение ответа напрямую](response-directly.md){.target=_blank}.
+Для этого создайте объект `Response`, как описано в разделе [Возвращение ответа напрямую](response-directly.md){.internal-link target=_blank}.
Затем установите cookies и верните этот объект:
-{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
+{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *}
+
+/// tip | Совет
-/// tip | Примечание
-Имейте в виду, что если вы возвращаете ответ напрямую, вместо использования параметра `Response`, **FastAPI** отправит его без дополнительной обработки.
+Имейте в виду, что если вы возвращаете ответ напрямую, вместо использования параметра `Response`, FastAPI вернёт его напрямую.
-Убедитесь, что ваши данные имеют корректный тип. Например, они должны быть совместимы с JSON, если вы используете `JSONResponse`.
+Убедитесь, что ваши данные имеют корректный тип. Например, они должны быть совместимы с JSON, если вы возвращаете `JSONResponse`.
Также убедитесь, что вы не отправляете данные, которые должны были быть отфильтрованы через `response_model`.
+
///
-### Дополнительная информация
+### Дополнительная информация { #more-info }
/// note | Технические детали
+
Вы также можете использовать `from starlette.responses import Response` или `from starlette.responses import JSONResponse`.
**FastAPI** предоставляет `fastapi.responses`, которые являются теми же объектами, что и `starlette.responses`, просто для удобства. Однако большинство доступных типов ответов поступает непосредственно из **Starlette**.
-Для установки заголовков и cookies `Response` используется часто, поэтому **FastAPI** также предоставляет его через `fastapi.responses`.
+И так как `Response` часто используется для установки HTTP-заголовков и cookies, **FastAPI** также предоставляет его как `fastapi.Response`.
+
///
-Чтобы увидеть все доступные параметры и настройки, ознакомьтесь с документацией Starlette.
+Чтобы увидеть все доступные параметры и настройки, ознакомьтесь с документацией Starlette.
diff --git a/docs/ru/docs/advanced/response-directly.md b/docs/ru/docs/advanced/response-directly.md
index ee83d22b1..b45281071 100644
--- a/docs/ru/docs/advanced/response-directly.md
+++ b/docs/ru/docs/advanced/response-directly.md
@@ -1,4 +1,4 @@
-# Возврат ответа напрямую
+# Возврат ответа напрямую { #return-a-response-directly }
Когда вы создаёте **FastAPI** *операцию пути*, вы можете возвращать из неё любые данные: `dict`, `list`, Pydantic-модель, модель базы данных и т.д.
@@ -8,9 +8,9 @@
Но вы можете возвращать `JSONResponse` напрямую из ваших *операций пути*.
-Это может быть полезно, например, если нужно вернуть пользовательские заголовки или куки.
+Это может быть полезно, например, если нужно вернуть пользовательские HTTP-заголовки или cookie.
-## Возврат `Response`
+## Возврат `Response` { #return-a-response }
На самом деле, вы можете возвращать любой объект `Response` или его подкласс.
@@ -26,7 +26,7 @@
Это даёт вам большую гибкость. Вы можете возвращать любые типы данных, переопределять любые объявления или валидацию данных и т.д.
-## Использование `jsonable_encoder` в `Response`
+## Использование `jsonable_encoder` в `Response` { #using-the-jsonable-encoder-in-a-response }
Поскольку **FastAPI** не изменяет объект `Response`, который вы возвращаете, вы должны убедиться, что его содержимое готово к отправке.
@@ -34,7 +34,7 @@
В таких случаях вы можете использовать `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 | Технические детали
@@ -44,7 +44,7 @@
///
-## Возврат пользовательского `Response`
+## Возврат пользовательского `Response` { #returning-a-custom-response }
Пример выше показывает все необходимые части, но он пока не очень полезен, так как вы могли бы просто вернуть `item` напрямую, и **FastAPI** поместил бы его в `JSONResponse`, преобразовав в `dict` и т.д. Всё это происходит по умолчанию.
@@ -54,9 +54,9 @@
Вы можете поместить ваш XML-контент в строку, поместить её в `Response` и вернуть:
-{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
+{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
-## Примечания
+## Примечания { #notes }
Когда вы возвращаете объект `Response` напрямую, его данные не валидируются, не преобразуются (не сериализуются) и не документируются автоматически.
diff --git a/docs/ru/docs/advanced/response-headers.md b/docs/ru/docs/advanced/response-headers.md
new file mode 100644
index 000000000..8f24f05b0
--- /dev/null
+++ b/docs/ru/docs/advanced/response-headers.md
@@ -0,0 +1,41 @@
+# HTTP-заголовки ответа { #response-headers }
+
+## Использовать параметр `Response` { #use-a-response-parameter }
+
+Вы можете объявить параметр типа `Response` в вашей функции-обработчике пути (как можно сделать и для cookie).
+
+А затем вы можете устанавливать HTTP-заголовки в этом *временном* объекте ответа.
+
+{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *}
+
+После этого вы можете вернуть любой нужный объект, как обычно (например, `dict`, модель из базы данных и т.д.).
+
+И, если вы объявили `response_model`, он всё равно будет использован для фильтрации и преобразования возвращённого объекта.
+
+**FastAPI** использует этот *временный* ответ, чтобы извлечь HTTP-заголовки (а также cookie и статус-код) и поместит их в финальный HTTP-ответ, который содержит возвращённое вами значение, отфильтрованное согласно `response_model`.
+
+Вы также можете объявлять параметр `Response` в зависимостях и устанавливать в них заголовки (и cookie).
+
+## Вернуть `Response` напрямую { #return-a-response-directly }
+
+Вы также можете добавить HTTP-заголовки, когда возвращаете `Response` напрямую.
+
+Создайте ответ, как описано в [Вернуть Response напрямую](response-directly.md){.internal-link target=_blank}, и передайте заголовки как дополнительный параметр:
+
+{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *}
+
+/// note | Технические детали
+
+Вы также можете использовать `from starlette.responses import Response` или `from starlette.responses import JSONResponse`.
+
+**FastAPI** предоставляет те же самые `starlette.responses` как `fastapi.responses` — для вашего удобства как разработчика. Но большинство доступных классов ответов поступают напрямую из Starlette.
+
+И поскольку `Response` часто используется для установки заголовков и cookie, **FastAPI** также предоставляет его как `fastapi.Response`.
+
+///
+
+## Пользовательские HTTP-заголовки { #custom-headers }
+
+Помните, что собственные проприетарные заголовки можно добавлять, используя префикс `X-`.
+
+Но если у вас есть пользовательские заголовки, которые вы хотите показывать клиенту в браузере, вам нужно добавить их в настройки CORS (подробнее см. в [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, описанный в документации Starlette по CORS.
diff --git a/docs/ru/docs/advanced/security/http-basic-auth.md b/docs/ru/docs/advanced/security/http-basic-auth.md
new file mode 100644
index 000000000..41e62d4bf
--- /dev/null
+++ b/docs/ru/docs/advanced/security/http-basic-auth.md
@@ -0,0 +1,107 @@
+# HTTP Basic Auth { #http-basic-auth }
+
+Для самых простых случаев можно использовать HTTP Basic Auth.
+
+При HTTP Basic Auth приложение ожидает HTTP-заголовок, который содержит имя пользователя и пароль.
+
+Если его нет, возвращается ошибка HTTP 401 «Unauthorized».
+
+Также возвращается заголовок `WWW-Authenticate` со значением `Basic` и необязательным параметром `realm`.
+
+Это говорит браузеру показать встроенное окно запроса имени пользователя и пароля.
+
+Затем, когда вы вводите эти данные, браузер автоматически отправляет их в заголовке.
+
+## Простой HTTP Basic Auth { #simple-http-basic-auth }
+
+* Импортируйте `HTTPBasic` и `HTTPBasicCredentials`.
+* Создайте «схему» `security` с помощью `HTTPBasic`.
+* Используйте эту `security` как зависимость в вашей *операции пути*.
+* Она возвращает объект типа `HTTPBasicCredentials`:
+ * Он содержит отправленные `username` и `password`.
+
+{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *}
+
+Когда вы впервые откроете URL (или нажмёте кнопку «Execute» в документации), браузер попросит ввести имя пользователя и пароль:
+
+
+
+## Проверка имени пользователя { #check-the-username }
+
+Вот более полный пример.
+
+Используйте зависимость, чтобы проверить, корректны ли имя пользователя и пароль.
+
+Для этого используйте стандартный модуль Python `secrets` для проверки имени пользователя и пароля.
+
+`secrets.compare_digest()` должен получать `bytes` или `str`, который содержит только символы ASCII (английские символы). Это значит, что он не будет работать с символами вроде `á`, как в `Sebastián`.
+
+Чтобы это обработать, сначала преобразуем `username` и `password` в `bytes`, закодировав их в UTF-8.
+
+Затем можно использовать `secrets.compare_digest()`, чтобы убедиться, что `credentials.username` равен `"stanleyjobson"`, а `credentials.password` — `"swordfish"`.
+
+{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *}
+
+Это было бы похоже на:
+
+```Python
+if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
+ # Вернуть ошибку
+ ...
+```
+
+Но используя `secrets.compare_digest()`, вы защитите код от атак типа «тайминговая атака» (атака по времени).
+
+### Тайминговые атаки { #timing-attacks }
+
+Что такое «тайминговая атака»?
+
+Представим, что злоумышленники пытаются угадать имя пользователя и пароль.
+
+И они отправляют запрос с именем пользователя `johndoe` и паролем `love123`.
+
+Тогда Python-код в вашем приложении будет эквивалентен чему-то вроде:
+
+```Python
+if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
+ ...
+```
+
+Но в момент, когда Python сравнит первую `j` в `johndoe` с первой `s` в `stanleyjobson`, он вернёт `False`, потому что уже ясно, что строки не совпадают, решив, что «нет смысла тратить ресурсы на сравнение остальных букв». И ваше приложение ответит «Неверное имя пользователя или пароль».
+
+Затем злоумышленники попробуют имя пользователя `stanleyjobsox` и пароль `love123`.
+
+И ваш код сделает что-то вроде:
+
+```Python
+if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
+ ...
+```
+
+Pythonу придётся сравнить весь общий префикс `stanleyjobso` в `stanleyjobsox` и `stanleyjobson`, прежде чем понять, что строки отличаются. Поэтому на ответ «Неверное имя пользователя или пароль» уйдёт на несколько микросекунд больше.
+
+#### Время ответа помогает злоумышленникам { #the-time-to-answer-helps-the-attackers }
+
+Замечая, что сервер прислал «Неверное имя пользователя или пароль» на несколько микросекунд позже, злоумышленники поймут, что какая-то часть была угадана — начальные буквы верны.
+
+Тогда они могут попробовать снова, зная, что правильнее что-то ближе к `stanleyjobsox`, чем к `johndoe`.
+
+#### «Профессиональная» атака { #a-professional-attack }
+
+Конечно, злоумышленники не будут делать всё это вручную — они напишут программу, возможно, с тысячами или миллионами попыток в секунду. И будут подбирать по одной дополнительной верной букве за раз.
+
+Так за минуты или часы они смогут угадать правильные имя пользователя и пароль — с «помощью» нашего приложения — используя лишь время, затраченное на ответ.
+
+#### Исправление с помощью `secrets.compare_digest()` { #fix-it-with-secrets-compare-digest }
+
+Но в нашем коде мы используем `secrets.compare_digest()`.
+
+Вкратце: сравнение `stanleyjobsox` с `stanleyjobson` займёт столько же времени, сколько и сравнение `johndoe` с `stanleyjobson`. То же относится и к паролю.
+
+Таким образом, используя `secrets.compare_digest()` в коде приложения, вы защитите его от всего этого класса атак на безопасность.
+
+### Возврат ошибки { #return-the-error }
+
+После того как обнаружено, что учётные данные некорректны, верните `HTTPException` со статус-кодом ответа 401 (тем же, что и при отсутствии учётных данных) и добавьте HTTP-заголовок `WWW-Authenticate`, чтобы браузер снова показал окно входа:
+
+{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *}
diff --git a/docs/ru/docs/advanced/security/index.md b/docs/ru/docs/advanced/security/index.md
new file mode 100644
index 000000000..912e4812a
--- /dev/null
+++ b/docs/ru/docs/advanced/security/index.md
@@ -0,0 +1,19 @@
+# Расширенная безопасность { #advanced-security }
+
+## Дополнительные возможности { #additional-features }
+
+Есть дополнительные возможности для работы с безопасностью помимо тех, что описаны в [Учебник — Руководство пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}.
+
+/// tip | Совет
+
+Следующие разделы **не обязательно являются «продвинутыми»**.
+
+И возможно, что решение для вашего варианта использования находится в одном из них.
+
+///
+
+## Сначала прочитайте руководство { #read-the-tutorial-first }
+
+В следующих разделах предполагается, что вы уже прочитали основной [Учебник — Руководство пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}.
+
+Все они основаны на тех же концепциях, но предоставляют дополнительные возможности.
diff --git a/docs/ru/docs/advanced/security/oauth2-scopes.md b/docs/ru/docs/advanced/security/oauth2-scopes.md
new file mode 100644
index 000000000..8788df199
--- /dev/null
+++ b/docs/ru/docs/advanced/security/oauth2-scopes.md
@@ -0,0 +1,274 @@
+# OAuth2 scopes { #oauth2-scopes }
+
+Вы можете использовать OAuth2 scopes (scope - область, рамки) напрямую с **FastAPI** — они интегрированы и работают бесшовно.
+
+Это позволит вам иметь более детальную систему разрешений по стандарту OAuth2, интегрированную в ваше OpenAPI‑приложение (и документацию API).
+
+OAuth2 со scopes — это механизм, который используют многие крупные провайдеры аутентификации: Facebook, Google, GitHub, Microsoft, X (Twitter) и т.д. Они применяют его, чтобы предоставлять конкретные разрешения пользователям и приложениям.
+
+Каждый раз, когда вы «входите через» Facebook, Google, GitHub, Microsoft, X (Twitter), это приложение использует OAuth2 со scopes.
+
+В этом разделе вы увидите, как управлять аутентификацией и авторизацией с теми же OAuth2 scopes в вашем приложении на **FastAPI**.
+
+/// warning | Предупреждение
+
+Это более-менее продвинутый раздел. Если вы только начинаете, можете пропустить его.
+
+Вам не обязательно нужны OAuth2 scopes — аутентификацию и авторизацию можно реализовать любым нужным вам способом.
+
+Но OAuth2 со scopes можно красиво интегрировать в ваш API (через OpenAPI) и документацию API.
+
+Так или иначе, вы все равно будете применять эти scopes или какие-то другие требования безопасности/авторизации, как вам нужно, в вашем коде.
+
+Во многих случаях OAuth2 со scopes может быть избыточным.
+
+Но если вы знаете, что это нужно, или вам просто интересно — продолжайте чтение.
+
+///
+
+## OAuth2 scopes и OpenAPI { #oauth2-scopes-and-openapi }
+
+Спецификация OAuth2 определяет «scopes» как список строк, разделённых пробелами.
+
+Содержимое каждой такой строки может иметь любой формат, но не должно содержать пробелов.
+
+Эти scopes представляют «разрешения».
+
+В OpenAPI (например, в документации API) можно определить «схемы безопасности» (security schemes).
+
+Когда одна из таких схем безопасности использует OAuth2, вы также можете объявлять и использовать scopes.
+
+Каждый «scope» — это просто строка (без пробелов).
+
+Обычно они используются для объявления конкретных разрешений безопасности, например:
+
+- `users:read` или `users:write` — распространённые примеры.
+- `instagram_basic` используется Facebook / Instagram.
+- `https://www.googleapis.com/auth/drive` используется Google.
+
+/// info | Информация
+
+В OAuth2 «scope» — это просто строка, объявляющая требуемое конкретное разрешение.
+
+Неважно, есть ли там другие символы, такие как `:`, или это URL.
+
+Эти детали зависят от реализации.
+
+Для OAuth2 это просто строки.
+
+///
+
+## Взгляд издалека { #global-view }
+
+Сначала быстро посмотрим, что изменилось по сравнению с примерами из основного раздела **Учебник - Руководство пользователя** — [OAuth2 с паролем (и хешированием), Bearer с JWT-токенами](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Теперь — с использованием OAuth2 scopes:
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *}
+
+Теперь рассмотрим эти изменения шаг за шагом.
+
+## OAuth2 схема безопасности { #oauth2-security-scheme }
+
+Первое изменение — мы объявляем схему безопасности OAuth2 с двумя доступными scopes: `me` и `items`.
+
+Параметр `scopes` получает `dict`, где каждый scope — это ключ, а описание — значение:
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *}
+
+Так как теперь мы объявляем эти scopes, они появятся в документации API при входе/авторизации.
+
+И вы сможете выбрать, какие scopes вы хотите выдать доступ: `me` и `items`.
+
+Это тот же механизм, когда вы даёте разрешения при входе через Facebook, Google, GitHub и т.д.:
+
+
+
+## JWT-токены со scopes { #jwt-token-with-scopes }
+
+Теперь измените операцию пути, выдающую токен, чтобы возвращать запрошенные scopes.
+
+Мы всё ещё используем тот же `OAuth2PasswordRequestForm`. Он включает свойство `scopes` с `list` из `str` — каждый scope, полученный в запросе.
+
+И мы возвращаем scopes как часть JWT‑токена.
+
+/// danger | Опасность
+
+Для простоты здесь мы просто добавляем полученные scopes прямо в токен.
+
+Но в вашем приложении, в целях безопасности, следует убедиться, что вы добавляете только те scopes, которые пользователь действительно может иметь, или те, которые вы заранее определили.
+
+///
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}
+
+## Объявление scopes в *обработчиках путей* и зависимостях { #declare-scopes-in-path-operations-and-dependencies }
+
+Теперь объявим, что операция пути для `/users/me/items/` требует scope `items`.
+
+Для этого импортируем и используем `Security` из `fastapi`.
+
+Вы можете использовать `Security` для объявления зависимостей (как `Depends`), но `Security` также принимает параметр `scopes` со списком scopes (строк).
+
+В этом случае мы передаём функцию‑зависимость `get_current_active_user` в `Security` (точно так же, как сделали бы с `Depends`).
+
+Но мы также передаём `list` scopes — в данном случае только один scope: `items` (их могло быть больше).
+
+И функция‑зависимость `get_current_active_user` тоже может объявлять подзависимости не только через `Depends`, но и через `Security`, объявляя свою подзависимость (`get_current_user`) и дополнительные требования по scopes.
+
+В данном случае требуется scope `me` (их также могло быть больше одного).
+
+/// note | Примечание
+
+Вам не обязательно добавлять разные scopes в разных местах.
+
+Мы делаем это здесь, чтобы показать, как **FastAPI** обрабатывает scopes, объявленные на разных уровнях.
+
+///
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}
+
+/// info | Технические детали
+
+`Security` на самом деле является подклассом `Depends` и имеет всего один дополнительный параметр, который мы рассмотрим позже.
+
+Но используя `Security` вместо `Depends`, **FastAPI** будет знать, что можно объявлять security scopes, использовать их внутри и документировать API в OpenAPI.
+
+Однако когда вы импортируете `Query`, `Path`, `Depends`, `Security` и другие из `fastapi`, это на самом деле функции, возвращающие специальные классы.
+
+///
+
+## Использование `SecurityScopes` { #use-securityscopes }
+
+Теперь обновим зависимость `get_current_user`.
+
+Именно её используют зависимости выше.
+
+Здесь мы используем ту же схему OAuth2, созданную ранее, объявляя её как зависимость: `oauth2_scheme`.
+
+Поскольку у этой функции‑зависимости нет собственных требований по scopes, мы можем использовать `Depends` с `oauth2_scheme` — нам не нужно использовать `Security`, если не требуется указывать security scopes.
+
+Мы также объявляем специальный параметр типа `SecurityScopes`, импортированный из `fastapi.security`.
+
+Класс `SecurityScopes` похож на `Request` (через `Request` мы получали сам объект запроса).
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}
+
+## Использование `scopes` { #use-the-scopes }
+
+Параметр `security_scopes` будет типа `SecurityScopes`.
+
+У него есть свойство `scopes` со списком, содержащим все scopes, требуемые им самим и всеми зависимостями, использующими его как подзависимость. То есть всеми «зависящими»… это может звучать запутанно, ниже есть дополнительное объяснение.
+
+Объект `security_scopes` (класс `SecurityScopes`) также предоставляет атрибут `scope_str` — это одна строка с этими scopes, разделёнными пробелами (мы будем её использовать).
+
+Мы создаём `HTTPException`, который можем переиспользовать (`raise`) в нескольких местах.
+
+В этом исключении мы включаем требуемые scopes (если есть) в виде строки, разделённой пробелами (используя `scope_str`). Эту строку со scopes мы помещаем в HTTP‑заголовок `WWW-Authenticate` (это часть спецификации).
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}
+
+## Проверка `username` и формата данных { #verify-the-username-and-data-shape }
+
+Мы проверяем, что получили `username`, и извлекаем scopes.
+
+Затем валидируем эти данные с помощью Pydantic‑модели (перехватывая исключение `ValidationError`), и если возникает ошибка при чтении JWT‑токена или при валидации данных с Pydantic, мы вызываем `HTTPException`, созданное ранее.
+
+Для этого мы обновляем Pydantic‑модель `TokenData`, добавляя новое свойство `scopes`.
+
+Валидируя данные с помощью Pydantic, мы можем удостовериться, что у нас, например, именно `list` из `str` со scopes и `str` с `username`.
+
+А не, скажем, `dict` или что‑то ещё — ведь это могло бы где‑то позже сломать приложение и создать риск для безопасности.
+
+Мы также проверяем, что существует пользователь с таким именем, и если нет — вызываем то же исключение, созданное ранее.
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}
+
+## Проверка `scopes` { #verify-the-scopes }
+
+Теперь проверяем, что все требуемые scopes — этой зависимостью и всеми зависящими (включая операции пути) — присутствуют среди scopes, предоставленных в полученном токене, иначе вызываем `HTTPException`.
+
+Для этого используем `security_scopes.scopes`, содержащий `list` со всеми этими scopes как `str`.
+
+{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}
+
+## Дерево зависимостей и scopes { #dependency-tree-and-scopes }
+
+Ещё раз рассмотрим дерево зависимостей и scopes.
+
+Так как у зависимости `get_current_active_user` есть подзависимость `get_current_user`, scope `"me"`, объявленный в `get_current_active_user`, будет включён в список требуемых scopes в `security_scopes.scopes`, передаваемый в `get_current_user`.
+
+Сама операция пути тоже объявляет scope — `"items"`, поэтому он также будет в списке `security_scopes.scopes`, передаваемом в `get_current_user`.
+
+Иерархия зависимостей и scopes выглядит так:
+
+- Операция пути `read_own_items`:
+ - Запрашивает scopes `["items"]` с зависимостью:
+ - `get_current_active_user`:
+ - Функция‑зависимость `get_current_active_user`:
+ - Запрашивает scopes `["me"]` с зависимостью:
+ - `get_current_user`:
+ - Функция‑зависимость `get_current_user`:
+ - Собственных scopes не запрашивает.
+ - Имеет зависимость, использующую `oauth2_scheme`.
+ - Имеет параметр `security_scopes` типа `SecurityScopes`:
+ - Этот параметр `security_scopes` имеет свойство `scopes` с `list`, содержащим все объявленные выше scopes, то есть:
+ - `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` тоже не объявляет `scopes`.
+
+/// tip | Совет
+
+Важный и «магический» момент здесь в том, что `get_current_user` будет иметь разный список `scopes` для проверки для каждой операции пути.
+
+Всё это зависит от `scopes`, объявленных в каждой операции пути и в каждой зависимости в дереве зависимостей конкретной операции пути.
+
+///
+
+## Больше деталей о `SecurityScopes` { #more-details-about-securityscopes }
+
+Вы можете использовать `SecurityScopes` в любой точке и в нескольких местах — необязательно в «корневой» зависимости.
+
+Он всегда будет содержать security scopes, объявленные в текущих зависимостях `Security`, и всеми зависящими — для этой конкретной операции пути и этого конкретного дерева зависимостей.
+
+Поскольку `SecurityScopes` будет содержать все scopes, объявленные зависящими, вы можете использовать его, чтобы централизованно проверять наличие требуемых scopes в токене в одной функции‑зависимости, а затем объявлять разные требования по scopes в разных операциях пути.
+
+Они будут проверяться независимо для каждой операции пути.
+
+## Проверим это { #check-it }
+
+Откройте документацию API — вы сможете аутентифицироваться и указать, какие scopes вы хотите авторизовать.
+
+
+
+Если вы не выберете ни один scope, вы будете «аутентифицированы», но при попытке доступа к `/users/me/` или `/users/me/items/` получите ошибку о недостаточных разрешениях. При этом доступ к `/status/` будет возможен.
+
+Если вы выберете scope `me`, но не `items`, вы сможете получить доступ к `/users/me/`, но не к `/users/me/items/`.
+
+Так и будет происходить со сторонним приложением, которое попытается обратиться к одной из этих операций пути с токеном, предоставленным пользователем, — в зависимости от того, сколько разрешений пользователь дал приложению.
+
+## О сторонних интеграциях { #about-third-party-integrations }
+
+В этом примере мы используем OAuth2 «password flow» (аутентификация по паролю).
+
+Это уместно, когда мы входим в наше собственное приложение, вероятно, с нашим собственным фронтендом.
+
+Мы можем ему доверять при получении `username` и `password`, потому что он под нашим контролем.
+
+Но если вы создаёте OAuth2‑приложение, к которому будут подключаться другие (т.е. вы строите провайдера аутентификации наподобие Facebook, Google, GitHub и т.п.), вам следует использовать один из других «flows».
+
+Самый распространённый — «implicit flow».
+
+Самый безопасный — «code flow», но он сложнее в реализации, так как требует больше шагов. Из‑за сложности многие провайдеры в итоге рекомендуют «implicit flow».
+
+/// note | Примечание
+
+Часто каждый провайдер аутентификации называет свои «flows» по‑разному — как часть бренда.
+
+Но в итоге они реализуют один и тот же стандарт OAuth2.
+
+///
+
+FastAPI включает утилиты для всех этих OAuth2‑flows в `fastapi.security.oauth2`.
+
+## `Security` в параметре `dependencies` декоратора { #security-in-decorator-dependencies }
+
+Точно так же, как вы можете определить `list` из `Depends` в параметре `dependencies` декоратора (см. [Зависимости в декораторах операции пути](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), вы можете использовать там и `Security` со `scopes`.
diff --git a/docs/ru/docs/advanced/settings.md b/docs/ru/docs/advanced/settings.md
new file mode 100644
index 000000000..8408faebf
--- /dev/null
+++ b/docs/ru/docs/advanced/settings.md
@@ -0,0 +1,302 @@
+# Настройки и переменные окружения { #settings-and-environment-variables }
+
+Во многих случаях вашему приложению могут понадобиться внешние настройки или конфигурации, например секретные ключи, учетные данные для базы данных, учетные данные для email‑сервисов и т.д.
+
+Большинство таких настроек являются изменяемыми (могут меняться), например URL базы данных. И многие из них могут быть «чувствительными», например секреты.
+
+По этой причине обычно их передают через переменные окружения, которые считываются приложением.
+
+/// tip | Совет
+
+Чтобы понять, что такое переменные окружения, вы можете прочитать [Переменные окружения](../environment-variables.md){.internal-link target=_blank}.
+
+///
+
+## Типы и валидация { #types-and-validation }
+
+Переменные окружения могут содержать только текстовые строки, так как они внешние по отношению к Python и должны быть совместимы с другими программами и остальной системой (и даже с разными операционными системами, такими как Linux, Windows, macOS).
+
+Это означает, что любое значение, прочитанное в Python из переменной окружения, будет `str`, а любые преобразования к другим типам или любая валидация должны выполняться в коде.
+
+## Pydantic `Settings` { #pydantic-settings }
+
+К счастью, Pydantic предоставляет отличную утилиту для работы с этими настройками, поступающими из переменных окружения, — Pydantic: управление настройками.
+
+### Установка `pydantic-settings` { #install-pydantic-settings }
+
+Сначала убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет `pydantic-settings`:
+
+
+
+Затем откройте документацию для подприложения по адресу http://127.0.0.1:8000/subapi/docs.
+
+Вы увидите автоматическую документацию API для подприложения, включающую только его собственные _операции пути_, все под корректным префиксом подпути `/subapi`:
+
+
+
+Если вы попробуете взаимодействовать с любым из двух интерфейсов, всё будет работать корректно, потому что браузер сможет обращаться к каждому конкретному приложению и подприложению.
+
+### Технические подробности: `root_path` { #technical-details-root-path }
+
+Когда вы монтируете подприложение, как описано выше, FastAPI позаботится о передаче пути монтирования для подприложения, используя механизм из спецификации ASGI под названием `root_path`.
+
+Таким образом подприложение будет знать, что для интерфейса документации нужно использовать этот префикс пути.
+
+У подприложения также могут быть свои собственные смонтированные подприложения, и всё будет работать корректно, потому что FastAPI автоматически обрабатывает все эти `root_path`.
+
+Вы узнаете больше о `root_path` и о том, как использовать его явно, в разделе [За прокси](behind-a-proxy.md){.internal-link target=_blank}.
diff --git a/docs/ru/docs/advanced/templates.md b/docs/ru/docs/advanced/templates.md
new file mode 100644
index 000000000..460e2e466
--- /dev/null
+++ b/docs/ru/docs/advanced/templates.md
@@ -0,0 +1,126 @@
+# Шаблоны { #templates }
+
+Вы можете использовать любой шаблонизатор вместе с **FastAPI**.
+
+Часто выбирают Jinja2 — тот же, что используется во Flask и других инструментах.
+
+Есть утилиты для простой настройки, которые вы можете использовать прямо в своем приложении **FastAPI** (предоставляются Starlette).
+
+## Установка зависимостей { #install-dependencies }
+
+Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и установили `jinja2`:
+
+
-## Обработка отключений и работа с несколькими клиентами
+## Обработка отключений и работа с несколькими клиентами { #handling-disconnections-and-multiple-clients }
Если веб-сокет соединение закрыто, то `await websocket.receive_text()` вызовет исключение `WebSocketDisconnect`, которое можно поймать и обработать как в этом примере:
@@ -168,7 +168,7 @@ $ fastapi dev main.py
Client #1596980209979 left the chat
```
-/// tip | Примечание
+/// tip | Подсказка
Приложение выше - это всего лишь простой минимальный пример, демонстрирующий обработку и передачу сообщений нескольким веб-сокет соединениям.
@@ -178,9 +178,9 @@ Client #1596980209979 left the chat
///
-## Дополнительная информация
+## Дополнительная информация { #more-info }
Для более глубокого изучения темы воспользуйтесь документацией Starlette:
-* The `WebSocket` class.
-* Class-based WebSocket handling.
+* The `WebSocket` class.
+* Class-based WebSocket handling.
diff --git a/docs/ru/docs/advanced/wsgi.md b/docs/ru/docs/advanced/wsgi.md
new file mode 100644
index 000000000..64d7c7a28
--- /dev/null
+++ b/docs/ru/docs/advanced/wsgi.md
@@ -0,0 +1,35 @@
+# Подключение WSGI — Flask, Django и другие { #including-wsgi-flask-django-others }
+
+Вы можете монтировать WSGI‑приложения, как вы видели в [Подприложения — Mounts](sub-applications.md){.internal-link target=_blank}, [За прокси‑сервером](behind-a-proxy.md){.internal-link target=_blank}.
+
+Для этого вы можете использовать `WSGIMiddleware` и обернуть им ваше WSGI‑приложение, например Flask, Django и т.д.
+
+## Использование `WSGIMiddleware` { #using-wsgimiddleware }
+
+Нужно импортировать `WSGIMiddleware`.
+
+Затем оберните WSGI‑приложение (например, Flask) в middleware (Промежуточный слой).
+
+После этого смонтируйте его на путь.
+
+{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *}
+
+## Проверьте { #check-it }
+
+Теперь каждый HTTP‑запрос по пути `/v1/` будет обрабатываться приложением Flask.
+
+А всё остальное будет обрабатываться **FastAPI**.
+
+Если вы запустите это и перейдёте по http://localhost:8000/v1/, вы увидите HTTP‑ответ от Flask:
+
+```txt
+Hello, World from Flask!
+```
+
+А если вы перейдёте по http://localhost:8000/v2, вы увидите HTTP‑ответ от FastAPI:
+
+```JSON
+{
+ "message": "Hello World"
+}
+```
diff --git a/docs/ru/docs/alternatives.md b/docs/ru/docs/alternatives.md
index 3c5147e79..17b54aad2 100644
--- a/docs/ru/docs/alternatives.md
+++ b/docs/ru/docs/alternatives.md
@@ -1,104 +1,94 @@
-# Альтернативы, источники вдохновения и сравнения
+# Альтернативы, источники вдохновения и сравнения { #alternatives-inspiration-and-comparisons }
-Что вдохновило на создание **FastAPI**, сравнение его с альтернативами и чему он научился у них.
+Что вдохновило **FastAPI**, сравнение с альтернативами и чему он у них научился.
-## Введение
+## Введение { #intro }
-**FastAPI** не существовал бы, если б не было более ранних работ других людей.
+**FastAPI** не существовал бы без предыдущих работ других людей.
-Они создали большое количество инструментов, которые вдохновили меня на создание **FastAPI**.
+Было создано множество инструментов, которые вдохновили на его появление.
-Я всячески избегал создания нового фреймворка в течение нескольких лет.
-Сначала я пытался собрать все нужные функции, которые ныне есть в **FastAPI**, используя множество различных фреймворков, плагинов и инструментов.
+Я несколько лет избегал создания нового фреймворка. Сначала пытался закрыть все возможности, которые сейчас предоставляет **FastAPI**, с помощью множества разных фреймворков, плагинов и инструментов.
-Но в какой-то момент не осталось другого выбора, кроме как создать что-то, что предоставляло бы все эти функции сразу.
-Взять самые лучшие идеи из предыдущих инструментов и, используя новые возможности Python (которых не было до версии 3.6, то есть подсказки типов), объединить их.
+Но в какой-то момент не осталось другого варианта, кроме как создать что-то, что включает все эти возможности, взяв лучшие идеи из прежних инструментов и совместив их максимально удачным образом, используя возможности языка, которых прежде не было (аннотации типов в Python 3.6+).
-## Предшествующие инструменты
+## Предшествующие инструменты { #previous-tools }
-### Django
+### Django { #django }
-Это самый популярный Python-фреймворк, и он пользуется доверием.
-Он используется для создания проектов типа Instagram.
+Это самый популярный Python-фреймворк, ему широко доверяют. Он используется для построения систем вроде Instagram.
-Django довольно тесно связан с реляционными базами данных (такими как MySQL или PostgreSQL), потому использовать NoSQL базы данных (например, Couchbase, MongoDB, Cassandra и т.п.) в качестве основного хранилища данных - непросто.
+Он относительно тесно связан с реляционными базами данных (например, MySQL или PostgreSQL), поэтому использовать NoSQL-базу данных (например, Couchbase, MongoDB, Cassandra и т. п.) в качестве основного хранилища не очень просто.
-Он был создан для генерации HTML-страниц на сервере, а не для создания API, используемых современными веб-интерфейсами (React, Vue.js, Angular и т.п.) или другими системами (например, IoT) взаимодействующими с сервером.
+Он был создан для генерации HTML на бэкенде, а не для создания API, используемых современным фронтендом (например, React, Vue.js и Angular) или другими системами (например, устройствами IoT), которые с ним общаются.
-### Django REST Framework
+### Django REST Framework { #django-rest-framework }
-Фреймворк Django REST был создан, как гибкий инструментарий для создания веб-API на основе Django.
+Django REST Framework был создан как гибкий набор инструментов для построения веб-API поверх Django, чтобы улучшить его возможности в части API.
-DRF использовался многими компаниями, включая Mozilla, Red Hat и Eventbrite.
+Он используется многими компаниями, включая Mozilla, Red Hat и Eventbrite.
-Это был один из первых примеров **автоматического документирования API** и это была одна из первых идей, вдохновивших на создание **FastAPI**.
+Это был один из первых примеров **автоматической документации API**, и именно эта идея одной из первых вдохновила на «поиск» **FastAPI**.
/// note | Заметка
-Django REST Framework был создан Tom Christie.
-Он же создал Starlette и Uvicorn, на которых основан **FastAPI**.
+Django REST Framework был создан Томом Кристи. Он же создал Starlette и Uvicorn, на которых основан **FastAPI**.
///
-/// check | Идея для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Должно быть автоматическое создание документации API с пользовательским веб-интерфейсом.
+Наличие пользовательского веб-интерфейса с автоматической документацией API.
///
-### Flask
+### Flask { #flask }
-Flask - это "микрофреймворк", в нём нет интеграции с базами данных и многих других вещей, которые предустановлены в Django.
+Flask — это «микрофреймворк», он не включает интеграции с базами данных и многие другие вещи, которые в Django идут «из коробки».
-Его простота и гибкость дают широкие возможности, такие как использование баз данных NoSQL в качестве основной системы хранения данных.
+Эта простота и гибкость позволяет, например, использовать NoSQL-базы в качестве основной системы хранения данных.
-Он очень прост, его изучение интуитивно понятно, хотя в некоторых местах документация довольно техническая.
+Он очень прост, его относительно легко интуитивно освоить, хотя местами документация довольно техническая.
-Flask часто используется и для приложений, которым не нужна база данных, настройки прав доступа для пользователей и прочие из множества функций, предварительно встроенных в Django.
-Хотя многие из этих функций могут быть добавлены с помощью плагинов.
+Его также часто используют для приложений, которым не нужна база данных, управление пользователями или многие другие функции, предварительно встроенные в Django. Хотя многие из этих возможностей можно добавить плагинами.
-Такое разделение на части и то, что это "микрофреймворк", который можно расширить, добавляя необходимые возможности, было ключевой особенностью, которую я хотел сохранить.
+Такое разбиение на части и то, что это «микрофреймворк», который можно расширять ровно под нужды, — ключевая особенность, которую хотелось сохранить.
-Простота Flask, показалась мне подходящей для создания API.
-Но ещё нужно было найти "Django REST Framework" для Flask.
+С учётом простоты Flask он казался хорошим вариантом для создания API. Следующим было найти «Django REST Framework» для Flask.
-/// check | Идеи для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Это будет микрофреймворк. К нему легко будет добавить необходимые инструменты и части.
+Быть микро-фреймворком. Облегчить комбинирование необходимых инструментов и компонентов.
-Должна быть простая и лёгкая в использовании система маршрутизации запросов.
+Иметь простую и удобную систему маршрутизации.
///
-### Requests
+### Requests { #requests }
-На самом деле **FastAPI** не является альтернативой **Requests**.
-Их область применения очень разная.
+**FastAPI** на самом деле не альтернатива **Requests**. Их области применения очень различны.
-В принципе, можно использовать Requests *внутри* приложения FastAPI.
+Обычно Requests используют даже внутри приложения FastAPI.
-Но всё же я использовал в FastAPI некоторые идеи из Requests.
+И всё же **FastAPI** во многом вдохновлялся Requests.
-**Requests** - это библиотека для взаимодействия с API в качестве клиента,
-в то время как **FastAPI** - это библиотека для *создания* API (то есть сервера).
+**Requests** — это библиотека для взаимодействия с API (как клиент), а **FastAPI** — библиотека для создания API (как сервер).
-Они, так или иначе, диаметрально противоположны и дополняют друг друга.
+Они, в каком-то смысле, находятся на противоположных концах и дополняют друг друга.
-Requests имеет очень простой и понятный дизайн, очень прост в использовании и имеет разумные значения по умолчанию.
-И в то же время он очень мощный и настраиваемый.
+Requests имеет очень простой и понятный дизайн, им очень легко пользоваться, есть разумные значения по умолчанию. И при этом он очень мощный и настраиваемый.
-Вот почему на официальном сайте написано:
+Именно поэтому на официальном сайте сказано:
-> Requests - один из самых загружаемых пакетов Python всех времен
+> Requests — один из самых загружаемых Python-пакетов всех времён
-
-Использовать его очень просто. Например, чтобы выполнить запрос `GET`, Вы бы написали:
+Пользоваться им очень просто. Например, чтобы сделать запрос `GET`, вы бы написали:
```Python
response = requests.get("http://example.com/some/url")
```
-Противоположная *операция пути* в FastAPI может выглядеть следующим образом:
+Соответствующая в FastAPI API-операция пути могла бы выглядеть так:
```Python hl_lines="1"
@app.get("/some/url")
@@ -106,428 +96,390 @@ def read_url():
return {"message": "Hello World"}
```
-Глядите, как похоже `requests.get(...)` и `@app.get(...)`.
+Посмотрите, насколько похожи `requests.get(...)` и `@app.get(...)`.
-/// check | Идеи для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-* Должен быть простой и понятный API.
-* Нужно использовать названия HTTP-методов (операций) для упрощения понимания происходящего.
-* Должны быть разумные настройки по умолчанию и широкие возможности их кастомизации.
+* Иметь простой и понятный API.
+* Использовать названия HTTP-методов (операций) напрямую, простым и интуитивным образом.
+* Иметь разумные значения по умолчанию, но и мощные настройки.
///
-### Swagger / OpenAPI
+### Swagger / OpenAPI { #swagger-openapi }
-Главной функцией, которую я хотел унаследовать от Django REST Framework, была автоматическая документация API.
+Главной возможностью, которую хотелось взять из Django REST Framework, была автоматическая документация API.
-Но потом я обнаружил, что существует стандарт документирования API, использующий JSON (или YAML, расширение JSON) под названием Swagger.
+Затем я обнаружил, что есть стандарт для документирования API с использованием JSON (или YAML — расширения JSON), под названием Swagger.
-И к нему уже был создан пользовательский веб-интерфейс.
-Таким образом, возможность генерировать документацию Swagger для API позволила бы использовать этот интерфейс.
+И уже существовал веб-интерфейс для Swagger API. Поэтому возможность генерировать документацию Swagger для API позволила бы автоматически использовать этот веб-интерфейс.
В какой-то момент Swagger был передан Linux Foundation и переименован в OpenAPI.
-Вот почему, когда говорят о версии 2.0, обычно говорят "Swagger", а для версии 3+ "OpenAPI".
+Вот почему, говоря о версии 2.0, обычно говорят «Swagger», а о версии 3+ — «OpenAPI».
-/// check | Идеи для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Использовать открытые стандарты для спецификаций API вместо самодельных схем.
+Использовать открытый стандарт для спецификаций API вместо самодельной схемы.
-Совместимость с основанными на стандартах пользовательскими интерфейсами:
+И интегрировать основанные на стандартах инструменты пользовательского интерфейса:
* Swagger UI
* ReDoc
-Они были выбраны за популярность и стабильность.
-Но сделав беглый поиск, Вы можете найти десятки альтернативных пользовательских интерфейсов для OpenAPI, которые Вы можете использовать с **FastAPI**.
+Эти два инструмента выбраны за популярность и стабильность, но даже при беглом поиске можно найти десятки альтернативных интерфейсов для OpenAPI (которые можно использовать с **FastAPI**).
///
-### REST фреймворки для Flask
+### REST-фреймворки для Flask { #flask-rest-frameworks }
-Существует несколько REST фреймворков для Flask, но потратив время и усилия на их изучение, я обнаружил, что многие из них не обновляются или заброшены и имеют нерешённые проблемы из-за которых они непригодны к использованию.
+Существует несколько REST-фреймворков для Flask, но, вложив время и усилия в исследование, я обнаружил, что многие из них прекращены или заброшены, с несколькими нерешёнными Issue (тикет\обращение), из-за которых они непригодны.
-### Marshmallow
+### Marshmallow { #marshmallow }
-Одной из основных функций, необходимых системам API, является "сериализация" данных, то есть преобразование данных из кода (Python) во что-то, что может быть отправлено по сети.
-Например, превращение объекта содержащего данные из базы данных в объект JSON, конвертация объекта `datetime` в строку и т.п.
+Одна из основных возможностей, нужных системам API, — «сериализация» данных, то есть преобразование данных из кода (Python) во что-то, что можно отправить по сети. Например, преобразование объекта с данными из базы в JSON-объект. Преобразование объектов `datetime` в строки и т. п.
-Еще одна важная функция, необходимая API — проверка данных, позволяющая убедиться, что данные действительны и соответствуют заданным параметрам.
-Как пример, можно указать, что ожидаются данные типа `int`, а не какая-то произвольная строка.
-Это особенно полезно для входящих данных.
+Ещё одна важная возможность, востребованная API, — валидация данных: убеждаться, что данные валидны с учётом заданных параметров. Например, что какое-то поле — `int`, а не произвольная строка. Это особенно полезно для входящих данных.
-Без системы проверки данных Вам пришлось бы прописывать все проверки вручную.
+Без системы валидации данных вам пришлось бы выполнять все проверки вручную в коде.
-Именно для обеспечения этих функций и была создана Marshmallow.
-Это отличная библиотека и я много раз пользовался ею раньше.
+Именно для этих возможностей и был создан Marshmallow. Это отличная библиотека, я много ей пользовался раньше.
-Но она была создана до того, как появились подсказки типов Python.
-Итак, чтобы определить каждую схему,
-Вам нужно использовать определенные утилиты и классы, предоставляемые Marshmallow.
+Но она появилась до того, как в Python появились аннотации типов. Поэтому для определения каждой схемы нужно использовать специальные утилиты и классы, предоставляемые Marshmallow.
-/// check | Идея для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Использовать код программы для автоматического создания "схем", определяющих типы данных и их проверку.
+Использовать код для автоматического определения «схем», задающих типы данных и их валидацию.
///
-### Webargs
+### Webargs { #webargs }
-Другая немаловажная функция API - парсинг данных из входящих запросов.
+Ещё одна важная возможность для API — парсинг данных из входящих HTTP-запросов.
-Webargs - это инструмент, который был создан для этого и поддерживает несколько фреймворков, включая Flask.
+Webargs — это инструмент, созданный для этого поверх нескольких фреймворков, включая Flask.
-Для проверки данных он использует Marshmallow и создан теми же авторами.
+Он использует Marshmallow для валидации данных. И создан теми же разработчиками.
-Это превосходный инструмент и я тоже часто пользовался им до **FastAPI**.
+Это отличный инструмент, и я тоже много им пользовался до появления **FastAPI**.
/// info | Информация
-Webargs бы создан разработчиками Marshmallow.
+Webargs был создан теми же разработчиками, что и Marshmallow.
///
-/// check | Идея для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Должна быть автоматическая проверка входных данных.
+Автоматическую валидацию входящих данных HTTP-запроса.
///
-### APISpec
+### APISpec { #apispec }
-Marshmallow и Webargs осуществляют проверку, анализ и сериализацию данных как плагины.
+Marshmallow и Webargs предоставляют валидацию, парсинг и сериализацию как плагины.
-Но документации API всё ещё не было. Тогда был создан APISpec.
+Но документации всё ещё не было. Тогда появился APISpec.
-Это плагин для множества фреймворков, в том числе и для Starlette.
+Это плагин для многих фреймворков (есть плагин и для Starlette).
-Он работает так - Вы записываете определение схем, используя формат YAML, внутри докстринга каждой функции, обрабатывающей маршрут.
+Он работает так: вы пишете определение схемы в формате YAML внутри докстринга каждой функции, обрабатывающей маршрут.
-Используя эти докстринги, он генерирует схему OpenAPI.
+И он генерирует схемы OpenAPI.
-Так это работает для Flask, Starlette, Responder и т.п.
+Так это работает во Flask, Starlette, Responder и т. д.
-Но теперь у нас возникает новая проблема - наличие постороннего микро-синтаксиса внутри кода Python (большие YAML).
+Но у нас снова возникает проблема: появляется микро-синтаксис внутри строки Python (большой YAML).
-Редактор кода не особо может помочь в такой парадигме.
-А изменив какие-то параметры или схемы для Marshmallow можно забыть отредактировать докстринг с YAML и сгенерированная схема становится недействительной.
+Редактор кода мало чем может помочь. И если мы изменим параметры или схемы Marshmallow и забудем также изменить YAML в докстринге, сгенерированная схема устареет.
/// info | Информация
-APISpec тоже был создан авторами Marshmallow.
+APISpec был создан теми же разработчиками, что и Marshmallow.
///
-/// check | Идея для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Необходима поддержка открытого стандарта для API - OpenAPI.
+Поддержку открытого стандарта для API — OpenAPI.
///
-### Flask-apispec
+### Flask-apispec { #flask-apispec }
-Это плагин для Flask, который связан с Webargs, Marshmallow и APISpec.
+Это плагин для Flask, который связывает Webargs, Marshmallow и APISpec.
-Он получает информацию от Webargs и Marshmallow, а затем использует APISpec для автоматического создания схемы OpenAPI.
+Он использует информацию из Webargs и Marshmallow, чтобы автоматически генерировать схемы OpenAPI с помощью APISpec.
-Это отличный, но крайне недооценённый инструмент.
-Он должен быть более популярен, чем многие плагины для Flask.
-Возможно, это связано с тем, что его документация слишком скудна и абстрактна.
+Отличный и недооценённый инструмент. Он заслуживает большей популярности, чем многие плагины для Flask. Возможно, из-за слишком краткой и абстрактной документации.
-Он избавил от необходимости писать чужеродный синтаксис YAML внутри докстрингов.
+Это решило проблему необходимости писать YAML (ещё один синтаксис) в докстрингах Python.
-Такое сочетание Flask, Flask-apispec, Marshmallow и Webargs было моим любимым стеком при построении бэкенда до появления **FastAPI**.
+Комбинация Flask, Flask-apispec с Marshmallow и Webargs была моим любимым бэкенд-стеком до создания **FastAPI**.
-Использование этого стека привело к созданию нескольких генераторов проектов. Я и некоторые другие команды до сих пор используем их:
+Его использование привело к созданию нескольких full-stack генераторов на Flask. Это основные стеки, которые я (и несколько внешних команд) использовали до сих пор:
* 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}.
+И эти же full-stack генераторы стали основой для [Генераторов проектов **FastAPI**](project-generation.md){.internal-link target=_blank}.
/// info | Информация
-Как ни странно, но Flask-apispec тоже создан авторами Marshmallow.
+Flask-apispec был создан теми же разработчиками, что и Marshmallow.
///
-/// check | Идея для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Схема OpenAPI должна создаваться автоматически и использовать тот же код, который осуществляет сериализацию и проверку данных.
+Автоматическую генерацию схемы OpenAPI из того же кода, который определяет сериализацию и валидацию.
///
-### NestJS (и Angular)
+### NestJS (и Angular) { #nestjs-and-angular }
-Здесь даже не используется Python. NestJS - этот фреймворк написанный на JavaScript (TypeScript), основанный на NodeJS и вдохновлённый Angular.
+Это даже не Python. NestJS — это JavaScript/TypeScript-фреймворк на NodeJS, вдохновлённый Angular.
-Он позволяет получить нечто похожее на то, что можно сделать с помощью Flask-apispec.
+Он достигает чего-то отчасти похожего на то, что можно сделать с Flask-apispec.
-В него встроена система внедрения зависимостей, ещё одна идея взятая от Angular.
-Однако требуется предварительная регистрация "внедрений" (как и во всех других известных мне системах внедрения зависимостей), что увеличивает количество и повторяемость кода.
+В нём встроена система внедрения зависимостей, вдохновлённая Angular 2. Требуется предварительная регистрация «инжектируемых» компонентов (как и во всех известных мне системах внедрения зависимостей), что добавляет многословности и повторяемости кода.
-Так как параметры в нём описываются с помощью типов TypeScript (аналогично подсказкам типов в Python), поддержка редактора работает довольно хорошо.
+Поскольку параметры описываются с помощью типов TypeScript (аналог аннотаций типов в Python), поддержка редактора весьма хороша.
-Но поскольку данные из TypeScript не сохраняются после компиляции в JavaScript, он не может полагаться на подсказки типов для определения проверки данных, сериализации и документации.
-Из-за этого и некоторых дизайнерских решений, для валидации, сериализации и автоматической генерации схем, приходится во многих местах добавлять декораторы.
-Таким образом, это становится довольно многословным.
+Но так как данные о типах TypeScript не сохраняются после компиляции в JavaScript, он не может полагаться на типы для одновременного определения валидации, сериализации и документации. Из‑за этого и некоторых проектных решений для получения валидации, сериализации и автоматической генерации схем приходится добавлять декораторы во многих местах. В итоге это становится довольно многословным.
-Кроме того, он не очень хорошо справляется с вложенными моделями.
-Если в запросе имеется объект JSON, внутренние поля которого, в свою очередь, являются вложенными объектами JSON, это не может быть должным образом задокументировано и проверено.
+Он плохо справляется с вложенными моделями. Если JSON-тело запроса — это объект JSON, содержащий внутренние поля, которые сами являются вложенными объектами JSON, это нельзя как следует задокументировать и провалидировать.
-/// check | Идеи для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Нужно использовать подсказки типов, чтоб воспользоваться поддержкой редактора кода.
+Использовать типы Python для отличной поддержки в редакторе кода.
-Нужна мощная система внедрения зависимостей. Необходим способ для уменьшения повторов кода.
+Иметь мощную систему внедрения зависимостей. Найти способ минимизировать повторение кода.
///
-### Sanic
+### Sanic { #sanic }
-Sanic был одним из первых чрезвычайно быстрых Python-фреймворков основанных на `asyncio`.
-Он был сделан очень похожим на Flask.
+Это был один из первых чрезвычайно быстрых Python-фреймворков на основе `asyncio`. Он был сделан очень похожим на Flask.
/// note | Технические детали
-В нём использован `uvloop` вместо стандартного цикла событий `asyncio`, что и сделало его таким быстрым.
+Он использовал `uvloop` вместо стандартного цикла `asyncio` в Python. Это и сделало его таким быстрым.
-Он явно вдохновил создателей Uvicorn и Starlette, которые в настоящее время быстрее Sanic в открытых бенчмарках.
+Он явно вдохновил Uvicorn и Starlette, которые сейчас быстрее Sanic в открытых бенчмарках.
///
-/// check | Идеи для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Должна быть сумасшедшая производительность.
+Поиск способа достичь сумасшедшей производительности.
-Для этого **FastAPI** основан на Starlette, самом быстром из доступных фреймворков (по замерам незаинтересованных лиц).
+Именно поэтому **FastAPI** основан на Starlette, так как это самый быстрый доступный фреймворк (по данным сторонних бенчмарков).
///
-### Falcon
+### Falcon { #falcon }
-Falcon - ещё один высокопроизводительный Python-фреймворк.
-В нём минимум функций и он создан, чтоб быть основой для других фреймворков, например, Hug.
+Falcon — ещё один высокопроизводительный Python-фреймворк, он минималистичен и служит основой для других фреймворков, таких как Hug.
-Функции в нём получают два параметра - "запрос к серверу" и "ответ сервера".
-Затем Вы "читаете" часть запроса и "пишите" часть ответа.
-Из-за такой конструкции невозможно объявить параметры запроса и тела сообщения со стандартными подсказками типов Python в качестве параметров функции.
+Он спроектирован так, что функции получают два параметра: «request» и «response». Затем вы «читаете» части из запроса и «пишете» части в ответ. Из‑за такого дизайна невозможно объявить параметры запроса и тело запроса стандартными аннотациями типов Python как параметры функции.
-Таким образом, и валидацию данных, и их сериализацию, и документацию нужно прописывать вручную.
-Либо эти функции должны быть встроены во фреймворк, сконструированный поверх Falcon, как в Hug.
-Такая же особенность присутствует и в других фреймворках, вдохновлённых идеей Falcon, использовать только один объект запроса и один объект ответа.
+Поэтому валидация данных, сериализация и документация должны выполняться в коде вручную, не автоматически. Либо должны быть реализованы во фреймворке поверх Falcon, как в Hug. Та же особенность есть и в других фреймворках, вдохновлённых дизайном Falcon — с одним объектом запроса и одним объектом ответа в параметрах.
-/// check | Идея для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Найдите способы добиться отличной производительности.
+Поиск способов получить отличную производительность.
-Объявлять параметры `ответа сервера` в функциях, как в Hug.
+Вместе с Hug (так как Hug основан на Falcon) вдохновило **FastAPI** объявлять параметр `response` в функциях.
-Хотя в FastAPI это необязательно и используется в основном для установки заголовков, куки и альтернативных кодов состояния.
+Хотя в FastAPI это необязательно, и используется в основном для установки HTTP-заголовков, cookie и альтернативных статус-кодов.
///
-### Molten
+### Molten { #molten }
-Molten мне попался на начальной стадии написания **FastAPI**. В нём были похожие идеи:
+Я обнаружил Molten на ранних этапах создания **FastAPI**. И у него были очень похожие идеи:
-* Использование подсказок типов.
-* Валидация и документация исходя из этих подсказок.
+* Основан на аннотациях типов Python.
+* Валидация и документация из этих типов.
* Система внедрения зависимостей.
-В нём не используются сторонние библиотеки (такие, как Pydantic) для валидации, сериализации и документации.
-Поэтому переиспользовать эти определения типов непросто.
+Он не использует стороннюю библиотеку для валидации, сериализации и документации, такую как Pydantic, — у него своя. Поэтому такие определения типов данных будет сложнее переиспользовать.
-Также требуется более подробная конфигурация и используется стандарт WSGI, который не предназначен для использования с высокопроизводительными инструментами, такими как Uvicorn, Starlette и Sanic, в отличие от ASGI.
+Требуются более многословные конфигурации. И так как он основан на WSGI (вместо ASGI), он не предназначен для использования преимуществ высокой производительности инструментов вроде Uvicorn, Starlette и Sanic.
-Его система внедрения зависимостей требует предварительной регистрации, и зависимости определяются, как объявления типов.
-Из-за этого невозможно объявить более одного "компонента" (зависимости), который предоставляет определенный тип.
+Система внедрения зависимостей требует предварительной регистрации зависимостей, а зависимости разрешаются по объявленным типам. Поэтому невозможно объявить более одного «компонента», предоставляющего определённый тип.
-Маршруты объявляются в единственном месте с использованием функций, объявленных в других местах (вместо использования декораторов, в которые могут быть обёрнуты функции, обрабатывающие конкретные ресурсы).
-Это больше похоже на Django, чем на Flask и Starlette.
-Он разделяет в коде вещи, которые довольно тесно связаны.
+Маршруты объявляются в одном месте, используя функции, объявленные в других местах (вместо декораторов, которые можно разместить прямо над функцией, обрабатывающей эндпоинт). Это ближе к тому, как это делает Django, чем Flask (и Starlette). Это разделяет в коде вещи, которые довольно тесно связаны.
-/// check | Идея для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Определить дополнительные проверки типов данных, используя значения атрибутов модели "по умолчанию".
-Это улучшает помощь редактора и раньше это не было доступно в Pydantic.
+Определять дополнительные проверки типов данных, используя значение «по умолчанию» атрибутов модели. Это улучшает поддержку в редакторе кода, и раньше этого не было в Pydantic.
-Фактически это подтолкнуло на обновление Pydantic для поддержки одинакового стиля проверок (теперь этот функционал уже доступен в Pydantic).
+Фактически это вдохновило на обновление частей Pydantic, чтобы поддерживать такой же стиль объявления валидации (вся эта функциональность теперь уже есть в Pydantic).
///
-### Hug
+### Hug { #hug }
-Hug был одним из первых фреймворков, реализовавших объявление параметров API с использованием подсказок типов Python.
-Эта отличная идея была использована и другими инструментами.
+Hug был одним из первых фреймворков, реализовавших объявление типов параметров API с использованием аннотаций типов Python. Это была отличная идея, которая вдохновила и другие инструменты.
-При объявлении параметров вместо стандартных типов Python использовались собственные типы, но всё же это был огромный шаг вперед.
+Он использовал собственные типы в объявлениях вместо стандартных типов Python, но это всё равно был огромный шаг вперёд.
-Это также был один из первых фреймворков, генерировавших полную API-схему в формате JSON.
+Он также был одним из первых фреймворков, генерировавших собственную схему, описывающую весь API в JSON.
-Данная схема не придерживалась стандартов вроде OpenAPI и JSON Schema.
-Поэтому было бы непросто совместить её с другими инструментами, такими как Swagger UI.
-Но опять же, это была очень инновационная идея.
+Он не был основан на стандартах вроде OpenAPI и JSON Schema. Поэтому интегрировать его с другими инструментами, такими как Swagger UI, было бы непросто. Но, опять же, это была очень инновационная идея.
-Ещё у него есть интересная и необычная функция: используя один и тот же фреймворк можно создавать и API, и CLI.
+У него есть интересная и необычная особенность: с помощью одного и того же фреймворка можно создавать и API, и CLI.
-Поскольку он основан на WSGI, старом стандарте для синхронных веб-фреймворков, он не может работать с веб-сокетами и другими модными штуками, но всё равно обладает высокой производительностью.
+Так как он основан на предыдущем стандарте для синхронных веб-фреймворков Python (WSGI), он не может работать с WebSocket и прочим, хотя также демонстрирует высокую производительность.
/// info | Информация
-Hug создан Timothy Crosley, автором `isort`, отличного инструмента для автоматической сортировки импортов в Python-файлах.
+Hug был создан Тимоти Кросли, тем же автором `isort`, отличного инструмента для автоматической сортировки импортов в файлах Python.
///
-/// check | Идеи для **FastAPI**
+/// check | Идеи, вдохновившие **FastAPI**
-Hug повлиял на создание некоторых частей APIStar и был одним из инструментов, которые я счел наиболее многообещающими, наряду с APIStar.
+Hug вдохновил части APIStar и был одним из наиболее многообещающих инструментов, которые я нашёл, наряду с APIStar.
-Hug натолкнул на мысли использовать в **FastAPI** подсказки типов Python для автоматического создания схемы, определяющей API и его параметры.
+Hug помог вдохновить **FastAPI** использовать аннотации типов Python для объявления параметров и автоматически генерировать схему, определяющую API.
-Hug вдохновил **FastAPI** объявить параметр `ответа` в функциях для установки заголовков и куки.
+Hug вдохновил **FastAPI** объявлять параметр `response` в функциях для установки HTTP-заголовков и cookie.
///
-### APIStar (<= 0.5)
+### APIStar (<= 0.5) { #apistar-0-5 }
-Непосредственно перед тем, как принять решение о создании **FastAPI**, я обнаружил **APIStar**.
-В нем было почти все, что я искал и у него был отличный дизайн.
+Прямо перед решением строить **FastAPI** я нашёл сервер **APIStar**. В нём было почти всё, что я искал, и отличный дизайн.
-Это была одна из первых реализаций фреймворка, использующего подсказки типов для объявления параметров и запросов, которые я когда-либо видел (до NestJS и Molten).
-Я нашёл его примерно в то же время, что и Hug, но APIStar использовал стандарт OpenAPI.
+Это была одна из первых реализаций фреймворка, использующего аннотации типов Python для объявления параметров и запросов (до NestJS и Molten), которые я видел. Я обнаружил его примерно в то же время, что и Hug. Но APIStar использовал стандарт OpenAPI.
-В нём были автоматические проверка и сериализация данных и генерация схемы OpenAPI основанные на подсказках типов в нескольких местах.
+В нём были автоматические валидация данных, сериализация данных и генерация схемы OpenAPI на основе тех же аннотаций типов в нескольких местах.
-При определении схемы тела сообщения не использовались подсказки типов, как в Pydantic, это больше похоже на Marshmallow, поэтому помощь редактора была недостаточно хорошей, но всё же APIStar был лучшим доступным вариантом.
+Определение схемы тела запроса не использовало те же аннотации типов Python, как в Pydantic, — это было ближе к Marshmallow, поэтому поддержка редактора была бы хуже, но всё равно APIStar оставался лучшим доступным вариантом.
-На тот момент у него были лучшие показатели производительности (проигрывающие только Starlette).
+На тот момент у него были лучшие показатели в бенчмарках (его превосходил только Starlette).
-Изначально у него не было автоматической документации API для веб-интерфейса, но я знал, что могу добавить к нему Swagger UI.
+Сначала у него не было веб‑UI для автоматической документации API, но я знал, что могу добавить к нему Swagger UI.
-В APIStar была система внедрения зависимостей, которая тоже требовала предварительную регистрацию компонентов, как и ранее описанные инструменты.
-Но, тем не менее, это была отличная штука.
+У него была система внедрения зависимостей. Она требовала предварительной регистрации компонентов, как и другие инструменты, обсуждавшиеся выше. Но всё же это была отличная возможность.
-Я не смог использовать его в полноценном проекте, так как были проблемы со встраиванием функций безопасности в схему OpenAPI, из-за которых невозможно было встроить все функции, применяемые в генераторах проектов на основе Flask-apispec.
-Я добавил в свой список задач создание пул-реквеста, добавляющего эту функциональность.
+Мне так и не удалось использовать его в полном проекте, поскольку не было интеграции с системой безопасности, поэтому я не мог заменить все возможности, которые имел с full-stack генераторами на основе Flask-apispec. В моём бэклоге было создать пулл-реквест (запрос на изменение), добавляющий эту функциональность.
-В дальнейшем фокус проекта сместился.
+Затем фокус проекта сместился.
-Это больше не был API-фреймворк, так как автор сосредоточился на Starlette.
+Это перестал быть веб-фреймворк для API, так как автору нужно было сосредоточиться на Starlette.
-Ныне APIStar - это набор инструментов для проверки спецификаций OpenAPI.
+Сейчас APIStar — это набор инструментов для валидации спецификаций OpenAPI, а не веб-фреймворк.
/// info | Информация
-APIStar был создан Tom Christie. Тот самый парень, который создал:
+APIStar был создан Томом Кристи. Тем самым человеком, который создал:
* Django REST Framework
* Starlette (на котором основан **FastAPI**)
-* Uvicorn (используемый в Starlette и **FastAPI**)
+* Uvicorn (используется Starlette и **FastAPI**)
///
-/// check | Идеи для **FastAPI**
+/// check | Вдохновило **FastAPI** на
-Воплощение.
+Существование.
-Мне казалось блестящей идеей объявлять множество функций (проверка данных, сериализация, документация) с помощью одних и тех же типов Python, которые при этом обеспечивают ещё и помощь редактора кода.
+Идея объявлять сразу несколько вещей (валидацию данных, сериализацию и документацию) с помощью одних и тех же типов Python, которые одновременно обеспечивают отличную поддержку в редакторе кода, показалась мне блестящей.
-После долгих поисков среди похожих друг на друга фреймворков и сравнения их различий, APIStar стал самым лучшим выбором.
+После долгих поисков похожего фреймворка и тестирования множества альтернатив APIStar был лучшим доступным вариантом.
-Но APIStar перестал быть фреймворком для создания веб-сервера, зато появился Starlette, новая и лучшая основа для построения подобных систем.
-Это была последняя капля, сподвигнувшая на создание **FastAPI**.
+Затем APIStar перестал существовать как сервер, а был создан Starlette — новая и лучшая основа для такой системы. Это стало окончательным вдохновением для создания **FastAPI**.
-Я считаю **FastAPI** "духовным преемником" APIStar, улучившим его возможности благодаря урокам, извлечённым из всех упомянутых выше инструментов.
+Я считаю **FastAPI** «духовным преемником» APIStar, который улучшает и расширяет возможности, систему типов и другие части, опираясь на уроки от всех этих предыдущих инструментов.
///
-## Что используется в **FastAPI**
+## Что используется в **FastAPI** { #used-by-fastapi }
+
+### Pydantic { #pydantic }
-### Pydantic
+Pydantic — это библиотека для определения валидации данных, сериализации и документации (с использованием JSON Schema) на основе аннотаций типов Python.
-Pydantic - это библиотека для валидации данных, сериализации и документирования (используя JSON Schema), основываясь на подсказках типов Python, что делает его чрезвычайно интуитивным.
+Благодаря этому он чрезвычайно интуитивен.
-Его можно сравнить с Marshmallow, хотя в бенчмарках Pydantic быстрее, чем Marshmallow.
-И он основан на тех же подсказках типов, которые отлично поддерживаются редакторами кода.
+Его можно сравнить с Marshmallow. Хотя в бенчмарках он быстрее Marshmallow. И поскольку он основан на тех же аннотациях типов Python, поддержка в редакторе кода отличная.
-/// check | **FastAPI** использует Pydantic
+/// check | **FastAPI** использует его для
-Для проверки данных, сериализации данных и автоматической документации моделей (на основе JSON Schema).
+Обработки всей валидации данных, сериализации данных и автоматической документации моделей (на основе JSON Schema).
-Затем **FastAPI** берёт эти схемы JSON и помещает их в схему OpenAPI, не касаясь других вещей, которые он делает.
+Затем **FastAPI** берёт эти данные JSON Schema и помещает их в OpenAPI, помимо всех прочих функций.
///
-### Starlette
+### Starlette { #starlette }
-Starlette - это легковесный ASGI фреймворк/набор инструментов, который идеален для построения высокопроизводительных асинхронных сервисов.
+Starlette — это лёгкий ASGI фреймворк/набор инструментов, идеально подходящий для создания высокопроизводительных asyncio‑сервисов.
-Starlette очень простой и интуитивный.
-Он разработан таким образом, чтобы быть легко расширяемым и иметь модульные компоненты.
+Он очень простой и интуитивный. Спроектирован так, чтобы его было легко расширять, и чтобы компоненты были модульными.
В нём есть:
* Впечатляющая производительность.
-* Поддержка веб-сокетов.
-* Фоновые задачи.
-* Обработка событий при старте и финише приложения.
-* Тестовый клиент на основе HTTPX.
-* Поддержка CORS, сжатие GZip, статические файлы, потоковая передача данных.
-* Поддержка сессий и куки.
+* Поддержка WebSocket.
+* Фоновые задачи, выполняемые в том же процессе.
+* События запуска и завершения.
+* Тестовый клиент на базе HTTPX.
+* CORS, GZip, статические файлы, потоковые ответы.
+* Поддержка сессий и cookie.
* 100% покрытие тестами.
-* 100% аннотированный код.
-* Несколько жёстких зависимостей.
+* 100% кодовой базы с аннотациями типов.
+* Мало жёстких зависимостей.
+
+В настоящее время Starlette — самый быстрый из протестированных Python-фреймворков. Его превосходит только Uvicorn, который не фреймворк, а сервер.
-В настоящее время Starlette показывает самую высокую скорость среди Python-фреймворков в тестовых замерах.
-Быстрее только Uvicorn, который является сервером, а не фреймворком.
+Starlette предоставляет весь базовый функционал веб-микрофреймворка.
-Starlette обеспечивает весь функционал микрофреймворка, но не предоставляет автоматическую валидацию данных, сериализацию и документацию.
+Но он не предоставляет автоматическую валидацию данных, сериализацию или документацию.
-**FastAPI** добавляет эти функции используя подсказки типов Python и Pydantic.
-Ещё **FastAPI** добавляет систему внедрения зависимостей, утилиты безопасности, генерацию схемы OpenAPI и т.д.
+Это одна из главных вещей, которые **FastAPI** добавляет поверх, всё на основе аннотаций типов Python (с использованием Pydantic). Плюс система внедрения зависимостей, утилиты безопасности, генерация схемы OpenAPI и т. д.
/// note | Технические детали
-ASGI - это новый "стандарт" разработанный участниками команды Django.
-Он пока что не является "стандартом в Python" (то есть принятым PEP), но процесс принятия запущен.
+ASGI — это новый «стандарт», разрабатываемый участниками core-команды Django. Он всё ещё не является «стандартом Python» (PEP), хотя процесс идёт.
-Тем не менее он уже используется в качестве "стандарта" несколькими инструментами.
-Это значительно улучшает совместимость, поскольку Вы можете переключиться с Uvicorn на любой другой ASGI-сервер (например, Daphne или Hypercorn) или Вы можете добавить ASGI-совместимые инструменты, такие как `python-socketio`.
+Тем не менее его уже используют как «стандарт» несколько инструментов. Это сильно улучшает совместимость: вы можете заменить Uvicorn на любой другой ASGI-сервер (например, Daphne или Hypercorn) или добавить совместимые с ASGI инструменты, такие как `python-socketio`.
///
-/// check | **FastAPI** использует Starlette
+/// check | **FastAPI** использует его для
-В качестве ядра веб-сервиса для обработки запросов, добавив некоторые функции сверху.
+Обработки всех основных веб-частей. Добавляя возможности поверх.
-Класс `FastAPI` наследуется напрямую от класса `Starlette`.
+Класс `FastAPI` напрямую наследуется от класса `Starlette`.
-Таким образом, всё что Вы могли делать со Starlette, Вы можете делать с **FastAPI**, по сути это прокачанный Starlette.
+Так что всё, что вы можете сделать со Starlette, вы можете сделать напрямую с **FastAPI**, по сути это «Starlette на стероидах».
///
-### Uvicorn
+### Uvicorn { #uvicorn }
-Uvicorn - это молниеносный ASGI-сервер, построенный на uvloop и httptools.
+Uvicorn — молниеносный ASGI-сервер, построенный на uvloop и httptools.
-Uvicorn является сервером, а не фреймворком.
-Например, он не предоставляет инструментов для маршрутизации запросов по ресурсам.
-Для этого нужна надстройка, такая как Starlette (или **FastAPI**).
+Это не веб-фреймворк, а сервер. Например, он не предоставляет инструменты для маршрутизации по путям. Это предоставляет сверху фреймворк, такой как Starlette (или **FastAPI**).
-Он рекомендуется в качестве сервера для Starlette и **FastAPI**.
+Это рекомендуемый сервер для Starlette и **FastAPI**.
-/// check | **FastAPI** рекомендует его
+/// check | **FastAPI** рекомендует его как
-Как основной сервер для запуска приложения **FastAPI**.
+Основной веб-сервер для запуска приложений **FastAPI**.
-Вы можете объединить его с Gunicorn, чтобы иметь асинхронный многопроцессный сервер.
+Также вы можете использовать опцию командной строки `--workers`, чтобы получить асинхронный многопроцессный сервер.
-Узнать больше деталей можно в разделе [Развёртывание](deployment/index.md){.internal-link target=_blank}.
+Подробнее см. раздел [Развёртывание](deployment/index.md){.internal-link target=_blank}.
///
-## Тестовые замеры и скорость
+## Бенчмарки и скорость { #benchmarks-and-speed }
-Чтобы понять, сравнить и увидеть разницу между Uvicorn, Starlette и FastAPI, ознакомьтесь с разделом [Тестовые замеры](benchmarks.md){.internal-link target=_blank}.
+Чтобы понять, сравнить и увидеть разницу между Uvicorn, Starlette и FastAPI, см. раздел о [Бенчмарках](benchmarks.md){.internal-link target=_blank}.
diff --git a/docs/ru/docs/async.md b/docs/ru/docs/async.md
index 813836d36..15d4e108a 100644
--- a/docs/ru/docs/async.md
+++ b/docs/ru/docs/async.md
@@ -1,18 +1,18 @@
-# Конкурентность и async / await
+# Конкурентность и async / await { #concurrency-and-async-await }
-Здесь приведена подробная информация об использовании синтаксиса `async def` при написании *функций обработки пути*, а также рассмотрены основы асинхронного программирования, конкурентности и параллелизма.
+Подробности о синтаксисе `async def` для *функций-обработчиков пути* и немного фона об асинхронном коде, конкурентности и параллелизме.
-## Нет времени?
+## Нет времени? { #in-a-hurry }
-TL;DR:
+TL;DR:
-Допустим, вы используете сторонюю библиотеку, которая требует вызова с ключевым словом `await`:
+Если вы используете сторонние библиотеки, которые нужно вызывать с `await`, например:
```Python
results = await some_library()
```
-В этом случае *функции обработки пути* необходимо объявлять с использованием синтаксиса `async def`:
+Тогда объявляйте *функции-обработчики пути* с `async def`, например:
```Python hl_lines="2"
@app.get('/')
@@ -21,18 +21,15 @@ async def read_results():
return results
```
-/// note
+/// note | Примечание
-`await` можно использовать только внутри функций, объявленных с использованием `async def`.
+`await` можно использовать только внутри функций, объявленных с `async def`.
///
---
-Если вы обращаетесь к сторонней библиотеке, которая с чем-то взаимодействует
-(с базой данных, API, файловой системой и т. д.), и не имеет поддержки синтаксиса `await`
-(что относится сейчас к большинству библиотек для работы с базами данных), то
-объявляйте *функции обработки пути* обычным образом с помощью `def`, например:
+Если вы используете стороннюю библиотеку, которая взаимодействует с чем-то (база данных, API, файловая система и т.д.) и не поддерживает использование `await` (сейчас это относится к большинству библиотек для БД), тогда объявляйте *функции-обработчики пути* как обычно, просто с `def`, например:
```Python hl_lines="2"
@app.get('/')
@@ -43,310 +40,283 @@ def results():
---
-Если вашему приложению (странным образом) не нужно ни с чем взаимодействовать и, соответственно,
-ожидать ответа, используйте `async def`.
+Если вашему приложению (по какой-то причине) не нужно ни с чем взаимодействовать и ждать ответа, используйте `async def`, даже если внутри не нужен `await`.
---
-Если вы не уверены, используйте обычный синтаксис `def`.
+Если вы просто не уверены, используйте обычный `def`.
---
-**Примечание**: при необходимости можно смешивать `def` и `async def` в *функциях обработки пути*
-и использовать в каждом случае наиболее подходящий синтаксис. А FastAPI сделает с этим всё, что нужно.
+**Примечание**: вы можете смешивать `def` и `async def` в *функциях-обработчиках пути* столько, сколько нужно, и объявлять каждую так, как лучше для вашего случая. FastAPI сделает с ними всё как надо.
-В любом из описанных случаев FastAPI работает асинхронно и очень быстро.
+В любом из случаев выше FastAPI всё равно работает асинхронно и очень быстро.
-Однако придерживаясь указанных советов, можно получить дополнительную оптимизацию производительности.
+Но следуя этим шагам, он сможет выполнить некоторые оптимизации производительности.
-## Технические подробности
+## Технические подробности { #technical-details }
-Современные версии Python поддерживают разработку так называемого **"асинхронного кода"** посредством написания **"сопрограмм"** с использованием синтаксиса **`async` и `await`**.
+Современные версии Python поддерживают **«асинхронный код»** с помощью **«сопрограмм»** (coroutines) и синтаксиса **`async` и `await`**.
-Ниже разберём эту фразу по частям:
+Разберём эту фразу по частям в разделах ниже:
* **Асинхронный код**
* **`async` и `await`**
* **Сопрограммы**
-## Асинхронный код
+## Асинхронный код { #asynchronous-code }
-Асинхронный код означает, что в языке 💬 есть возможность сообщить машине / программе 🤖,
-что в определённой точке кода ей 🤖 нужно будет ожидать завершения выполнения *чего-то ещё* в другом месте. Допустим это *что-то ещё* называется "медленный файл" 📝.
+Асинхронный код значит, что в языке 💬 есть способ сказать компьютеру/программе 🤖, что в некоторый момент кода ему 🤖 придётся подождать, пока *что-то ещё* где-то в другом месте завершится. Назовём это *что-то ещё* «медленный файл» 📝.
-И пока мы ждём завершения работы с "медленным файлом" 📝, компьютер может переключиться для выполнения других задач.
+И пока мы ждём завершения работы с «медленныи файлом» 📝, компьютер может заняться другой работой.
-Но при каждой возможности компьютер / программа 🤖 будет возвращаться обратно. Например, если он 🤖 опять окажется в режиме ожидания, или когда закончит всю работу. В этом случае компьютер 🤖 проверяет, не завершена ли какая-нибудь из текущих задач.
+Затем компьютер/программа 🤖 будет возвращаться каждый раз, когда появится возможность (пока снова где-то идёт ожидание), или когда 🤖 завершит всю текущую работу. И он 🤖 проверит, не завершилась ли какая-либо из задач, которых он ждал, и сделает то, что нужно.
-Потом он 🤖 берёт первую выполненную задачу (допустим, наш "медленный файл" 📝) и продолжает работу, производя с ней необходимые действия.
+Далее он 🤖 возьмёт первую завершившуюся задачу (скажем, наш «медленный файл» 📝) и продолжит делать с ней то, что требуется.
-Вышеупомянутое "что-то ещё", завершения которого приходится ожидать, обычно относится к достаточно "медленным" операциям I/O (по сравнению со скоростью работы процессора и оперативной памяти), например:
+Это «ожидание чего-то ещё» обычно относится к операциям I/O, которые относительно «медленные» (по сравнению со скоростью процессора и оперативной памяти), например ожидание:
-* отправка данных от клиента по сети
-* получение клиентом данных, отправленных вашей программой по сети
-* чтение системой содержимого файла с диска и передача этих данных программе
-* запись на диск данных, которые программа передала системе
-* обращение к удалённому API
-* ожидание завершения операции с базой данных
-* получение результатов запроса к базе данных
-* и т. д.
+* отправки данных клиентом по сети
+* получения клиентом данных, отправленных вашей программой по сети
+* чтения системой содержимого файла на диске и передачи этих данных вашей программе
+* записи на диск содержимого, которое ваша программа передала системе
+* операции удалённого API
+* завершения операции базы данных
+* возврата результатов запроса к базе данных
+* и т.д.
-Поскольку в основном время тратится на ожидание выполнения операций I/O,
-их обычно называют операциями, ограниченными скоростью ввода-вывода.
+Поскольку основное время выполнения уходит на ожидание операций I/O, их называют операциями, «ограниченными вводом-выводом» (I/O bound).
-Код называют "асинхронным", потому что компьютеру / программе не требуется "синхронизироваться" с медленной задачей и,
-будучи в простое, ожидать момента её завершения, с тем чтобы забрать результат и продолжить работу.
+Это называется «асинхронным», потому что компьютеру/программе не нужно «синхронизироваться» с медленной задачей, простаивая и выжидая точный момент её завершения, чтобы забрать результат и продолжить работу.
-Вместо этого в "асинхронной" системе завершённая задача может немного подождать (буквально несколько микросекунд),
-пока компьютер / программа занимается другими важными вещами, с тем чтобы потом вернуться,
-забрать результаты выполнения и начать их обрабатывать.
+Вместо этого, в «асинхронной» системе, уже завершившаяся задача может немного подождать (несколько микросекунд) в очереди, пока компьютер/программа завершит то, чем занимался, и затем вернётся, чтобы забрать результаты и продолжить работу с ними.
-"Синхронное" исполнение (в противовес "асинхронному") также называют "последовательным",
-потому что компьютер / программа последовательно выполняет все требуемые шаги перед тем, как перейти к следующей задаче,
-даже если в процессе приходится ждать.
+Для «синхронного» (в противоположность «асинхронному») исполнения часто используют термин «последовательный», потому что компьютер/программа выполняет все шаги по порядку, прежде чем переключиться на другую задачу, даже если эти шаги включают ожидание.
-### Конкурентность и бургеры
+### Конкурентность и бургеры { #concurrency-and-burgers }
-Тот **асинхронный** код, о котором идёт речь выше, иногда называют **"конкурентностью"**. Она отличается от **"параллелизма"**.
+Та идея **асинхронного** кода, описанная выше, иногда также называется **«конкурентностью»**. Она отличается от **«параллелизма»**.
-Да, **конкурентность** и **параллелизм** подразумевают, что разные вещи происходят примерно в одно время.
+И **конкурентность**, и **параллелизм** относятся к «разным вещам, происходящим примерно одновременно».
-Но внутреннее устройство **конкурентности** и **параллелизма** довольно разное.
+Но различия между *конкурентностью* и *параллелизмом* довольно существенные.
-Чтобы это понять, представьте такую картину:
+Чтобы их увидеть, представьте следующую историю про бургеры:
-### Конкурентные бургеры
+### Конкурентные бургеры { #concurrent-burgers }
-
+Вы идёте со своей возлюбленной за фастфудом, вы стоите в очереди, пока кассир принимает заказы у людей перед вами. 😍
-Вы идёте со своей возлюбленной 😍 в фастфуд 🍔 и становитесь в очередь, в это время кассир 💁 принимает заказы у посетителей перед вами.
+
-Когда наконец подходит очередь, вы заказываете парочку самых вкусных и навороченных бургеров 🍔, один для своей возлюбленной 😍, а другой себе.
+Наконец ваша очередь: вы заказываете 2 очень «навороченных» бургера — для вашей возлюбленной и для себя. 🍔🍔
-Отдаёте деньги 💸.
+
-Кассир 💁 что-то говорит поварам на кухне 👨🍳, теперь они знают, какие бургеры нужно будет приготовить 🍔
-(но пока они заняты бургерами предыдущих клиентов).
+Кассир говорит что-то повару на кухне, чтобы они знали, что нужно приготовить ваши бургеры (хотя сейчас они готовят бургеры для предыдущих клиентов).
-Кассир 💁 отдаёт вам чек с номером заказа.
+
-В ожидании еды вы идёте со своей возлюбленной 😍 выбрать столик, садитесь и довольно продолжительное время общаетесь 😍
-(поскольку ваши бургеры самые навороченные, готовятся они не так быстро ✨🍔✨).
+Вы платите. 💸
-Сидя за столиком с возлюбленной 😍 в ожидании бургеров 🍔, вы отлично проводите время,
-восхищаясь её великолепием, красотой и умом ✨😍✨.
+Кассир выдаёт вам номер вашей очереди.
-Всё ещё ожидая заказ и болтая со своей возлюбленной 😍, время от времени вы проверяете,
-какой номер горит над прилавком, и не подошла ли уже ваша очередь.
+
-И вот наконец настаёт этот момент, и вы идёте к стойке, чтобы забрать бургеры 🍔 и вернуться за столик.
+Пока вы ждёте, вы вместе со своей возлюбленной идёте и выбираете столик, садитесь и долго болтаете (ваши бургеры очень «навороченные», поэтому им нужно время на приготовление).
-Вы со своей возлюбленной 😍 едите бургеры 🍔 и отлично проводите время ✨.
+Сидя за столиком со своей возлюбленной в ожидании бургеров, вы можете провести это время, восхищаясь тем, какая она классная, милая и умная ✨😍✨.
+
+
+
+Пока вы ждёте и разговариваете, время от времени вы поглядываете на номер на табло, чтобы понять, не подошла ли уже ваша очередь.
+
+И вот в какой-то момент ваша очередь наступает. Вы подходите к стойке, забираете свои бургеры и возвращаетесь к столику.
+
+
+
+Вы со своей возлюбленной едите бургеры и отлично проводите время. ✨
+
+
+
+/// info | Информация
+
+Прекрасные иллюстрации от Ketrina Thompson. 🎨
+
+///
---
-А теперь представьте, что в этой небольшой истории вы компьютер / программа 🤖.
+Представьте, что в этой истории вы — компьютер/программа 🤖.
-В очереди вы просто глазеете по сторонам 😴, ждёте и ничего особо "продуктивного" не делаете.
-Но очередь движется довольно быстро, поскольку кассир 💁 только принимает заказы (а не занимается приготовлением еды), так что ничего страшного.
+Пока вы стоите в очереди, вы просто бездействуете 😴, ждёте своей очереди и не делаете ничего особо «продуктивного». Но очередь движется быстро, потому что кассир только принимает заказы (а не готовит их), так что это нормально.
-Когда подходит очередь вы наконец предпринимаете "продуктивные" действия 🤓: просматриваете меню, выбираете в нём что-то, узнаёте, что хочет ваша возлюбленная 😍, собираетесь оплатить 💸, смотрите, какую достали карту, проверяете, чтобы с вас списали верную сумму, и что в заказе всё верно и т. д.
+Когда приходит ваша очередь, вы выполняете действительно «продуктивную» работу: просматриваете меню, решаете, чего хотите, учитываете выбор своей возлюбленной, платите, проверяете, что дали правильную купюру/карту, что сумма списана корректно, что в заказе верные позиции и т.д.
-И хотя вы всё ещё не получили бургеры 🍔, ваша работа с кассиром 💁 ставится "на паузу" ⏸,
-поскольку теперь нужно ждать 🕙, когда заказ приготовят.
+Но затем, хотя у вас ещё нет бургеров, ваша «работа» с кассиром поставлена «на паузу» ⏸, потому что нужно подождать 🕙, пока бургеры будут готовы.
-Но отойдя с номерком от прилавка, вы садитесь за столик и можете переключить 🔀 внимание
-на свою возлюбленную 😍 и "работать" ⏯ 🤓 уже над этим. И вот вы снова очень
-"продуктивны" 🤓, мило болтаете вдвоём и всё такое 😍.
+Но, отойдя от стойки и сев за столик с номерком, вы можете переключить 🔀 внимание на свою возлюбленную и «поработать» ⏯ 🤓 над этим. Снова очень «продуктивно» — флирт с вашей возлюбленной 😍.
-В какой-то момент кассир 💁 поместит на табло ваш номер, подразумевая, что бургеры готовы 🍔, но вы не станете подскакивать как умалишённый, лишь только увидев на экране свою очередь. Вы уверены, что ваши бургеры 🍔 никто не утащит, ведь у вас свой номерок, а у других свой.
+Потом кассир 💁 «говорит»: «Я закончил делать бургеры», — выводя ваш номер на табло, но вы не подпрыгиваете как сумасшедший в ту же секунду, как только номер сменился на ваш. Вы знаете, что ваши бургеры никто не украдёт, потому что у вас есть номер вашей очереди, а у других — их.
-Поэтому вы подождёте, пока возлюбленная 😍 закончит рассказывать историю (закончите текущую работу ⏯ / задачу в обработке 🤓),
-и мило улыбнувшись, скажете, что идёте забирать заказ ⏸.
+Поэтому вы дожидаетесь, пока ваша возлюбленная закончит историю (завершится текущая работа ⏯ / выполняемая задача 🤓), мягко улыбаетесь и говорите, что идёте за бургерами ⏸.
-И вот вы подходите к стойке 🔀, к первоначальной задаче, которая уже завершена ⏯, берёте бургеры 🍔, говорите спасибо и относите заказ за столик. На этом заканчивается этап / задача взаимодействия с кассой ⏹.
-В свою очередь порождается задача "поедание бургеров" 🔀 ⏯, но предыдущая ("получение бургеров") завершена ⏹.
+Затем вы идёте к стойке 🔀, к исходной задаче, которая теперь завершена ⏯, забираете бургеры, благодарите и несёте их к столику. На этом шаг/задача взаимодействия со стойкой завершён ⏹. Это, в свою очередь, создаёт новую задачу — «есть бургеры» 🔀 ⏯, но предыдущая «получить бургеры» — завершена ⏹.
-### Параллельные бургеры
+### Параллельные бургеры { #parallel-burgers }
-Теперь представим, что вместо бургерной "Конкурентные бургеры" вы решили сходить в "Параллельные бургеры".
+Теперь представим, что это не «Конкурентные бургеры», а «Параллельные бургеры».
-И вот вы идёте со своей возлюбленной 😍 отведать параллельного фастфуда 🍔.
+Вы идёте со своей возлюбленной за параллельным фастфудом.
-Вы становитесь в очередь пока несколько (пусть будет 8) кассиров, которые по совместительству ещё и повары 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳, принимают заказы у посетителей перед вами.
+Вы стоите в очереди, пока несколько (скажем, 8) кассиров, которые одновременно являются поварами, принимают заказы у людей перед вами.
-При этом клиенты не отходят от стойки и ждут 🕙 получения еды, поскольку каждый
-из 8 кассиров идёт на кухню готовить бургеры 🍔, а только потом принимает следующий заказ.
+Все перед вами ждут, пока их бургеры будут готовы, не отходя от стойки, потому что каждый из 8 кассиров сразу идёт готовить бургер перед тем, как принять следующий заказ.
-Наконец настаёт ваша очередь, и вы просите два самых навороченных бургера 🍔, один для дамы сердца 😍, а другой себе.
+
-Ни о чём не жалея, расплачиваетесь 💸.
+Наконец ваша очередь: вы заказываете 2 очень «навороченных» бургера — для вашей возлюбленной и для себя.
-И кассир уходит на кухню 👨🍳.
+Вы платите 💸.
-Вам приходится ждать перед стойкой 🕙, чтобы никто по случайности не забрал ваши бургеры 🍔, ведь никаких номерков у вас нет.
+
-Поскольку вы с возлюбленной 😍 хотите получить заказ вовремя 🕙, и следите за тем, чтобы никто не вклинился в очередь,
-у вас не получается уделять должного внимание своей даме сердца 😞.
+Кассир уходит на кухню.
-Это "синхронная" работа, вы "синхронизированы" с кассиром/поваром 👨🍳. Приходится ждать 🕙 у стойки,
-когда кассир/повар 👨🍳 закончит делать бургеры 🍔 и вручит вам заказ, иначе его случайно может забрать кто-то другой.
+Вы ждёте, стоя у стойки 🕙, чтобы никто не забрал ваши бургеры раньше вас, так как никаких номерков нет.
-Наконец кассир/повар 👨🍳 возвращается с бургерами 🍔 после невыносимо долгого ожидания 🕙 за стойкой.
+
-Вы скорее забираете заказ 🍔 и идёте с возлюбленной 😍 за столик.
+Так как вы со своей возлюбленной заняты тем, чтобы никто не встал перед вами и не забрал ваши бургеры, как только они появятся, вы не можете уделить внимание своей возлюбленной. 😞
-Там вы просто едите эти бургеры, и на этом всё 🍔 ⏹.
+Это «синхронная» работа, вы «синхронизированы» с кассиром/поваром 👨🍳. Вам нужно ждать 🕙 и находиться там в точный момент, когда кассир/повар 👨🍳 закончит бургеры и вручит их вам, иначе их может забрать кто-то другой.
-Вам не особо удалось пообщаться, потому что большую часть времени 🕙 пришлось провести у кассы 😞.
+
----
+Затем ваш кассир/повар 👨🍳 наконец возвращается с вашими бургерами, после долгого ожидания 🕙 у стойки.
-В описанном сценарии вы компьютер / программа 🤖 с двумя исполнителями (вы и ваша возлюбленная 😍),
-на протяжении долгого времени 🕙 вы оба уделяете всё внимание ⏯ задаче "ждать на кассе".
+
-В этом ресторане быстрого питания 8 исполнителей (кассиров/поваров) 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳.
-Хотя в бургерной конкурентного типа было всего два (один кассир и один повар) 💁 👨🍳.
+Вы берёте бургеры и идёте со своей возлюбленной к столику.
-Несмотря на обилие работников, опыт в итоге получился не из лучших 😞.
+Вы просто их съедаете — и всё. ⏹
+
+
+
+Разговоров и флирта было немного, потому что большую часть времени вы ждали 🕙 у стойки. 😞
+
+/// info | Информация
+
+Прекрасные иллюстрации от Ketrina Thompson. 🎨
+
+///
---
-Так бы выглядел аналог истории про бургерную 🍔 в "параллельном" мире.
+В этом сценарии «параллельных бургеров» вы — компьютер/программа 🤖 с двумя процессорами (вы и ваша возлюбленная), оба ждут 🕙 и уделяют внимание ⏯ тому, чтобы «ждать у стойки» 🕙 долгое время.
+
+В ресторане 8 процессоров (кассиров/поваров). Тогда как в «конкурентных бургерах» могло быть только 2 (один кассир и один повар).
-Вот более реалистичный пример. Представьте себе банк.
+И всё же финальный опыт — не самый лучший. 😞
-До недавних пор в большинстве банков было несколько кассиров 👨💼👨💼👨💼👨💼 и длинные очереди 🕙🕙🕙🕙🕙🕙🕙🕙.
+---
-Каждый кассир обслуживал одного клиента, потом следующего 👨💼⏯.
+Это была параллельная версия истории про бургеры. 🍔
-Нужно было долгое время 🕙 стоять перед окошком вместе со всеми, иначе пропустишь свою очередь.
+Для более «жизненного» примера представьте банк.
-Сомневаюсь, что у вас бы возникло желание прийти с возлюбленной 😍 в банк 🏦 оплачивать налоги.
+До недавнего времени в большинстве банков было несколько кассиров 👨💼👨💼👨💼👨💼 и длинная очередь 🕙🕙🕙🕙🕙🕙🕙🕙.
-### Выводы о бургерах
+Все кассиры делают всю работу с одним клиентом за другим 👨💼⏯.
-В нашей истории про поход в фастфуд за бургерами приходится много ждать 🕙,
-поэтому имеет смысл организовать конкурентную систему ⏸🔀⏯.
+И вам приходится долго 🕙 стоять в очереди, иначе вы потеряете свою очередь.
-И то же самое с большинством веб-приложений.
+Вы вряд ли захотите взять свою возлюбленную 😍 с собой, чтобы заняться делами в банке 🏦.
-Пользователей очень много, но ваш сервер всё равно вынужден ждать 🕙 запросы по их слабому интернет-соединению.
+### Вывод про бургеры { #burger-conclusion }
-Потом снова ждать 🕙, пока вернётся ответ.
+В этом сценарии «фастфуда с вашей возлюбленной», так как много ожидания 🕙, гораздо логичнее иметь конкурентную систему ⏸🔀⏯.
-
-Это ожидание 🕙 измеряется микросекундами, но если всё сложить, то набегает довольно много времени.
+Так обстоит дело и с большинством веб-приложений.
-Вот почему есть смысл использовать асинхронное ⏸🔀⏯ программирование при построении веб-API.
+Очень много пользователей, но ваш сервер ждёт 🕙, пока их не самое хорошее соединение отправит их запросы.
-Большинство популярных фреймворков (включая Flask и Django) создавались
-до появления в Python новых возможностей асинхронного программирования. Поэтому
-их можно разворачивать с поддержкой параллельного исполнения или асинхронного
-программирования старого типа, которое не настолько эффективно.
+А затем снова ждёт 🕙, пока отправятся ответы.
-При том, что основная спецификация асинхронного взаимодействия Python с веб-сервером
-(ASGI)
-была разработана командой Django для внедрения поддержки веб-сокетов.
+Это «ожидание» 🕙 измеряется микросекундами, но если всё сложить, то в сумме получается много ожидания.
-Именно асинхронность сделала NodeJS таким популярным (несмотря на то, что он не параллельный),
-и в этом преимущество Go как языка программирования.
+Вот почему асинхронный ⏸🔀⏯ код очень уместен для веб-API.
-И тот же уровень производительности даёт **FastAPI**.
+Именно такая асинхронность сделала NodeJS популярным (хотя NodeJS — не параллельный), и это сильная сторона Go как языка программирования.
-Поскольку можно использовать преимущества параллелизма и асинхронности вместе,
-вы получаете производительность лучше, чем у большинства протестированных NodeJS фреймворков
-и на уровне с Go, который является компилируемым языком близким к C (всё благодаря Starlette).
+Того же уровня производительности вы получаете с **FastAPI**.
-### Получается, конкурентность лучше параллелизма?
+А так как можно одновременно использовать параллелизм и асинхронность, вы получаете производительность выше, чем у большинства протестированных фреймворков на NodeJS и на уровне Go, который — компилируемый язык, ближе к C (всё благодаря Starlette).
-Нет! Мораль истории совсем не в этом.
+### Конкурентность лучше параллелизма? { #is-concurrency-better-than-parallelism }
-Конкурентность отличается от параллелизма. Она лучше в **конкретных** случаях, где много времени приходится на ожидание.
-Вот почему она зачастую лучше параллелизма при разработке веб-приложений. Но это не значит, что конкурентность лучше в любых сценариях.
+Нет! Мораль истории не в этом.
-Давайте посмотрим с другой стороны, представьте такую картину:
+Конкурентность отличается от параллелизма. И она лучше в **конкретных** сценариях, где много ожидания. Поэтому при разработке веб-приложений она обычно намного лучше параллелизма. Но не во всём.
-> Вам нужно убраться в большом грязном доме.
+Чтобы уравновесить это, представьте такую короткую историю:
+
+> Вам нужно убрать большой грязный дом.
*Да, это вся история*.
---
-Тут не нужно нигде ждать 🕙, просто есть куча работы в разных частях дома.
+Здесь нигде нет ожидания 🕙, просто очень много работы в разных местах дома.
-Можно организовать очередь как в примере с бургерами, сначала гостиная, потом кухня,
-но это ни на что не повлияет, поскольку вы нигде не ждёте 🕙, а просто трёте да моете.
+Можно организовать «очереди» как в примере с бургерами — сначала гостиная, потом кухня, — но так как вы ничего не ждёте 🕙, а просто убираете и убираете, очереди ни на что не повлияют.
-И понадобится одинаковое количество времени с очередью (конкурентностью) и без неё,
-и работы будет сделано тоже одинаковое количество.
+На завершение уйдёт одинаковое время — с очередями (конкурентностью) и без них — и объём выполненной работы будет одинаковым.
-Однако в случае, если бы вы могли привести 8 бывших кассиров/поваров, а ныне уборщиков 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳,
-и каждый из них (вместе с вами) взялся бы за свой участок дома,
-с такой помощью вы бы закончили намного быстрее, делая всю работу **параллельно**.
+Но в этом случае, если бы вы могли привести 8 бывших кассиров/поваров, а теперь — уборщиков, и каждый из них (плюс вы) взял бы свою зону дома для уборки, вы могли бы сделать всю работу **параллельно**, с дополнительной помощью, и завершить гораздо быстрее.
-В описанном сценарии каждый уборщик (включая вас) был бы исполнителем, занятым на своём участке работы.
+В этом сценарии каждый уборщик (включая вас) был бы процессором, выполняющим свою часть работы.
-И поскольку большую часть времени выполнения занимает реальная работа (а не ожидание),
-а работу в компьютере делает ЦП,
-такие задачи называют ограниченными производительностью процессора.
+И так как основное время выполнения уходит на реальную работу (а не ожидание), а работу в компьютере выполняет CPU, такие задачи называют «ограниченными процессором» (CPU bound).
---
-Ограничение по процессору проявляется в операциях, где требуется выполнять сложные математические вычисления.
+Типичные примеры CPU-bound операций — те, которые требуют сложной математической обработки.
Например:
-* Обработка **звука** или **изображений**.
-* **Компьютерное зрение**: изображение состоит из миллионов пикселей, в каждом пикселе 3 составляющих цвета,
-обработка обычно требует проведения расчётов по всем пикселям сразу.
-* **Машинное обучение**: здесь обычно требуется умножение "матриц" и "векторов".
-Представьте гигантскую таблицу с числами в Экселе, и все их надо одновременно перемножить.
-* **Глубокое обучение**: это область *машинного обучения*, поэтому сюда подходит то же описание.
-Просто у вас будет не одна таблица в Экселе, а множество. В ряде случаев используется
-специальный процессор для создания и / или использования построенных таким образом моделей.
+* Обработка **аудио** или **изображений**.
+* **Компьютерное зрение**: изображение состоит из миллионов пикселей, каждый пиксель имеет 3 значения/цвета; обычно требуется вычислить что-то для всех этих пикселей одновременно.
+* **Машинное обучение**: обычно требует множества умножений «матриц» и «векторов». Представьте огромную таблицу с числами и умножение всех этих чисел «одновременно».
+* **Глубокое обучение**: это подполе Машинного обучения, так что всё вышесказанное применимо. Просто это не одна таблица чисел, а их огромный набор, и во многих случаях вы используете специальный процессор, чтобы строить и/или использовать такие модели.
-### Конкурентность + параллелизм: Веб + машинное обучение
+### Конкурентность + параллелизм: Веб + Машинное обучение { #concurrency-parallelism-web-machine-learning }
-**FastAPI** предоставляет возможности конкуретного программирования,
-которое очень распространено в веб-разработке (именно этим славится NodeJS).
+С **FastAPI** вы можете использовать преимущества конкурентности, что очень распространено в веб-разработке (это та же основная «фишка» NodeJS).
-Кроме того вы сможете использовать все преимущества параллелизма и
-многопроцессорности (когда несколько процессов работают параллельно),
-если рабочая нагрузка предполагает **ограничение по процессору**,
-как, например, в системах машинного обучения.
+Но вы также можете использовать выгоды параллелизма и многопроцессности (когда несколько процессов работают параллельно) для рабочих нагрузок, **ограниченных процессором** (CPU bound), как в системах Машинного обучения.
-Необходимо также отметить, что Python является главным языком в области
-**дата-сайенс**,
-машинного обучения и, особенно, глубокого обучения. Всё это делает FastAPI
-отличным вариантом (среди многих других) для разработки веб-API и приложений
-в области дата-сайенс / машинного обучения.
+Плюс к этому простой факт, что Python — основной язык для **Data Science**, Машинного обучения и особенно Глубокого обучения, делает FastAPI очень хорошим выбором для веб-API и приложений в области Data Science / Машинного обучения (среди многих других).
-Как добиться такого параллелизма в эксплуатации описано в разделе [Развёртывание](deployment/index.md){.internal-link target=_blank}.
+Как добиться такого параллелизма в продакшн, см. раздел [Развёртывание](deployment/index.md){.internal-link target=_blank}.
-## `async` и `await`
+## `async` и `await` { #async-and-await }
-В современных версиях Python разработка асинхронного кода реализована очень интуитивно.
-Он выглядит как обычный "последовательный" код и самостоятельно выполняет "ожидание", когда это необходимо.
+В современных версиях Python есть очень интуитивный способ определять асинхронный код. Это делает его похожим на обычный «последовательный» код, а «ожидание» выполняется за вас в нужные моменты.
-Если некая операция требует ожидания перед тем, как вернуть результат, и
-поддерживает современные возможности Python, код можно написать следующим образом:
+Когда есть операция, которой нужно подождать перед тем, как вернуть результат, и она поддерживает эти новые возможности Python, вы можете написать так:
```Python
burgers = await get_burgers(2)
```
-Главное здесь слово `await`. Оно сообщает интерпретатору, что необходимо дождаться ⏸
-пока `get_burgers(2)` закончит свои дела 🕙, и только после этого сохранить результат в `burgers`.
-Зная это, Python может пока переключиться на выполнение других задач 🔀 ⏯
-(например получение следующего запроса).
+Ключ здесь — `await`. Он говорит Python, что нужно подождать ⏸, пока `get_burgers(2)` закончит своё дело 🕙, прежде чем сохранять результат в `burgers`. Благодаря этому Python будет знать, что за это время можно заняться чем-то ещё 🔀 ⏯ (например, принять другой запрос).
-Чтобы ключевое слово `await` сработало, оно должно находиться внутри функции,
-которая поддерживает асинхронность. Для этого вам просто нужно объявить её как `async def`:
+Чтобы `await` работал, он должен находиться внутри функции, которая поддерживает такую асинхронность. Для этого просто объявите её с `async def`:
```Python hl_lines="1"
async def get_burgers(number: int):
- # Готовим бургеры по специальному асинхронному рецепту
+ # Сделать что-то асинхронное, чтобы приготовить бургеры
return burgers
```
@@ -355,26 +325,22 @@ async def get_burgers(number: int):
```Python hl_lines="2"
# Это не асинхронный код
def get_sequential_burgers(number: int):
- # Готовим бургеры последовательно по шагам
+ # Сделать что-то последовательное, чтобы приготовить бургеры
return burgers
```
-Объявление `async def` указывает интерпретатору, что внутри этой функции
-следует ожидать выражений `await`, и что можно поставить выполнение такой функции на "паузу" ⏸ и
-переключиться на другие задачи 🔀, с тем чтобы вернуться сюда позже.
+С `async def` Python знает, что внутри этой функции нужно учитывать выражения `await` и что выполнение такой функции можно «приостанавливать» ⏸ и идти делать что-то ещё 🔀, чтобы потом вернуться.
-Если вы хотите вызвать функцию с `async def`, вам нужно "ожидать" её.
-Поэтому такое не сработает:
+Когда вы хотите вызвать функцию, объявленную с `async def`, нужно её «ожидать». Поэтому вот так не сработает:
```Python
-# Это не заработает, поскольку get_burgers объявлена с использованием async def
+# Это не сработает, потому что get_burgers определена с: async def
burgers = get_burgers(2)
```
---
-Если сторонняя библиотека требует вызывать её с ключевым словом `await`,
-необходимо писать *функции обработки пути* с использованием `async def`, например:
+Итак, если вы используете библиотеку, которую можно вызывать с `await`, вам нужно создать *функцию-обработчик пути*, которая её использует, с `async def`, например:
```Python hl_lines="2-3"
@app.get('/burgers')
@@ -383,129 +349,96 @@ async def read_burgers():
return burgers
```
-### Технические подробности
+### Более технические подробности { #more-technical-details }
+
+Вы могли заметить, что `await` можно использовать только внутри функций, определённых с `async def`.
-Как вы могли заметить, `await` может применяться только в функциях, объявленных с использованием `async def`.
+Но при этом функции, определённые с `async def`, нужно «ожидать». Значит, функции с `async def` тоже можно вызывать только из функций, определённых с `async def`.
-
-Но выполнение такой функции необходимо "ожидать" с помощью `await`.
-Это означает, что её можно вызвать только из другой функции, которая тоже объявлена с `async def`.
+Так что же с «яйцом и курицей» — как вызвать первую `async` функцию?
-Но как же тогда появилась первая курица? В смысле... как нам вызвать первую асинхронную функцию?
+Если вы работаете с **FastAPI**, вам не о чем беспокоиться, потому что этой «первой» функцией будет ваша *функция-обработчик пути*, а FastAPI знает, как сделать всё правильно.
-При работе с **FastAPI** просто не думайте об этом, потому что "первой" функцией является ваша *функция обработки пути*,
-и дальше с этим разберётся FastAPI.
+Но если вы хотите использовать `async` / `await` без FastAPI, вы тоже можете это сделать.
-Кроме того, если хотите, вы можете использовать синтаксис `async` / `await` и без FastAPI.
+### Пишите свой асинхронный код { #write-your-own-async-code }
-### Пишите свой асинхронный код
+Starlette (и **FastAPI**) основаны на AnyIO, что делает их совместимыми и со стандартной библиотекой Python asyncio, и с Trio.
-Starlette (и **FastAPI**) основаны на AnyIO, что делает их совместимыми как со стандартной библиотекой asyncio в Python, так и с Trio.
+В частности, вы можете напрямую использовать AnyIO для продвинутых сценариев конкурентности, где в вашем коде нужны более сложные паттерны.
-В частности, вы можете напрямую использовать AnyIO в тех проектах, где требуется более сложная логика работы с конкурентностью.
+И даже если вы не используете FastAPI, вы можете писать свои асинхронные приложения с AnyIO, чтобы они были максимально совместимыми и получали его преимущества (например, *структурную конкурентность*).
-Даже если вы не используете FastAPI, вы можете писать асинхронные приложения с помощью AnyIO, чтобы они были максимально совместимыми и получали его преимущества (например *структурную конкурентность*).
+Я создал ещё одну библиотеку поверх AnyIO, тонкий слой, чтобы немного улучшить аннотации типов и получить более качественное **автозавершение**, **ошибки прямо в редакторе** и т.д. Там также есть дружелюбное введение и руководство, чтобы помочь вам **понять** и писать **свой собственный асинхронный код**: Asyncer. Она особенно полезна, если вам нужно **комбинировать асинхронный код с обычным** (блокирующим/синхронным) кодом.
-### Другие виды асинхронного программирования
+### Другие формы асинхронного кода { #other-forms-of-asynchronous-code }
-Стиль написания кода с `async` и `await` появился в языке Python относительно недавно.
+Такой стиль использования `async` и `await` относительно новый в языке.
-Но он сильно облегчает работу с асинхронным кодом.
+Но он сильно упрощает работу с асинхронным кодом.
-Ровно такой же синтаксис (ну или почти такой же) недавно был включён в современные версии JavaScript (в браузере и NodeJS).
+Такой же (или почти такой же) синтаксис недавно появился в современных версиях JavaScript (в браузере и NodeJS).
-До этого поддержка асинхронного кода была реализована намного сложнее, и его было труднее воспринимать.
+До этого работа с асинхронным кодом была заметно сложнее и труднее для понимания.
-В предыдущих версиях Python для этого использовались потоки или Gevent. Но такой код намного сложнее понимать, отлаживать и мысленно представлять.
+В предыдущих версиях Python можно было использовать потоки или Gevent. Но такой код гораздо сложнее понимать, отлаживать и держать в голове.
-Что касается JavaScript (в браузере и NodeJS), раньше там использовали для этой цели
-"обратные вызовы". Что выливалось в
-"ад обратных вызовов".
+В прежних версиях NodeJS/браузерного JavaScript вы бы использовали «callbacks» (обратные вызовы), что приводит к «callback hell» (ад обратных вызовов).
-## Сопрограммы
+## Сопрограммы { #coroutines }
-**Корути́на** (или же сопрограмма) — это крутое словечко для именования той сущности,
-которую возвращает функция `async def`. Python знает, что её можно запустить, как и обычную функцию,
-но кроме того сопрограмму можно поставить на паузу ⏸ в том месте, где встретится слово `await`.
+**Сопрограмма** (coroutine) — это просто «навороченное» слово для того, что возвращает функция `async def`. Python знает, что это похоже на функцию: её можно запустить, она когда-нибудь завершится, но её выполнение может приостанавливаться ⏸ внутри, когда встречается `await`.
-Всю функциональность асинхронного программирования с использованием `async` и `await`
-часто обобщают словом "корутины". Они аналогичны "горутинам", ключевой особенности
-языка Go.
+Часто всю функциональность использования асинхронного кода с `async` и `await` кратко называют «сопрограммами». Это сопоставимо с ключевой особенностью Go — «goroutines».
-## Заключение
+## Заключение { #conclusion }
-В самом начале была такая фраза:
+Вернёмся к той же фразе:
-> Современные версии Python поддерживают разработку так называемого
-**"асинхронного кода"** посредством написания **"сопрограмм"** с использованием
-синтаксиса **`async` и `await`**.
+> Современные версии Python поддерживают **«асинхронный код»** с помощью **«сопрограмм»** (coroutines) и синтаксиса **`async` и `await`**.
-Теперь всё должно звучать понятнее. ✨
+Теперь это должно звучать понятнее. ✨
-На этом основана работа FastAPI (посредством Starlette), и именно это
-обеспечивает его высокую производительность.
+Именно это «движет» FastAPI (через Starlette) и обеспечивает столь впечатляющую производительность.
-## Очень технические подробности
+## Очень технические подробности { #very-technical-details }
-/// warning
+/// warning | Предупреждение
-Этот раздел читать не обязательно.
+Скорее всего, этот раздел можно пропустить.
-Здесь приводятся подробности внутреннего устройства **FastAPI**.
+Здесь — очень технические подробности о том, как **FastAPI** работает «под капотом».
-Но если вы обладаете техническими знаниями (корутины, потоки, блокировка и т. д.)
-и вам интересно, как FastAPI обрабатывает `async def` в отличие от обычных `def`,
-читайте дальше.
+Если у вас есть достаточно технических знаний (сопрограммы, потоки, блокировки и т.д.) и вам интересно, как FastAPI обрабатывает `async def` по сравнению с обычным `def`, — вперёд.
///
-### Функции обработки пути
+### Функции-обработчики пути { #path-operation-functions }
-Когда вы объявляете *функцию обработки пути* обычным образом с ключевым словом `def`
-вместо `async def`, FastAPI ожидает её выполнения, запустив функцию во внешнем
-пуле потоков, а не напрямую (это бы заблокировало сервер).
+Когда вы объявляете *функцию-обработчик пути* обычным `def` вместо `async def`, она запускается во внешнем пуле потоков, который затем «ожидается», вместо прямого вызова (прямой вызов заблокировал бы сервер).
-Если ранее вы использовали другой асинхронный фреймворк, который работает иначе,
-и привыкли объявлять простые вычислительные *функции* через `def` ради
-незначительного прироста скорости (порядка 100 наносекунд), обратите внимание,
-что с **FastAPI** вы получите противоположный эффект. В таком случае больше подходит
-`async def`, если только *функция обработки пути* не использует код, приводящий
-к блокировке I/O.
-
+Если вы пришли из другого async-фреймворка, который работает иначе, и привыкли объявлять тривиальные *функции-обработчики пути*, выполняющие только вычисления, через простой `def` ради крошечной выгоды в производительности (около 100 наносекунд), обратите внимание: в **FastAPI** эффект будет противоположным. В таких случаях лучше использовать `async def`, если только ваши *функции-обработчики пути* не используют код, выполняющий блокирующий I/O.
-
-Но в любом случае велика вероятность, что **FastAPI** [окажется быстрее](index.md#_11){.internal-link target=_blank}
-другого фреймворка (или хотя бы на уровне с ним).
+Тем не менее, в обоих случаях велика вероятность, что **FastAPI** [всё равно будет быстрее](index.md#performance){.internal-link target=_blank} (или как минимум сопоставим) с вашим предыдущим фреймворком.
-### Зависимости
+### Зависимости { #dependencies }
-То же относится к зависимостям. Если это обычная функция `def`, а не `async def`,
-она запускается во внешнем пуле потоков.
+То же относится к [зависимостям](tutorial/dependencies/index.md){.internal-link target=_blank}. Если зависимость — это обычная функция `def`, а не `async def`, она запускается во внешнем пуле потоков.
-### Подзависимости
+### Подзависимости { #sub-dependencies }
-Вы можете объявить множество ссылающихся друг на друга зависимостей и подзависимостей
-(в виде параметров при определении функции). Какие-то будут созданы с помощью `async def`,
-другие обычным образом через `def`, и такая схема вполне работоспособна. Функции,
-объявленные с помощью `def` будут запускаться на внешнем потоке (из пула),
-а не с помощью `await`.
+У вас может быть несколько зависимостей и [подзависимостей](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}, которые требуют друг друга (в виде параметров определений функций): часть из них может быть объявлена с `async def`, а часть — обычным `def`. Всё будет работать, а те, что объявлены обычным `def`, будут вызываться во внешнем потоке (из пула), а не «ожидаться».
-### Другие служебные функции
+### Другие служебные функции { #other-utility-functions }
-Любые другие служебные функции, которые вы вызываете напрямую, можно объявлять
-с использованием `def` или `async def`. FastAPI не будет влиять на то, как вы
-их запускаете.
+Любые другие служебные функции, которые вы вызываете напрямую, можно объявлять обычным `def` или `async def`, и FastAPI не будет влиять на то, как вы их вызываете.
-Этим они отличаются от функций, которые FastAPI вызывает самостоятельно:
-*функции обработки пути* и зависимости.
+В отличие от функций, которые FastAPI вызывает за вас: *функции-обработчики пути* и зависимости.
-Если служебная функция объявлена с помощью `def`, она будет вызвана напрямую
-(как вы и написали в коде), а не в отдельном потоке. Если же она объявлена с
-помощью `async def`, её вызов должен осуществляться с ожиданием через `await`.
+Если служебная функция — обычная функция с `def`, она будет вызвана напрямую (как вы и пишете в коде), не в пуле потоков; если функция объявлена с `async def`, тогда при её вызове в вашем коде вы должны использовать `await`.
---
-
-Ещё раз повторим, что все эти технические подробности полезны, только если вы специально их искали.
+Снова: это очень технические подробности, полезные, вероятно, только если вы целенаправленно их ищете.
-В противном случае просто ознакомьтесь с основными принципами в разделе выше: Нет времени?.
+Иначе вам достаточно руководствоваться рекомендациями из раздела выше: Нет времени?.
diff --git a/docs/ru/docs/benchmarks.md b/docs/ru/docs/benchmarks.md
index 259dca8e6..612b39f70 100644
--- a/docs/ru/docs/benchmarks.md
+++ b/docs/ru/docs/benchmarks.md
@@ -1,37 +1,34 @@
-# Замеры производительности
+# Бенчмарки (тесты производительности) { #benchmarks }
-Независимые тесты производительности приложений от TechEmpower показывают, что **FastAPI** под управлением Uvicorn один из самых быстрых Python-фреймворков и уступает только Starlette и Uvicorn (которые используются в FastAPI). (*)
+Независимые бенчмарки TechEmpower показывают, что приложения **FastAPI** под управлением Uvicorn — одни из самых быстрых Python‑фреймворков, уступающие только Starlette и самому Uvicorn (используются внутри FastAPI).
-Но при просмотре и сравнении замеров производительности следует иметь в виду нижеописанное.
+Но при просмотре бенчмарков и сравнений следует иметь в виду следующее.
-## Замеры производительности и скорости
+## Бенчмарки и скорость { #benchmarks-and-speed }
-В подобных тестах часто можно увидеть, что инструменты разного типа сравнивают друг с другом, как аналогичные.
+При проверке бенчмарков часто можно увидеть, что инструменты разных типов сравнивают как эквивалентные.
-В частности, сравнивают вместе Uvicorn, Starlette и FastAPI (среди многих других инструментов).
+В частности, часто сравнивают вместе Uvicorn, Starlette и FastAPI (среди многих других инструментов).
-Чем проще проблема, которую решает инструмент, тем выше его производительность. И большинство тестов не проверяют дополнительные функции, предоставляемые инструментом.
+Чем проще задача, которую решает инструмент, тем выше его производительность. И большинство бенчмарков не тестируют дополнительные возможности, предоставляемые инструментом.
-Иерархия инструментов имеет следующий вид:
+Иерархия выглядит так:
* **Uvicorn**: ASGI-сервер
- * **Starlette** (использует Uvicorn): веб-микрофреймворк
- * **FastAPI** (использует Starlette): API-микрофреймворк с дополнительными функциями для создания API, с валидацией данных и т.д.
+ * **Starlette**: (использует Uvicorn) веб-микрофреймворк
+ * **FastAPI**: (использует Starlette) API-микрофреймворк с рядом дополнительных возможностей для создания API, включая валидацию данных и т. п.
* **Uvicorn**:
- * Будет иметь наилучшую производительность, так как не имеет большого количества дополнительного кода, кроме самого сервера.
- * Вы не будете писать приложение на Uvicorn напрямую. Это означало бы, что Ваш код должен включать как минимум весь
- код, предоставляемый Starlette (или **FastAPI**). И если Вы так сделаете, то в конечном итоге Ваше приложение будет иметь те же накладные расходы, что и при использовании фреймворка, минимизирующего код Вашего приложения и Ваши ошибки.
- * Uvicorn подлежит сравнению с Daphne, Hypercorn, uWSGI и другими веб-серверами.
-
+ * Будет иметь наилучшую производительность, так как помимо самого сервера у него немного дополнительного кода.
+ * Вы не будете писать приложение непосредственно на Uvicorn. Это означало бы, что Ваш код должен включать как минимум весь код, предоставляемый Starlette (или **FastAPI**). И если Вы так сделаете, то в конечном итоге Ваше приложение будет иметь те же накладные расходы, что и при использовании фреймворка, минимизирующего код Вашего приложения и Ваши ошибки.
+ * Если Вы сравниваете Uvicorn, сравнивайте его с Daphne, Hypercorn, uWSGI и т. д. — серверами приложений.
* **Starlette**:
- * Будет уступать Uvicorn по производительности. Фактически Starlette управляется Uvicorn и из-за выполнения большего количества кода он не может быть быстрее, чем Uvicorn.
- * Зато он предоставляет Вам инструменты для создания простых веб-приложений с обработкой маршрутов URL и т.д.
- * Starlette следует сравнивать с Sanic, Flask, Django и другими веб-фреймворками (или микрофреймворками).
-
+ * Будет на следующем месте по производительности после Uvicorn. Фактически Starlette запускается под управлением Uvicorn, поэтому он может быть только «медленнее» Uvicorn из‑за выполнения большего объёма кода.
+ * Зато он предоставляет Вам инструменты для создания простых веб‑приложений с маршрутизацией по путям и т. п.
+ * Если Вы сравниваете Starlette, сравнивайте его с Sanic, Flask, Django и т. д. — веб‑фреймворками (или микрофреймворками).
* **FastAPI**:
- * Так же как Starlette использует Uvicorn и не может быть быстрее него, **FastAPI** использует Starlette, то есть он не может быть быстрее Starlette.
- * FastAPI предоставляет больше возможностей поверх Starlette, которые наверняка Вам понадобятся при создании API, такие как проверка данных и сериализация. В довесок Вы ещё и получаете автоматическую документацию (автоматическая документация даже не увеличивает накладные расходы при работе приложения, так как она создается при запуске).
- * Если Вы не используете FastAPI, а используете Starlette напрямую (или другой инструмент вроде Sanic, Flask, Responder и т.д.), Вам пришлось бы самостоятельно реализовать валидацию и сериализацию данных. То есть, в итоге, Ваше приложение имело бы такие же накладные расходы, как если бы оно было создано с использованием FastAPI. И во многих случаях валидация и сериализация данных представляют собой самый большой объём кода, написанного в приложениях.
- * Таким образом, используя FastAPI Вы потратите меньше времени на разработку, уменьшите количество ошибок, строк кода и, вероятно, получите ту же производительность (или лучше), как и если бы не использовали его (поскольку Вам пришлось бы реализовать все его возможности в своем коде).
- * FastAPI должно сравнивать с фреймворками веб-приложений (или наборами инструментов), которые обеспечивают валидацию и сериализацию данных, а также предоставляют автоматическую документацию, такими как Flask-apispec, NestJS, Molten и им подобные.
+ * Точно так же, как Starlette использует Uvicorn и не может быть быстрее него, **FastAPI** использует Starlette, поэтому не может быть быстрее его.
+ * FastAPI предоставляет больше возможностей поверх Starlette — те, которые почти всегда нужны при создании API, такие как валидация и сериализация данных. В довесок Вы ещё и получаете автоматическую документацию (автоматическая документация даже не увеличивает накладные расходы при работе приложения, так как она создаётся при запуске).
+ * Если бы Вы не использовали FastAPI, а использовали Starlette напрямую (или другой инструмент вроде Sanic, Flask, Responder и т. д.), Вам пришлось бы самостоятельно реализовать валидацию и сериализацию данных. То есть, в итоге, Ваше приложение имело бы такие же накладные расходы, как если бы оно было создано с использованием FastAPI. И во многих случаях валидация и сериализация данных представляют собой самый большой объём кода, написанного в приложениях.
+ * Таким образом, используя FastAPI, Вы экономите время разработки, уменьшаете количество ошибок, строк кода и, вероятно, получите ту же производительность (или лучше), как и если бы не использовали его (поскольку Вам пришлось бы реализовать все его возможности в своём коде).
+ * Если Вы сравниваете FastAPI, сравнивайте его с фреймворком веб‑приложений (или набором инструментов), который обеспечивает валидацию данных, сериализацию и документацию, такими как Flask-apispec, NestJS, Molten и им подобные. Фреймворки с интегрированной автоматической валидацией данных, сериализацией и документацией.
diff --git a/docs/ru/docs/deployment/cloud.md b/docs/ru/docs/deployment/cloud.md
new file mode 100644
index 000000000..955db2a15
--- /dev/null
+++ b/docs/ru/docs/deployment/cloud.md
@@ -0,0 +1,24 @@
+# Развертывание FastAPI у облачных провайдеров { #deploy-fastapi-on-cloud-providers }
+
+Вы можете использовать практически любого облачного провайдера, чтобы развернуть свое приложение на FastAPI.
+
+В большинстве случаев у основных облачных провайдеров есть руководства по развертыванию FastAPI на их платформе.
+
+## FastAPI Cloud { #fastapi-cloud }
+
+**FastAPI Cloud** создан тем же автором и командой, стоящими за **FastAPI**.
+
+Он упрощает процесс **создания образа**, **развертывания** и **доступа** к API с минимальными усилиями.
+
+Он переносит тот же **опыт разработчика** создания приложений с FastAPI на их **развертывание** в облаке. 🎉
+
+FastAPI Cloud — основной спонсор и источник финансирования для open source проектов *FastAPI and friends*. ✨
+
+## Облачные провайдеры — спонсоры { #cloud-providers-sponsors }
+
+Некоторые другие облачные провайдеры ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ тоже. 🙇
+
+Возможно, вы захотите попробовать их сервисы и воспользоваться их руководствами:
+
+* Render
+* Railway
diff --git a/docs/ru/docs/deployment/concepts.md b/docs/ru/docs/deployment/concepts.md
index acfa1f4fe..207d1604d 100644
--- a/docs/ru/docs/deployment/concepts.md
+++ b/docs/ru/docs/deployment/concepts.md
@@ -1,323 +1,321 @@
-# Концепции развёртывания
+# Концепции развёртывания { #deployments-concepts }
-Существует несколько концепций, применяемых для развёртывания приложений **FastAPI**, равно как и для любых других типов веб-приложений, среди которых вы можете выбрать **наиболее подходящий** способ.
+При развёртывании приложения **FastAPI** (и вообще любого веб‑API) есть несколько концепций, о которых стоит думать — с их помощью можно выбрать **наиболее подходящий** способ **развёртывания вашего приложения**.
-Самые важные из них:
+Некоторые из важных концепций:
-* Использование более безопасного протокола HTTPS
-* Настройки запуска приложения
-* Перезагрузка приложения
-* Запуск нескольких экземпляров приложения
-* Управление памятью
-* Использование перечисленных функций перед запуском приложения.
+* Безопасность — HTTPS
+* Запуск при старте
+* Перезапуски
+* Репликация (количество запущенных процессов)
+* Память
+* Предварительные шаги перед запуском
-Рассмотрим ниже влияние каждого из них на процесс **развёртывания**.
+Посмотрим, как они влияют на **развёртывания**.
-Наша конечная цель - **обслуживать клиентов вашего API безопасно** и **бесперебойно**, с максимально эффективным использованием **вычислительных ресурсов** (например, удалённых серверов/виртуальных машин). 🚀
+В конечном итоге цель — **обслуживать клиентов вашего API** безопасно, **избегать перебоев** и максимально эффективно использовать **вычислительные ресурсы** (например, удалённые серверы/виртуальные машины). 🚀
-Здесь я немного расскажу Вам об этих **концепциях** и надеюсь, что у вас сложится **интуитивное понимание**, какой способ выбрать при развертывании вашего API в различных окружениях, возможно, даже **ещё не существующих**.
+Здесь я немного расскажу о этих **концепциях**, чтобы у вас появилась **интуиция**, как развёртывать ваш API в разных окружениях, возможно даже в **будущих**, которых ещё не существует.
-Ознакомившись с этими концепциями, вы сможете **оценить и выбрать** лучший способ развёртывании **Вашего API**.
+Учитывая эти концепции, вы сможете **оценить и спроектировать** лучший способ развёртывания **своих API**.
-В последующих главах я предоставлю Вам **конкретные рецепты** развёртывания приложения FastAPI.
+В следующих главах я дам более **конкретные рецепты** по развёртыванию приложений FastAPI.
-А сейчас давайте остановимся на важных **идеях этих концепций**. Эти идеи можно также применить и к другим типам веб-приложений. 💡
+А пока давайте разберём важные **идеи**. Эти концепции применимы и к другим типам веб‑API. 💡
-## Использование более безопасного протокола HTTPS
+## Безопасность — HTTPS { #security-https }
-В [предыдущей главе об HTTPS](https.md){.internal-link target=_blank} мы рассмотрели, как HTTPS обеспечивает шифрование для вашего API.
+В [предыдущей главе про HTTPS](https.md){.internal-link target=_blank} мы разобрались, как HTTPS обеспечивает шифрование для вашего API.
-Также мы заметили, что обычно для работы с HTTPS вашему приложению нужен **дополнительный** компонент - **прокси-сервер завершения работы TLS**.
+Также мы увидели, что HTTPS обычно обеспечивает компонент, **внешний** по отношению к серверу вашего приложения — **TLS Termination Proxy**.
-И если прокси-сервер не умеет сам **обновлять сертификаты HTTPS**, то нужен ещё один компонент для этого действия.
+И должен быть компонент, отвечающий за **обновление HTTPS‑сертификатов** — это может быть тот же самый компонент или отдельный.
-### Примеры инструментов для работы с HTTPS
+### Примеры инструментов для HTTPS { #example-tools-for-https }
-Вот некоторые инструменты, которые вы можете применять как прокси-серверы:
+Некоторые инструменты, которые можно использовать как TLS Termination Proxy:
* Traefik
- * С автоматическим обновлением сертификатов ✨
+ * Автоматически обновляет сертификаты ✨
* Caddy
- * С автоматическим обновлением сертификатов ✨
+ * Автоматически обновляет сертификаты ✨
* Nginx
- * С дополнительным компонентом типа Certbot для обновления сертификатов
+ * С внешним компонентом (например, Certbot) для обновления сертификатов
* HAProxy
- * С дополнительным компонентом типа Certbot для обновления сертификатов
-* Kubernetes с Ingress Controller похожим на Nginx
- * С дополнительным компонентом типа cert-manager для обновления сертификатов
-* Использование услуг облачного провайдера (читайте ниже 👇)
+ * С внешним компонентом (например, Certbot) для обновления сертификатов
+* Kubernetes с Ingress Controller (например, Nginx)
+ * С внешним компонентом (например, cert-manager) для обновления сертификатов
+* Обрабатывается внутри облачного провайдера как часть его услуг (см. ниже 👇)
-В последнем варианте вы можете воспользоваться услугами **облачного сервиса**, который сделает большую часть работы, включая настройку HTTPS. Это может наложить дополнительные ограничения или потребовать дополнительную плату и т.п. Зато Вам не понадобится самостоятельно заниматься настройками прокси-сервера.
+Другой вариант — использовать **облачный сервис**, который возьмёт на себя больше задач, включая настройку HTTPS. Там могут быть ограничения или дополнительная стоимость и т.п., но в таком случае вам не придётся самим настраивать TLS Termination Proxy.
-В дальнейшем я покажу Вам некоторые конкретные примеры их применения.
+В следующих главах я покажу конкретные примеры.
---
-Следующие концепции рассматривают применение программы, запускающей Ваш API (такой как Uvicorn).
+Далее рассмотрим концепции, связанные с программой, которая запускает ваш реальный API (например, Uvicorn).
-## Программа и процесс
+## Программа и процесс { #program-and-process }
-Мы часто будем встречать слова **процесс** и **программа**, потому следует уяснить отличия между ними.
+Мы часто будем говорить о работающем "**процессе**", поэтому полезно чётко понимать, что это значит и чем отличается от "**программы**".
-### Что такое программа
+### Что такое программа { #what-is-a-program }
-Термином **программа** обычно описывают множество вещей:
+Словом **программа** обычно называют разные вещи:
-* **Код**, который вы написали, в нашем случае **Python-файлы**.
-* **Файл**, который может быть **исполнен** операционной системой, например `python`, `python.exe` или `uvicorn`.
-* Конкретная программа, **запущенная** операционной системой и использующая центральный процессор и память. В таком случае это также называется **процесс**.
+* **Код**, который вы пишете, то есть **Python‑файлы**.
+* **Файл**, который может быть **запущен** операционной системой, например: `python`, `python.exe` или `uvicorn`.
+* Конкретную программу в момент, когда она **работает** в операционной системе, используя CPU и память. Это также называют **процессом**.
-### Что такое процесс
+### Что такое процесс { #what-is-a-process }
-Термин **процесс** имеет более узкое толкование, подразумевая что-то, запущенное операционной системой (как в последнем пункте из вышестоящего абзаца):
+Слово **процесс** обычно используют более конкретно — только для того, что реально выполняется в операционной системе (как в последнем пункте выше):
-* Конкретная программа, **запущенная** операционной системой.
- * Это не имеет отношения к какому-либо файлу или коду, но нечто **определённое**, управляемое и **выполняемое** операционной системой.
-* Любая программа, любой код, **могут делать что-то** только когда они **выполняются**. То есть, когда являются **работающим процессом**.
-* Процесс может быть **прерван** (или "убит") Вами или вашей операционной системой. В результате чего он перестанет исполняться и **не будет продолжать делать что-либо**.
-* Каждое приложение, которое вы запустили на своём компьютере, каждая программа, каждое "окно" запускает какой-то процесс. И обычно на включенном компьютере **одновременно** запущено множество процессов.
-* И **одна программа** может запустить **несколько параллельных процессов**.
+* Конкретная программа в момент, когда она **запущена** в операционной системе.
+ * Речь не о файле и не о коде, а **конкретно** о том, что **исполняется** и управляется операционной системой.
+* Любая программа, любой код **могут что‑то делать** только когда **исполняются**, то есть когда есть **работающий процесс**.
+* Процесс можно **завершить** (или «убить») вами или операционной системой. В этот момент он перестаёт выполняться и **больше ничего делать не может**.
+* У каждого запущенного приложения на вашем компьютере есть свой процесс; у каждой программы, у каждого окна и т.д. Обычно одновременно **работает много процессов**, пока компьютер включён.
+* Могут **одновременно** работать **несколько процессов** одной и той же **программы**.
-Если вы заглянете в "диспетчер задач" или "системный монитор" (или аналогичные инструменты) вашей операционной системы, то увидите множество работающих процессов.
+Если вы посмотрите «диспетчер задач» или «системный монитор» (или аналогичные инструменты) в вашей операционной системе, то увидите множество работающих процессов.
-Вполне вероятно, что вы увидите несколько процессов с одним и тем же названием браузерной программы (Firefox, Chrome, Edge и т. Д.). Обычно браузеры запускают один процесс на вкладку и вдобавок некоторые дополнительные процессы.
+Например, вы, скорее всего, увидите несколько процессов одного и того же браузера (Firefox, Chrome, Edge и т.д.). Обычно браузеры запускают один процесс на вкладку плюс дополнительные процессы.
---
-Теперь, когда нам известна разница между **процессом** и **программой**, давайте продолжим обсуждение развёртывания.
+Теперь, когда мы понимаем разницу между **процессом** и **программой**, продолжим разговор о развёртываниях.
-## Настройки запуска приложения
+## Запуск при старте { #running-on-startup }
-В большинстве случаев когда вы создаёте веб-приложение, то желаете, чтоб оно **работало постоянно** и непрерывно, предоставляя клиентам доступ в любое время. Хотя иногда у вас могут быть причины, чтоб оно запускалось только при определённых условиях.
+В большинстве случаев, создавая веб‑API, вы хотите, чтобы он **работал постоянно**, без перерывов, чтобы клиенты всегда могли к нему обратиться. Разве что у вас есть особые причины запускать его только при определённых условиях, но обычно вы хотите, чтобы он был постоянно запущен и **доступен**.
-### Удалённый сервер
+### На удалённом сервере { #in-a-remote-server }
-Когда вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самое простое, что можно сделать, запустить Uvicorn (или его аналог) вручную, как вы делаете при локальной разработке.
+Когда вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самый простой вариант — вручную использовать `fastapi run` (он использует Uvicorn) или что‑то похожее, как вы делаете при локальной разработке.
-Это рабочий способ и он полезен **во время разработки**.
+Это будет работать и полезно **во время разработки**.
-Но если вы потеряете соединение с сервером, то не сможете отслеживать - работает ли всё ещё **запущенный Вами процесс**.
+Но если соединение с сервером прервётся, **запущенный процесс**, скорее всего, завершится.
-И если сервер перезагрузится (например, после обновления или каких-то действий облачного провайдера), вы скорее всего **этого не заметите**, чтобы снова запустить процесс вручную. Вследствие этого Ваш API останется мёртвым. 😱
+А если сервер перезагрузится (например, после обновлений или миграций у облачного провайдера), вы, вероятно, **даже не заметите этого**. Из‑за этого вы не узнаете, что нужно вручную перезапустить процесс — и ваш API просто будет «мёртв». 😱
-### Автоматический запуск программ
+### Автоматический запуск при старте { #run-automatically-on-startup }
-Вероятно вы захотите, чтоб Ваша серверная программа (такая, как Uvicorn) стартовала автоматически при включении сервера, без **человеческого вмешательства** и всегда могла управлять Вашим API (так как Uvicorn запускает приложение FastAPI).
+Как правило, вы захотите, чтобы серверная программа (например, Uvicorn) запускалась автоматически при старте сервера и без **участия человека**, чтобы всегда был процесс, запущенный с вашим API (например, Uvicorn, запускающий ваше приложение FastAPI).
-### Отдельная программа
+### Отдельная программа { #separate-program }
-Для этого у обычно используют отдельную программу, которая следит за тем, чтобы Ваши приложения запускались при включении сервера. Такой подход гарантирует, что другие компоненты или приложения также будут запущены, например, база данных
+Чтобы этого добиться, обычно используют **отдельную программу**, которая гарантирует запуск вашего приложения при старте. Во многих случаях она также запускает и другие компоненты/приложения, например базу данных.
-### Примеры инструментов, управляющих запуском программ
+### Примеры инструментов для запуска при старте { #example-tools-to-run-at-startup }
-Вот несколько примеров, которые могут справиться с такой задачей:
+Примеры инструментов, которые могут с этим справиться:
* Docker
* Kubernetes
* Docker Compose
-* Docker в режиме Swarm
+* Docker в режиме Swarm (Swarm Mode)
* Systemd
* Supervisor
-* Использование услуг облачного провайдера
+* Обработка внутри облачного провайдера как часть его услуг
* Прочие...
-Я покажу Вам некоторые примеры их использования в следующих главах.
+Более конкретные примеры будут в следующих главах.
-## Перезапуск
+## Перезапуски { #restarts }
-Вы, вероятно, также захотите, чтоб ваше приложение **перезапускалось**, если в нём произошёл сбой.
+Подобно тому как вы обеспечиваете запуск приложения при старте, вы, вероятно, захотите обеспечить его **перезапуск** после сбоев.
-### Мы ошибаемся
+### Мы ошибаемся { #we-make-mistakes }
-Все люди совершают **ошибки**. Программное обеспечение почти *всегда* содержит **баги** спрятавшиеся в разных местах. 🐛
+Мы, люди, постоянно совершаем **ошибки**. В программном обеспечении почти всегда есть **баги**, скрытые в разных местах. 🐛
-И мы, будучи разработчиками, продолжаем улучшать код, когда обнаруживаем в нём баги или добавляем новый функционал (возможно, добавляя при этом баги 😅).
+И мы, как разработчики, продолжаем улучшать код — находим баги и добавляем новые возможности (иногда добавляя новые баги 😅).
-### Небольшие ошибки обрабатываются автоматически
+### Небольшие ошибки обрабатываются автоматически { #small-errors-automatically-handled }
-Когда вы создаёте свои API на основе FastAPI и допускаете в коде ошибку, то FastAPI обычно остановит её распространение внутри одного запроса, при обработке которого она возникла. 🛡
+Создавая веб‑API с FastAPI, если в нашем коде возникает ошибка, FastAPI обычно «локализует» её в пределах одного запроса, который эту ошибку вызвал. 🛡
-Клиент получит ошибку **500 Internal Server Error** в ответ на свой запрос, но приложение не сломается и будет продолжать работать с последующими запросами.
+Клиент получит **500 Internal Server Error** для этого запроса, но приложение продолжит работать для последующих запросов, а не «упадёт» целиком.
-### Большие ошибки - Падение приложений
+### Большие ошибки — падения { #bigger-errors-crashes }
-Тем не менее, может случиться так, что ошибка вызовет **сбой всего приложения** или даже сбой в Uvicorn, а то и в самом Python. 💥
+Тем не менее возможны случаи, когда код **роняет всё приложение**, приводя к сбою Uvicorn и Python. 💥
-Но мы всё ещё хотим, чтобы приложение **продолжало работать** несмотря на эту единственную ошибку, обрабатывая, как минимум, запросы к *операциям пути* не имеющим ошибок.
+И вы, скорее всего, не захотите, чтобы приложение оставалось «мёртвым» из‑за ошибки в одном месте — вы захотите, чтобы оно **продолжало работать** хотя бы для *операций пути*, которые не сломаны.
-### Перезапуск после падения
+### Перезапуск после падения { #restart-after-crash }
-Для случаев, когда ошибки приводят к сбою в запущенном **процессе**, Вам понадобится добавить компонент, который **перезапустит** процесс хотя бы пару раз...
+В случаях действительно серьёзных ошибок, которые роняют работающий **процесс**, вам понадобится внешний компонент, отвечающий за **перезапуск** процесса, как минимум пару раз...
-/// tip | Заметка
+/// tip | Совет
-... Если приложение падает сразу же после запуска, вероятно бесполезно его бесконечно перезапускать. Но полагаю, вы заметите такое поведение во время разработки или, по крайней мере, сразу после развёртывания.
+...Хотя если приложение **падает сразу же**, вероятно, нет смысла перезапускать его бесконечно. Но такие случаи вы, скорее всего, заметите во время разработки или как минимум сразу после развёртывания.
-Так что давайте сосредоточимся на конкретных случаях, когда приложение может полностью выйти из строя, но всё ещё есть смысл его запустить заново.
+Давайте сосредоточимся на основных сценариях, когда в каких‑то конкретных ситуациях **в будущем** приложение может падать целиком, и при этом имеет смысл его перезапускать.
///
-Возможно вы захотите, чтоб был некий **внешний компонент**, ответственный за перезапуск вашего приложения даже если уже не работает Uvicorn или Python. То есть ничего из того, что написано в вашем коде внутри приложения, не может быть выполнено в принципе.
+Скорее всего, вы захотите, чтобы перезапуском вашего приложения занимался **внешний компонент**, потому что к тому моменту Uvicorn и Python уже упали, и внутри того же кода вашего приложения сделать уже ничего нельзя.
-### Примеры инструментов для автоматического перезапуска
+### Примеры инструментов для автоматического перезапуска { #example-tools-to-restart-automatically }
-В большинстве случаев инструменты **запускающие программы при старте сервера** умеют **перезапускать** эти программы.
+В большинстве случаев тот же инструмент, который **запускает программу при старте**, умеет обрабатывать и автоматические **перезапуски**.
-В качестве примера можно взять те же:
+Например, это может быть:
* Docker
* Kubernetes
* Docker Compose
-* Docker в режиме Swarm
+* Docker в режиме Swarm (Swarm Mode)
* Systemd
* Supervisor
-* Использование услуг облачного провайдера
+* Обработка внутри облачного провайдера как часть его услуг
* Прочие...
-## Запуск нескольких экземпляров приложения (Репликация) - Процессы и память
+## Репликация — процессы и память { #replication-processes-and-memory }
-Приложение FastAPI, управляемое серверной программой (такой как Uvicorn), запускается как **один процесс** и может обслуживать множество клиентов одновременно.
+В приложении FastAPI, используя серверную программу (например, команду `fastapi`, которая запускает Uvicorn), запуск в **одном процессе** уже позволяет обслуживать нескольких клиентов одновременно.
-Но часто Вам может понадобиться несколько одновременно работающих одинаковых процессов.
+Но во многих случаях вы захотите одновременно запустить несколько процессов‑воркеров.
-### Множество процессов - Воркеры (Workers)
+### Несколько процессов — Воркеры { #multiple-processes-workers }
-Если количество Ваших клиентов больше, чем может обслужить один процесс (допустим, что виртуальная машина не слишком мощная), но при этом Вам доступно **несколько ядер процессора**, то вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределить запросы между этими процессами.
+Если клиентов больше, чем способен обслужить один процесс (например, если виртуальная машина не слишком мощная), и на сервере есть **несколько ядер CPU**, вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределять запросы между ними.
-**Несколько запущенных процессов** одной и той же API-программы часто называют **воркерами**.
+Когда вы запускаете **несколько процессов** одной и той же программы API, их обычно называют **воркерами**.
-### Процессы и порты́
+### Процессы‑воркеры и порты { #worker-processes-and-ports }
-Помните ли Вы, как на странице [Об HTTPS](https.md){.internal-link target=_blank} мы обсуждали, что на сервере только один процесс может слушать одну комбинацию IP-адреса и порта?
+Помните из раздела [Об HTTPS](https.md){.internal-link target=_blank}, что на сервере только один процесс может слушать конкретную комбинацию порта и IP‑адреса?
-С тех пор ничего не изменилось.
+Это по‑прежнему так.
-Соответственно, чтобы иметь возможность работать с **несколькими процессами** одновременно, должен быть **один процесс, прослушивающий порт** и затем каким-либо образом передающий данные каждому рабочему процессу.
+Поэтому, чтобы одновременно работало **несколько процессов**, должен быть **один процесс, слушающий порт**, который затем каким‑то образом передаёт коммуникацию каждому воркер‑процессу.
-### У каждого процесса своя память
+### Память на процесс { #memory-per-process }
-Работающая программа загружает в память данные, необходимые для её работы, например, переменные содержащие модели машинного обучения или большие файлы. Каждая переменная **потребляет некоторое количество оперативной памяти (RAM)** сервера.
+Когда программа загружает что‑то в память (например, модель машинного обучения в переменную или содержимое большого файла в переменную), всё это **потребляет часть памяти (RAM)** сервера.
-Обычно процессы **не делятся памятью друг с другом**. Сие означает, что каждый работающий процесс имеет свои данные, переменные и свой кусок памяти. И если для выполнения вашего кода процессу нужно много памяти, то **каждый такой же процесс** запущенный дополнительно, потребует такого же количества памяти.
+И разные процессы обычно **не делят память**. Это значит, что у каждого процесса свои переменные и своя память. Если ваш код потребляет много памяти, то **каждый процесс** будет потреблять сопоставимый объём памяти.
-### Память сервера
+### Память сервера { #server-memory }
-Допустим, что Ваш код загружает модель машинного обучения **размером 1 ГБ**. Когда вы запустите своё API как один процесс, он займёт в оперативной памяти не менее 1 ГБ. А если вы запустите **4 таких же процесса** (4 воркера), то каждый из них займёт 1 ГБ оперативной памяти. В результате вашему API потребуется **4 ГБ оперативной памяти (RAM)**.
+Например, если ваш код загружает модель Машинного обучения размером **1 ГБ**, то при запуске одного процесса с вашим API он будет использовать как минимум 1 ГБ RAM. А если вы запустите **4 процесса** (4 воркера), каждый процесс будет использовать 1 ГБ RAM. Всего ваш API будет потреблять **4 ГБ RAM**.
-И если Ваш удалённый сервер или виртуальная машина располагает только 3 ГБ памяти, то попытка загрузить в неё 4 ГБ данных вызовет проблемы. 🚨
+И если у вашего удалённого сервера или виртуальной машины только 3 ГБ RAM, попытка загрузить более 4 ГБ вызовет проблемы. 🚨
-### Множество процессов - Пример
+### Несколько процессов — пример { #multiple-processes-an-example }
-В этом примере **менеджер процессов** запустит и будет управлять двумя **воркерами**.
+В этом примере есть **процесс‑менеджер**, который запускает и контролирует два **процесса‑воркера**.
-Менеджер процессов будет слушать определённый **сокет** (IP:порт) и передавать данные работающим процессам.
+Процесс‑менеджер, вероятно, будет тем, кто слушает **порт** на IP. И он будет передавать всю коммуникацию воркер‑процессам.
-Каждый из этих процессов будет запускать ваше приложение для обработки полученного **запроса** и возвращения вычисленного **ответа** и они будут использовать оперативную память.
+Эти воркеры будут запускать ваше приложение, выполнять основные вычисления для получения **запроса** и возврата **ответа**, и загружать всё, что вы кладёте в переменные, в RAM.
+
+Но вы можете отключить её, установив `syntaxHighlight` в `False`:
+
+{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *}
+
+…и после этого Swagger UI больше не будет показывать подсветку синтаксиса:
+
+
+
+## Изменить тему { #change-the-theme }
+
+Аналогично вы можете задать тему подсветки синтаксиса с ключом "syntaxHighlight.theme" (обратите внимание, что посередине стоит точка):
+
+{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *}
+
+Эта настройка изменит цветовую тему подсветки синтаксиса:
+
+
+
+## Изменить параметры Swagger UI по умолчанию { #change-default-swagger-ui-parameters }
+
+FastAPI включает некоторые параметры конфигурации по умолчанию, подходящие для большинства случаев.
+
+Это включает следующие настройки по умолчанию:
+
+{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *}
+
+Вы можете переопределить любую из них, указав другое значение в аргументе `swagger_ui_parameters`.
+
+Например, чтобы отключить `deepLinking`, можно передать такие настройки в `swagger_ui_parameters`:
+
+{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *}
+
+## Другие параметры Swagger UI { #other-swagger-ui-parameters }
+
+Чтобы увидеть все остальные возможные настройки, прочитайте официальную документацию по параметрам Swagger UI.
+
+## Настройки только для JavaScript { #javascript-only-settings }
+
+Swagger UI также допускает другие настройки, которые являются **чисто JavaScript-объектами** (например, JavaScript-функциями).
+
+FastAPI также включает следующие настройки `presets` (только для JavaScript):
+
+```JavaScript
+presets: [
+ SwaggerUIBundle.presets.apis,
+ SwaggerUIBundle.SwaggerUIStandalonePreset
+]
+```
+
+Это объекты **JavaScript**, а не строки, поэтому напрямую передать их из Python-кода нельзя.
+
+Если вам нужны такие настройки только для JavaScript, используйте один из методов выше. Переопределите *операцию пути* Swagger UI и вручную напишите любой необходимый JavaScript.
diff --git a/docs/ru/docs/how-to/custom-docs-ui-assets.md b/docs/ru/docs/how-to/custom-docs-ui-assets.md
new file mode 100644
index 000000000..f524911e6
--- /dev/null
+++ b/docs/ru/docs/how-to/custom-docs-ui-assets.md
@@ -0,0 +1,185 @@
+# Свои статические ресурсы UI документации (самостоятельный хостинг) { #custom-docs-ui-static-assets-self-hosting }
+
+Документация API использует **Swagger UI** и **ReDoc**, и для каждого из них нужны некоторые файлы JavaScript и CSS.
+
+По умолчанию эти файлы отдаются с CDN.
+
+Но это можно настроить: вы можете указать конкретный CDN или отдавать файлы самостоятельно.
+
+## Пользовательский CDN для JavaScript и CSS { #custom-cdn-for-javascript-and-css }
+
+Допустим, вы хотите использовать другой CDN, например `https://unpkg.com/`.
+
+Это может быть полезно, если, например, вы живёте в стране, где некоторые URL ограничены.
+
+### Отключить автоматическую документацию { #disable-the-automatic-docs }
+
+Первый шаг — отключить автоматическую документацию, так как по умолчанию она использует стандартный CDN.
+
+Чтобы отключить её, установите их URL в значение `None` при создании вашего приложения `FastAPI`:
+
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *}
+
+### Подключить пользовательскую документацию { #include-the-custom-docs }
+
+Теперь вы можете создать *операции пути* для пользовательской документации.
+
+Вы можете переиспользовать внутренние функции FastAPI для создания HTML-страниц документации и передать им необходимые аргументы:
+
+* `openapi_url`: URL, по которому HTML-страница документации сможет получить схему OpenAPI для вашего API. Здесь можно использовать атрибут `app.openapi_url`.
+* `title`: заголовок вашего API.
+* `oauth2_redirect_url`: здесь можно использовать `app.swagger_ui_oauth2_redirect_url`, чтобы оставить значение по умолчанию.
+* `swagger_js_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **JavaScript**. Это URL вашего пользовательского CDN.
+* `swagger_css_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **CSS**. Это URL вашего пользовательского CDN.
+
+Аналогично и для ReDoc...
+
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *}
+
+/// tip | Совет
+
+*Операция пути* для `swagger_ui_redirect` — это вспомогательный эндпоинт на случай, когда вы используете OAuth2.
+
+Если вы интегрируете свой API с провайдером OAuth2, вы сможете аутентифицироваться и вернуться к документации API с полученными учётными данными, а затем взаимодействовать с ним, используя реальную аутентификацию OAuth2.
+
+Swagger UI сделает это за вас «за кулисами», но для этого ему нужен этот вспомогательный «redirect» эндпоинт.
+
+///
+
+### Создайте *операцию пути*, чтобы проверить { #create-a-path-operation-to-test-it }
+
+Чтобы убедиться, что всё работает, создайте *операцию пути*:
+
+{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *}
+
+### Тестирование { #test-it }
+
+Теперь вы должны иметь возможность открыть свою документацию по адресу http://127.0.0.1:8000/docs и перезагрузить страницу — «ассеты» (статические файлы) будут загружаться с нового CDN.
+
+## Самостоятельный хостинг JavaScript и CSS для документации { #self-hosting-javascript-and-css-for-docs }
+
+Самостоятельный хостинг JavaScript и CSS может быть полезен, если, например, вам нужно, чтобы приложение продолжало работать в офлайне, без доступа к открытому Интернету, или в локальной сети.
+
+Здесь вы увидите, как отдавать эти файлы самостоятельно, в том же приложении FastAPI, и настроить документацию на их использование.
+
+### Структура файлов проекта { #project-file-structure }
+
+Допустим, структура файлов вашего проекта выглядит так:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+```
+
+Теперь создайте директорию для хранения этих статических файлов.
+
+Новая структура файлов может выглядеть так:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+└── static/
+```
+
+### Скачайте файлы { #download-the-files }
+
+Скачайте статические файлы, необходимые для документации, и поместите их в директорию `static/`.
+
+Скорее всего, вы можете кликнуть правой кнопкой на каждой ссылке и выбрать что-то вроде «Сохранить ссылку как...».
+
+**Swagger UI** использует файлы:
+
+* `swagger-ui-bundle.js`
+* `swagger-ui.css`
+
+А **ReDoc** использует файл:
+
+* `redoc.standalone.js`
+
+После этого структура файлов может выглядеть так:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+└── static
+ ├── redoc.standalone.js
+ ├── swagger-ui-bundle.js
+ └── swagger-ui.css
+```
+
+### Предоставьте доступ к статическим файлам { #serve-the-static-files }
+
+* Импортируйте `StaticFiles`.
+* Смонтируйте экземпляр `StaticFiles()` в определённый путь.
+
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *}
+
+### Протестируйте статические файлы { #test-the-static-files }
+
+Запустите своё приложение и откройте http://127.0.0.1:8000/static/redoc.standalone.js.
+
+Вы должны увидеть очень длинный JavaScript-файл для **ReDoc**.
+
+Он может начинаться примерно так:
+
+```JavaScript
+/*! For license information please see redoc.standalone.js.LICENSE.txt */
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):
+...
+```
+
+Это подтверждает, что ваше приложение умеет отдавать статические файлы и что вы поместили файлы документации в нужное место.
+
+Теперь можно настроить приложение так, чтобы документация использовала эти статические файлы.
+
+### Отключить автоматическую документацию для статических файлов { #disable-the-automatic-docs-for-static-files }
+
+Так же, как и при использовании пользовательского CDN, первым шагом будет отключение автоматической документации, так как по умолчанию она использует CDN.
+
+Чтобы отключить её, установите их URL в значение `None` при создании вашего приложения `FastAPI`:
+
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *}
+
+### Подключить пользовательскую документацию со статическими файлами { #include-the-custom-docs-for-static-files }
+
+Аналогично пользовательскому CDN, теперь вы можете создать *операции пути* для собственной документации.
+
+Снова можно переиспользовать внутренние функции FastAPI для создания HTML-страниц документации и передать им необходимые аргументы:
+
+* `openapi_url`: URL, по которому HTML-страница документации сможет получить схему OpenAPI для вашего API. Здесь можно использовать атрибут `app.openapi_url`.
+* `title`: заголовок вашего API.
+* `oauth2_redirect_url`: здесь можно использовать `app.swagger_ui_oauth2_redirect_url`, чтобы оставить значение по умолчанию.
+* `swagger_js_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **JavaScript**. **Это тот файл, который теперь отдаёт ваше собственное приложение**.
+* `swagger_css_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **CSS**. **Это тот файл, который теперь отдаёт ваше собственное приложение**.
+
+Аналогично и для ReDoc...
+
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *}
+
+/// tip | Совет
+
+*Операция пути* для `swagger_ui_redirect` — это вспомогательный эндпоинт на случай, когда вы используете OAuth2.
+
+Если вы интегрируете свой API с провайдером OAuth2, вы сможете аутентифицироваться и вернуться к документации API с полученными учётными данными, а затем взаимодействовать с ним, используя реальную аутентификацию OAuth2.
+
+Swagger UI сделает это за вас «за кулисами», но для этого ему нужен этот вспомогательный «redirect» эндпоинт.
+
+///
+
+### Создайте *операцию пути* для теста статических файлов { #create-a-path-operation-to-test-static-files }
+
+Чтобы убедиться, что всё работает, создайте *операцию пути*:
+
+{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *}
+
+### Тестирование UI со статическими файлами { #test-static-files-ui }
+
+Теперь вы можете отключить Wi‑Fi, открыть свою документацию по адресу http://127.0.0.1:8000/docs и перезагрузить страницу.
+
+Даже без Интернета вы сможете видеть документацию к своему API и взаимодействовать с ним.
diff --git a/docs/ru/docs/how-to/custom-request-and-route.md b/docs/ru/docs/how-to/custom-request-and-route.md
new file mode 100644
index 000000000..feef9670a
--- /dev/null
+++ b/docs/ru/docs/how-to/custom-request-and-route.md
@@ -0,0 +1,109 @@
+# Пользовательские классы Request и APIRoute { #custom-request-and-apiroute-class }
+
+В некоторых случаях может понадобиться переопределить логику, используемую классами `Request` и `APIRoute`.
+
+В частности, это может быть хорошей альтернативой логике в middleware.
+
+Например, если вы хотите прочитать или изменить тело запроса до того, как оно будет обработано вашим приложением.
+
+/// danger | Опасность
+
+Это «продвинутая» возможность.
+
+Если вы только начинаете работать с **FastAPI**, возможно, стоит пропустить этот раздел.
+
+///
+
+## Сценарии использования { #use-cases }
+
+Некоторые сценарии:
+
+* Преобразование тел запросов, не в формате JSON, в JSON (например, `msgpack`).
+* Распаковка тел запросов, сжатых с помощью gzip.
+* Автоматическое логирование всех тел запросов.
+
+## Обработка пользовательского кодирования тела запроса { #handling-custom-request-body-encodings }
+
+Посмотрим как использовать пользовательский подкласс `Request` для распаковки gzip-запросов.
+
+И подкласс `APIRoute`, чтобы использовать этот пользовательский класс запроса.
+
+### Создать пользовательский класс `GzipRequest` { #create-a-custom-gziprequest-class }
+
+/// tip | Совет
+
+Это учебный пример, демонстрирующий принцип работы. Если вам нужна поддержка Gzip, вы можете использовать готовый [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}.
+
+///
+
+Сначала создадим класс `GzipRequest`, который переопределит метод `Request.body()` и распакует тело запроса при наличии соответствующего HTTP-заголовка.
+
+Если в заголовке нет `gzip`, он не будет пытаться распаковывать тело.
+
+Таким образом, один и тот же класс маршрута сможет обрабатывать как gzip-сжатые, так и несжатые запросы.
+
+{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *}
+
+### Создать пользовательский класс `GzipRoute` { #create-a-custom-gziproute-class }
+
+Далее создадим пользовательский подкласс `fastapi.routing.APIRoute`, который будет использовать `GzipRequest`.
+
+На этот раз он переопределит метод `APIRoute.get_route_handler()`.
+
+Этот метод возвращает функцию. Именно эта функция получает HTTP-запрос и возвращает HTTP-ответ.
+
+Здесь мы используем её, чтобы создать `GzipRequest` из исходного HTTP-запроса.
+
+{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *}
+
+/// note | Технические детали
+
+У `Request` есть атрибут `request.scope` — это просто Python-`dict`, содержащий метаданные, связанные с HTTP-запросом.
+
+У `Request` также есть `request.receive` — функция для «получения» тела запроса.
+
+И `dict` `scope`, и функция `receive` являются частью спецификации ASGI.
+
+Именно этих двух компонентов — `scope` и `receive` — достаточно, чтобы создать новый экземпляр `Request`.
+
+Чтобы узнать больше о `Request`, см. документацию Starlette о запросах.
+
+///
+
+Единственное, что делает по-другому функция, возвращённая `GzipRequest.get_route_handler`, — преобразует `Request` в `GzipRequest`.
+
+Благодаря этому наш `GzipRequest` позаботится о распаковке данных (при необходимости) до передачи их в наши *операции пути*.
+
+Дальше вся логика обработки остаётся прежней.
+
+Но благодаря изменениям в `GzipRequest.body` тело запроса будет автоматически распаковано при необходимости, когда оно будет загружено **FastAPI**.
+
+## Доступ к телу запроса в обработчике исключений { #accessing-the-request-body-in-an-exception-handler }
+
+/// tip | Совет
+
+Для решения этой задачи, вероятно, намного проще использовать `body` в пользовательском обработчике `RequestValidationError` ([Обработка ошибок](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}).
+
+Но этот пример всё равно актуален и показывает, как взаимодействовать с внутренними компонентами.
+
+///
+
+Тем же подходом можно воспользоваться, чтобы получить доступ к телу запроса в обработчике исключений.
+
+Нужно лишь обработать запрос внутри блока `try`/`except`:
+
+{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *}
+
+Если произойдёт исключение, экземпляр `Request` всё ещё будет в области видимости, поэтому мы сможем прочитать тело запроса и использовать его при обработке ошибки:
+
+{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *}
+
+## Пользовательский класс `APIRoute` в роутере { #custom-apiroute-class-in-a-router }
+
+Вы также можете задать параметр `route_class` у `APIRouter`:
+
+{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *}
+
+В этом примере *операции пути*, объявленные в `router`, будут использовать пользовательский класс `TimedRoute` и получат дополнительный HTTP-заголовок `X-Response-Time` в ответе с временем, затраченным на формирование ответа:
+
+{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *}
diff --git a/docs/ru/docs/how-to/extending-openapi.md b/docs/ru/docs/how-to/extending-openapi.md
new file mode 100644
index 000000000..1d69cbdb3
--- /dev/null
+++ b/docs/ru/docs/how-to/extending-openapi.md
@@ -0,0 +1,80 @@
+# Расширение OpenAPI { #extending-openapi }
+
+Иногда может понадобиться изменить сгенерированную схему OpenAPI.
+
+В этом разделе показано, как это сделать.
+
+## Обычный процесс { #the-normal-process }
+
+Обычный (по умолчанию) процесс выглядит так.
+
+Приложение `FastAPI` (экземпляр) имеет метод `.openapi()`, который должен возвращать схему OpenAPI.
+
+В процессе создания объекта приложения регистрируется *операция пути* (обработчик пути) для `/openapi.json` (или для того, что указано в вашем `openapi_url`).
+
+Она просто возвращает JSON-ответ с результатом вызова метода приложения `.openapi()`.
+
+По умолчанию метод `.openapi()` проверяет свойство `.openapi_schema`: если в нём уже есть данные, возвращает их.
+
+Если нет — генерирует схему с помощью вспомогательной функции `fastapi.openapi.utils.get_openapi`.
+
+Функция `get_openapi()` принимает параметры:
+
+* `title`: Заголовок OpenAPI, отображается в документации.
+* `version`: Версия вашего API, например `2.5.0`.
+* `openapi_version`: Версия используемой спецификации OpenAPI. По умолчанию — последняя: `3.1.0`.
+* `summary`: Краткое описание API.
+* `description`: Описание вашего API; может включать Markdown и будет отображается в документации.
+* `routes`: Список маршрутов — это каждая зарегистрированная *операция пути*. Берутся из `app.routes`.
+
+/// info | Информация
+
+Параметр `summary` доступен в OpenAPI 3.1.0 и выше, поддерживается FastAPI версии 0.99.0 и выше.
+
+///
+
+## Переопределение значений по умолчанию { #overriding-the-defaults }
+
+Используя информацию выше, вы можете той же вспомогательной функцией сгенерировать схему OpenAPI и переопределить любые нужные части.
+
+Например, добавим расширение OpenAPI ReDoc для включения собственного логотипа.
+
+### Обычный **FastAPI** { #normal-fastapi }
+
+Сначала напишите приложение **FastAPI** как обычно:
+
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[1,4,7:9] *}
+
+### Сгенерируйте схему OpenAPI { #generate-the-openapi-schema }
+
+Затем используйте ту же вспомогательную функцию для генерации схемы OpenAPI внутри функции `custom_openapi()`:
+
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[2,15:21] *}
+
+### Измените схему OpenAPI { #modify-the-openapi-schema }
+
+Теперь можно добавить расширение ReDoc, добавив кастомный `x-logo` в «объект» `info` в схеме OpenAPI:
+
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *}
+
+### Кэшируйте схему OpenAPI { #cache-the-openapi-schema }
+
+Вы можете использовать свойство `.openapi_schema` как «кэш» для хранения сгенерированной схемы.
+
+Так приложению не придётся генерировать схему каждый раз, когда пользователь открывает документацию API.
+
+Она будет создана один раз, а затем тот же кэшированный вариант будет использоваться для последующих запросов.
+
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *}
+
+### Переопределите метод { #override-the-method }
+
+Теперь вы можете заменить метод `.openapi()` на вашу новую функцию.
+
+{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *}
+
+### Проверьте { #check-it }
+
+Перейдите на http://127.0.0.1:8000/redoc — вы увидите, что используется ваш кастомный логотип (в этом примере — логотип **FastAPI**):
+
+
diff --git a/docs/ru/docs/how-to/general.md b/docs/ru/docs/how-to/general.md
new file mode 100644
index 000000000..029ea1d27
--- /dev/null
+++ b/docs/ru/docs/how-to/general.md
@@ -0,0 +1,39 @@
+# Общее — Как сделать — Рецепты { #general-how-to-recipes }
+
+Здесь несколько указателей на другие места в документации для общих или частых вопросов.
+
+## Фильтрация данных — Безопасность { #filter-data-security }
+
+Чтобы убедиться, что вы не возвращаете больше данных, чем следует, прочитайте документацию: [Руководство — Модель ответа — Возвращаемый тип](../tutorial/response-model.md){.internal-link target=_blank}.
+
+## Теги в документации — OpenAPI { #documentation-tags-openapi }
+
+Чтобы добавить теги к вашим *операциям пути* и группировать их в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Теги](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}.
+
+## Краткое описание и описание в документации — OpenAPI { #documentation-summary-and-description-openapi }
+
+Чтобы добавить краткое описание и описание к вашим *операциям пути* и отобразить их в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Краткое описание и описание](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}.
+
+## Описание ответа в документации — OpenAPI { #documentation-response-description-openapi }
+
+Чтобы задать описание ответа, отображаемое в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Описание ответа](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}.
+
+## Документация — пометить операцию пути устаревшей — OpenAPI { #documentation-deprecate-a-path-operation-openapi }
+
+Чтобы пометить *операцию пути* как устаревшую и показать это в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Пометить операцию пути устаревшей](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}.
+
+## Преобразование любых данных к формату, совместимому с JSON { #convert-any-data-to-json-compatible }
+
+Чтобы преобразовать любые данные к формату, совместимому с JSON, прочитайте документацию: [Руководство — JSON-совместимый кодировщик](../tutorial/encoder.md){.internal-link target=_blank}.
+
+## Метаданные OpenAPI — Документация { #openapi-metadata-docs }
+
+Чтобы добавить метаданные в вашу схему OpenAPI, включая лицензию, версию, контакты и т.д., прочитайте документацию: [Руководство — Метаданные и URL документации](../tutorial/metadata.md){.internal-link target=_blank}.
+
+## Пользовательский URL OpenAPI { #openapi-custom-url }
+
+Чтобы настроить URL OpenAPI (или удалить его), прочитайте документацию: [Руководство — Метаданные и URL документации](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}.
+
+## URL документации OpenAPI { #openapi-docs-urls }
+
+Чтобы изменить URL, используемые для автоматически сгенерированных пользовательских интерфейсов документации, прочитайте документацию: [Руководство — Метаданные и URL документации](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}.
diff --git a/docs/ru/docs/how-to/graphql.md b/docs/ru/docs/how-to/graphql.md
new file mode 100644
index 000000000..50c321e7d
--- /dev/null
+++ b/docs/ru/docs/how-to/graphql.md
@@ -0,0 +1,60 @@
+# GraphQL { #graphql }
+
+Так как **FastAPI** основан на стандарте **ASGI**, очень легко интегрировать любую библиотеку **GraphQL**, также совместимую с ASGI.
+
+Вы можете комбинировать обычные *операции пути* FastAPI с GraphQL в одном приложении.
+
+/// tip | Совет
+
+**GraphQL** решает некоторые очень специфические задачи.
+
+У него есть как **преимущества**, так и **недостатки** по сравнению с обычными **веб-API**.
+
+Убедитесь, что **выгоды** для вашего случая использования перевешивают **недостатки**. 🤓
+
+///
+
+## Библиотеки GraphQL { #graphql-libraries }
+
+Ниже приведены некоторые библиотеки **GraphQL** с поддержкой **ASGI**. Их можно использовать с **FastAPI**:
+
+* Strawberry 🍓
+ * С документацией для FastAPI
+* Ariadne
+ * С документацией для FastAPI
+* Tartiflette
+ * С Tartiflette ASGI для интеграции с ASGI
+* Graphene
+ * С starlette-graphene3
+
+## GraphQL со Strawberry { #graphql-with-strawberry }
+
+Если вам нужно или хочется работать с **GraphQL**, **Strawberry** — **рекомендуемая** библиотека, так как её дизайн ближе всего к дизайну **FastAPI**, всё основано на **аннотациях типов**.
+
+В зависимости от вашего сценария использования вы можете предпочесть другую библиотеку, но если бы вы спросили меня, я, скорее всего, предложил бы попробовать **Strawberry**.
+
+Вот небольшой пример того, как можно интегрировать Strawberry с FastAPI:
+
+{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
+
+Подробнее о Strawberry можно узнать в документации Strawberry.
+
+А также в документации по интеграции Strawberry с FastAPI.
+
+## Устаревший `GraphQLApp` из Starlette { #older-graphqlapp-from-starlette }
+
+В предыдущих версиях Starlette был класс `GraphQLApp` для интеграции с Graphene.
+
+Он был объявлен устаревшим в Starlette, но если у вас есть код, который его использовал, вы можете легко **мигрировать** на starlette-graphene3, который решает ту же задачу и имеет **почти идентичный интерфейс**.
+
+/// tip | Совет
+
+Если вам нужен GraphQL, я всё же рекомендую посмотреть Strawberry, так как он основан на аннотациях типов, а не на пользовательских классах и типах.
+
+///
+
+## Подробнее { #learn-more }
+
+Подробнее о **GraphQL** вы можете узнать в официальной документации GraphQL.
+
+Также можно почитать больше о каждой из указанных выше библиотек по приведённым ссылкам.
diff --git a/docs/ru/docs/how-to/index.md b/docs/ru/docs/how-to/index.md
new file mode 100644
index 000000000..228c125dd
--- /dev/null
+++ b/docs/ru/docs/how-to/index.md
@@ -0,0 +1,13 @@
+# Как сделать — Рецепты { #how-to-recipes }
+
+Здесь вы найдете разные рецепты и руководства «как сделать» по различным темам.
+
+Большинство из этих идей более-менее независимы, и в большинстве случаев вам стоит изучать их только если они напрямую относятся к вашему проекту.
+
+Если что-то кажется интересным и полезным для вашего проекта, смело изучайте; в противном случае, вероятно, можно просто пропустить.
+
+/// tip | Совет
+
+Если вы хотите изучить FastAPI структурированно (рекомендуется), вместо этого читайте [Учебник — Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} по главам.
+
+///
diff --git a/docs/ru/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/ru/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
new file mode 100644
index 000000000..2b47c08f6
--- /dev/null
+++ b/docs/ru/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
@@ -0,0 +1,135 @@
+# Миграция с Pydantic v1 на Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 }
+
+Если у вас старое приложение FastAPI, возможно, вы используете Pydantic версии 1.
+
+FastAPI версии 0.100.0 поддерживал либо Pydantic v1, либо v2. Он использовал ту версию, которая была установлена.
+
+FastAPI версии 0.119.0 добавил частичную поддержку Pydantic v1 изнутри Pydantic v2 (как `pydantic.v1`), чтобы упростить миграцию на v2.
+
+FastAPI 0.126.0 убрал поддержку Pydantic v1, при этом ещё некоторое время продолжал поддерживать `pydantic.v1`.
+
+/// warning | Предупреждение
+
+Команда Pydantic прекратила поддержку Pydantic v1 для последних версий Python, начиная с **Python 3.14**.
+
+Это включает `pydantic.v1`, который больше не поддерживается в Python 3.14 и выше.
+
+Если вы хотите использовать последние возможности Python, вам нужно убедиться, что вы используете Pydantic v2.
+
+///
+
+Если у вас старое приложение FastAPI с Pydantic v1, здесь я покажу, как мигрировать на Pydantic v2, и **возможности FastAPI 0.119.0**, которые помогут выполнить постепенную миграцию.
+
+## Официальное руководство { #official-guide }
+
+У Pydantic есть официальное руководство по миграции с v1 на v2.
+
+Там также описано, что изменилось, как валидации стали более корректными и строгими, возможные нюансы и т.д.
+
+Прочитайте его, чтобы лучше понять, что изменилось.
+
+## Тесты { #tests }
+
+Убедитесь, что у вас есть [тесты](../tutorial/testing.md){.internal-link target=_blank} для вашего приложения и что вы запускаете их в системе непрерывной интеграции (CI).
+
+Так вы сможете выполнить обновление и убедиться, что всё работает как ожидается.
+
+## `bump-pydantic` { #bump-pydantic }
+
+Во многих случаях, когда вы используете обычные Pydantic‑модели без пользовательских настроек, вы сможете автоматизировать большую часть процесса миграции с Pydantic v1 на Pydantic v2.
+
+Вы можете использовать `bump-pydantic` от той же команды Pydantic.
+
+Этот инструмент поможет автоматически изменить большую часть кода, который нужно изменить.
+
+После этого вы можете запустить тесты и проверить, что всё работает. Если да — на этом всё. 😎
+
+## Pydantic v1 в v2 { #pydantic-v1-in-v2 }
+
+Pydantic v2 включает всё из Pydantic v1 как подмодуль `pydantic.v1`. Но это больше не поддерживается в версиях Python выше 3.13.
+
+Это означает, что вы можете установить последнюю версию Pydantic v2 и импортировать и использовать старые компоненты Pydantic v1 из этого подмодуля так, как если бы у вас был установлен старый Pydantic v1.
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *}
+
+### Поддержка FastAPI для Pydantic v1 внутри v2 { #fastapi-support-for-pydantic-v1-in-v2 }
+
+Начиная с FastAPI 0.119.0, есть также частичная поддержка Pydantic v1 изнутри Pydantic v2, чтобы упростить миграцию на v2.
+
+Таким образом, вы можете обновить Pydantic до последней версии 2 и сменить импорты на подмодуль `pydantic.v1` — во многих случаях всё просто заработает.
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *}
+
+/// warning | Предупреждение
+
+Имейте в виду, что так как команда Pydantic больше не поддерживает Pydantic v1 в последних версиях Python, начиная с Python 3.14, использование `pydantic.v1` также не поддерживается в Python 3.14 и выше.
+
+///
+
+### Pydantic v1 и v2 в одном приложении { #pydantic-v1-and-v2-on-the-same-app }
+
+В Pydantic **не поддерживается** ситуация, когда в одной модели Pydantic v2 используются поля, определённые как модели Pydantic v1, и наоборот.
+
+```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
+```
+
+…но в одном и том же приложении вы можете иметь отдельные модели на Pydantic v1 и v2.
+
+```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
+```
+
+В некоторых случаях можно использовать и модели Pydantic v1, и v2 в одной и той же **операции пути** (обработчике пути) вашего приложения FastAPI:
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *}
+
+В примере выше модель входных данных — это модель Pydantic v1, а модель выходных данных (указанная в `response_model=ItemV2`) — это модель Pydantic v2.
+
+### Параметры Pydantic v1 { #pydantic-v1-parameters }
+
+Если вам нужно использовать некоторые специфичные для FastAPI инструменты для параметров, такие как `Body`, `Query`, `Form` и т.п., с моделями Pydantic v1, вы можете импортировать их из `fastapi.temp_pydantic_v1_params`, пока завершаете миграцию на Pydantic v2:
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *}
+
+### Мигрируйте по шагам { #migrate-in-steps }
+
+/// tip | Совет
+
+Сначала попробуйте `bump-pydantic`: если тесты проходят и всё работает, вы справились одной командой. ✨
+
+///
+
+Если `bump-pydantic` не подходит для вашего случая, вы можете использовать поддержку одновременной работы моделей Pydantic v1 и v2 в одном приложении, чтобы мигрировать на Pydantic v2 постепенно.
+
+Сначала вы можете обновить Pydantic до последней 2-й версии и изменить импорты так, чтобы все ваши модели использовали `pydantic.v1`.
+
+Затем вы можете начать мигрировать ваши модели с Pydantic v1 на v2 группами, поэтапно. 🚶
diff --git a/docs/ru/docs/how-to/separate-openapi-schemas.md b/docs/ru/docs/how-to/separate-openapi-schemas.md
new file mode 100644
index 000000000..8f6c83e7e
--- /dev/null
+++ b/docs/ru/docs/how-to/separate-openapi-schemas.md
@@ -0,0 +1,102 @@
+# Разделять схемы OpenAPI для входа и выхода или нет { #separate-openapi-schemas-for-input-and-output-or-not }
+
+При использовании **Pydantic v2** сгенерированный OpenAPI становится чуть более точным и **корректным**, чем раньше. 😎
+
+На самом деле, в некоторых случаях в OpenAPI будет даже **две JSON-схемы** для одной и той же Pydantic‑модели: для входа и для выхода — в зависимости от наличия **значений по умолчанию**.
+
+Посмотрим, как это работает, и как это изменить при необходимости.
+
+## Pydantic‑модели для входа и выхода { #pydantic-models-for-input-and-output }
+
+Предположим, у вас есть Pydantic‑модель со значениями по умолчанию, как здесь:
+
+{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *}
+
+### Модель для входа { #model-for-input }
+
+Если использовать эту модель как входную, как здесь:
+
+{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *}
+
+…то поле `description` **не будет обязательным**, потому что у него значение по умолчанию `None`.
+
+### Входная модель в документации { #input-model-in-docs }
+
+В документации это видно: у поля `description` нет **красной звёздочки** — оно не отмечено как обязательное:
+
+
+
+
+
+
+- Готовый к внедрению высокопроизводительный фреймворк, простой в изучении и разработке. + Фреймворк FastAPI: высокая производительность, прост в изучении, позволяет быстро писать код, готов к продакшн
--- -**Документация**: https://fastapi.tiangolo.com +**Документация**: https://fastapi.tiangolo.com **Исходный код**: https://github.com/fastapi/fastapi --- -FastAPI — это современный, быстрый (высокопроизводительный) веб-фреймворк для создания API используя Python, в основе которого лежит стандартная аннотация типов Python. +FastAPI — это современный, быстрый (высокопроизводительный) веб-фреймворк для создания API на Python, основанный на стандартных аннотациях типов Python. Ключевые особенности: -* **Скорость**: Очень высокая производительность, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic). [Один из самых быстрых фреймворков Python](#_10). -* **Быстрота разработки**: Увеличьте скорость разработки примерно на 200–300%. * +* **Скорость**: Очень высокая производительность, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic). [Один из самых быстрых доступных фреймворков Python](#performance). +* **Быстрота разработки**: Увеличьте скорость разработки фич примерно на 200–300%. * * **Меньше ошибок**: Сократите примерно на 40% количество ошибок, вызванных человеком (разработчиком). * -* **Интуитивно понятный**: Отличная поддержка редактора. Автозавершение везде. Меньше времени на отладку. -* **Лёгкость**: Разработан так, чтобы его было легко использовать и осваивать. Меньше времени на чтение документации. -* **Краткость**: Сведите к минимуму дублирование кода. Каждый объявленный параметр - определяет несколько функций. Меньше ошибок. -* **Надежность**: Получите готовый к работе код. С автоматической интерактивной документацией. -* **На основе стандартов**: Основан на открытых стандартах API и полностью совместим с ними: OpenAPI (ранее известном как Swagger) и JSON Schema. +* **Интуитивность**: Отличная поддержка редактора кода. Автозавершение везде. Меньше времени на отладку. +* **Простота**: Разработан так, чтобы его было легко использовать и осваивать. Меньше времени на чтение документации. +* **Краткость**: Минимизируйте дублирование кода. Несколько возможностей из каждого объявления параметров. Меньше ошибок. +* **Надежность**: Получите код, готовый к продакшн. С автоматической интерактивной документацией. +* **На основе стандартов**: Основан на открытых стандартах API и полностью совместим с ними: OpenAPI (ранее известный как Swagger) и JSON Schema. -* оценка на основе тестов внутренней команды разработчиков, создающих производственные приложения. +* оценка на основе тестов внутренней команды разработчиков, создающих продакшн-приложения. -## Спонсоры +## Спонсоры { #sponsors } -{% if sponsors %} +### Ключевой-спонсор { #keystone-sponsor } + +{% for sponsor in sponsors.keystone -%} +
-* Starlette для части связанной с вебом.
-* Pydantic для части связанной с данными.
+## **Typer**, FastAPI для CLI { #typer-the-fastapi-of-clis }
-## Установка
+uvicorn main:app --reload...fastapi dev main.py...email-validator - для проверки электронной почты.
+* email-validator — для проверки адресов электронной почты.
Используется Starlette:
-* HTTPX - Обязательно, если вы хотите использовать `TestClient`.
-* jinja2 - Обязательно, если вы хотите использовать конфигурацию шаблона по умолчанию.
-* python-multipart - Обязательно, если вы хотите поддерживать форму "парсинга" с помощью `request.form()`.
-* itsdangerous - Обязательно, для поддержки `SessionMiddleware`.
-* pyyaml - Обязательно, для поддержки `SchemaGenerator` Starlette (возможно, вам это не нужно с FastAPI).
+* httpx — обязателен, если вы хотите использовать `TestClient`.
+* jinja2 — обязателен, если вы хотите использовать конфигурацию шаблонов по умолчанию.
+* python-multipart - обязателен, если вы хотите поддерживать «парсинг» форм через `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:
-Используется FastAPI / Starlette:
+* pydantic-settings — для управления настройками.
+* pydantic-extra-types — дополнительные типы для использования с Pydantic.
-* uvicorn - сервер, который загружает и обслуживает ваше приложение.
-* orjson - Обязательно, если вы хотите использовать `ORJSONResponse`.
-* ujson - Обязательно, если вы хотите использовать `UJSONResponse`.
+Дополнительные опциональные зависимости FastAPI:
-Вы можете установить все это с помощью `pip install "fastapi[all]"`.
+* orjson — обязателен, если вы хотите использовать `ORJSONResponse`.
+* ujson — обязателен, если вы хотите использовать `UJSONResponse`.
-## Лицензия
+## Лицензия { #license }
Этот проект распространяется на условиях лицензии MIT.
diff --git a/docs/ru/docs/learn/index.md b/docs/ru/docs/learn/index.md
index b2e4cabc7..50fbd7738 100644
--- a/docs/ru/docs/learn/index.md
+++ b/docs/ru/docs/learn/index.md
@@ -1,4 +1,4 @@
-# Обучение
+# Обучение { #learn }
Здесь представлены вводные разделы и учебные пособия для изучения **FastAPI**.
diff --git a/docs/ru/docs/project-generation.md b/docs/ru/docs/project-generation.md
index efd6794ad..dbedf76fe 100644
--- a/docs/ru/docs/project-generation.md
+++ b/docs/ru/docs/project-generation.md
@@ -1,84 +1,28 @@
-# Генераторы проектов - Шаблоны
-
-Чтобы начать работу быстрее, Вы можете использовать "генераторы проектов", в которые включены множество начальных настроек для функций безопасности, баз данных и некоторые эндпоинты API.
-
-В генераторе проектов всегда будут предустановлены какие-то настройки, которые Вам следует обновить и подогнать под свои нужды, но это может быть хорошей отправной точкой для Вашего проекта.
-
-## Full Stack FastAPI PostgreSQL
-
-GitHub: https://github.com/tiangolo/full-stack-fastapi-postgresql
-
-### Full Stack FastAPI PostgreSQL - Особенности
-
-* Полностью интегрирован с **Docker** (основан на Docker).
-* Развёртывается в режиме Docker Swarm.
-* Интегрирован с **Docker Compose** и оптимизирован для локальной разработки.
-* **Готовый к реальной работе** веб-сервер Python использующий Uvicorn и Gunicorn.
-* Бэкенд построен на фреймворке **FastAPI**:
- * **Быстрый**: Высокопроизводительный, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic).
- * **Интуитивно понятный**: Отличная поддержка редактора. Автодополнение кода везде. Меньше времени на отладку.
- * **Простой**: Разработан так, чтоб быть простым в использовании и изучении. Меньше времени на чтение документации.
- * **Лаконичный**: Минимизировано повторение кода. Каждый объявленный параметр определяет несколько функций.
- * **Надёжный**: Получите готовый к работе код. С автоматической интерактивной документацией.
- * **Стандартизированный**: Основан на открытых стандартах API (OpenAPI и JSON Schema) и полностью совместим с ними.
- * **Множество других возможностей** включая автоматическую проверку и сериализацию данных, интерактивную документацию, аутентификацию с помощью OAuth2 JWT-токенов и т.д.
-* **Безопасное хранение паролей**, которые хэшируются по умолчанию.
-* Аутентификация посредством **JWT-токенов**.
-* Модели **SQLAlchemy** (независящие от расширений Flask, а значит могут быть непосредственно использованы процессами Celery).
-* Базовая модель пользователя (измените или удалите её по необходимости).
-* **Alembic** для организации миграций.
-* **CORS** (Совместное использование ресурсов из разных источников).
-* **Celery**, процессы которого могут выборочно импортировать и использовать модели и код из остальной части бэкенда.
-* Тесты, на основе **Pytest**, интегрированные в Docker, чтобы Вы могли полностью проверить Ваше API, независимо от базы данных. Так как тесты запускаются в Docker, для них может создаваться новое хранилище данных каждый раз (Вы можете, по своему желанию, использовать ElasticSearch, MongoDB, CouchDB или другую СУБД, только лишь для проверки - будет ли Ваше API работать с этим хранилищем).
-* Простая интеграция Python с **Jupyter Kernels** для разработки удалённо или в Docker с расширениями похожими на Atom Hydrogen или Visual Studio Code Jupyter.
-* Фронтенд построен на фреймворке **Vue**:
- * Сгенерирован с помощью Vue CLI.
- * Поддерживает **аутентификацию с помощью JWT-токенов**.
- * Страница логина.
- * Перенаправление на страницу главной панели мониторинга после логина.
- * Главная страница мониторинга с возможностью создания и изменения пользователей.
- * Пользователь может изменять свои данные.
- * **Vuex**.
- * **Vue-router**.
- * **Vuetify** для конструирования красивых компонентов страниц.
- * **TypeScript**.
- * Сервер Docker основан на **Nginx** (настроен для удобной работы с Vue-router).
- * Многоступенчатая сборка Docker, то есть Вам не нужно сохранять или коммитить скомпилированный код.
- * Тесты фронтенда запускаются во время сборки (можно отключить).
- * Сделан настолько модульно, насколько возможно, поэтому работает "из коробки", но Вы можете повторно сгенерировать фронтенд с помощью Vue CLI или создать то, что Вам нужно и повторно использовать то, что захотите.
-* **PGAdmin** для СУБД PostgreSQL, которые легко можно заменить на PHPMyAdmin и MySQL.
-* **Flower** для отслеживания работы Celery.
-* Балансировка нагрузки между фронтендом и бэкендом с помощью **Traefik**, а значит, Вы можете расположить их на одном домене, разделив url-пути, так как они обслуживаются разными контейнерами.
-* Интеграция с Traefik включает автоматическую генерацию сертификатов Let's Encrypt для поддержки протокола **HTTPS**.
-* GitLab **CI** (непрерывная интеграция), которая включает тестирование фронтенда и бэкенда.
-
-## Full Stack FastAPI Couchbase
-
-GitHub: https://github.com/tiangolo/full-stack-fastapi-couchbase
-
-⚠️ **ПРЕДУПРЕЖДЕНИЕ** ⚠️
-
-Если Вы начинаете новый проект, ознакомьтесь с представленными здесь альтернативами.
-
-Например, генератор проектов Full Stack FastAPI PostgreSQL может быть более подходящей альтернативой, так как он активно поддерживается и используется. И он включает в себя все новые возможности и улучшения.
-
-Но никто не запрещает Вам использовать генератор с СУБД Couchbase, возможно, он всё ещё работает нормально. Или у Вас уже есть проект, созданный с помощью этого генератора ранее, и Вы, вероятно, уже обновили его в соответствии со своими потребностями.
-
-Вы можете прочитать о нём больше в документации соответствующего репозитория.
-
-## Full Stack FastAPI MongoDB
-
-...может быть когда-нибудь появится, в зависимости от наличия у меня свободного времени и прочих факторов. 😅 🎉
-
-## Модели машинного обучения на основе spaCy и FastAPI
-
-GitHub: https://github.com/microsoft/cookiecutter-spacy-fastapi
-
-### Модели машинного обучения на основе spaCy и FastAPI - Особенности
-
-* Интеграция с моделями **spaCy** NER.
-* Встроенный формат запросов к **когнитивному поиску Azure**.
-* **Готовый к реальной работе** веб-сервер Python использующий Uvicorn и Gunicorn.
-* Встроенное развёртывание на основе **Azure DevOps** Kubernetes (AKS) CI/CD.
-* **Многоязычность**. Лёгкий выбор одного из встроенных в spaCy языков во время настройки проекта.
-* **Легко подключить** модели из других фреймворков (Pytorch, Tensorflow) не ограничиваясь spaCy.
+# Шаблон Full Stack FastAPI { #full-stack-fastapi-template }
+
+Шаблоны, хотя обычно поставляются с определённой конфигурацией, спроектированы так, чтобы быть гибкими и настраиваемыми. Это позволяет вам изменять их и адаптировать под требования вашего проекта, что делает их отличной отправной точкой. 🏁
+
+Вы можете использовать этот шаблон для старта: в нём уже сделана значительная часть начальной настройки, безопасность, база данных и несколько эндпоинтов API.
+
+Репозиторий GitHub: Full Stack FastAPI Template
+
+## Шаблон Full Stack FastAPI — Технологический стек и возможности { #full-stack-fastapi-template-technology-stack-and-features }
+
+- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/ru) для бэкенд‑API на Python.
+ - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) для взаимодействия с SQL‑базой данных на Python (ORM).
+ - 🔍 [Pydantic](https://docs.pydantic.dev), используется FastAPI, для валидации данных и управления настройками.
+ - 💾 [PostgreSQL](https://www.postgresql.org) в качестве SQL‑базы данных.
+- 🚀 [React](https://react.dev) для фронтенда.
+ - 💃 Используются TypeScript, хуки, Vite и другие части современного фронтенд‑стека.
+ - 🎨 [Tailwind CSS](https://tailwindcss.com) и [shadcn/ui](https://ui.shadcn.com) для компонентов фронтенда.
+ - 🤖 Автоматически сгенерированный фронтенд‑клиент.
+ - 🧪 [Playwright](https://playwright.dev) для End‑to‑End тестирования.
+ - 🦇 Поддержка тёмной темы.
+- 🐋 [Docker Compose](https://www.docker.com) для разработки и продакшна.
+- 🔒 Безопасное хэширование паролей по умолчанию.
+- 🔑 Аутентификация по JWT‑токенам.
+- 📫 Восстановление пароля по электронной почте.
+- ✅ Тесты с [Pytest](https://pytest.org).
+- 📞 [Traefik](https://traefik.io) в роли обратного прокси / балансировщика нагрузки.
+- 🚢 Инструкции по развёртыванию с использованием Docker Compose, включая настройку фронтенд‑прокси Traefik для автоматического получения сертификатов HTTPS.
+- 🏭 CI (continuous integration) и CD (continuous deployment) на основе GitHub Actions.
diff --git a/docs/ru/docs/python-types.md b/docs/ru/docs/python-types.md
index b1d4715fd..ae4a1e2b7 100644
--- a/docs/ru/docs/python-types.md
+++ b/docs/ru/docs/python-types.md
@@ -1,29 +1,28 @@
-# Введение в аннотации типов Python
+# Введение в типы Python { #python-types-intro }
-Python имеет поддержку необязательных аннотаций типов.
+Python поддерживает необязательные «подсказки типов» (их также называют «аннотациями типов»).
-**Аннотации типов** являются специальным синтаксисом, который позволяет определять тип переменной.
+Эти **«подсказки типов»** или аннотации — это специальный синтаксис, позволяющий объявлять тип переменной.
-Объявление типов для переменных позволяет улучшить поддержку вашего кода редакторами и различными инструментами.
+Объявляя типы для ваших переменных, редакторы кода и инструменты смогут лучше вас поддерживать.
-Это просто **краткое руководство / напоминание** об аннотациях типов в 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()`.
-* Соединяет их через пробел.
-
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
+* Преобразует первую букву каждого значения в верхний регистр с помощью `title()`.
+* Соединяет их пробелом посередине.
+{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
-### Отредактируем пример
+### Отредактируем пример { #edit-it }
Это очень простая программа.
@@ -48,23 +46,23 @@ John Doe
В какой-то момент вы бы начали определение функции, у вас были бы готовы параметры...
-Но затем вы должны вызвать «тот метод, который преобразует первую букву в верхний регистр».
+Но затем нужно вызвать «тот метод, который делает первую букву заглавной».
-Было это `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
@@ -76,14 +74,13 @@ John Doe
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"
@@ -91,192 +88,314 @@ 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)`:
-
-{* ../../docs_src/python_types/tutorial004.py hl[2] *}
+Теперь вы знаете, что нужно исправить — преобразовать `age` в строку с помощью `str(age)`:
+{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
-## Объявление типов
+## Объявление типов { #declaring-types }
-Вы только что видели основное место для объявления подсказок типов. В качестве параметров функции.
+Вы только что увидели основное место, где объявляют подсказки типов — параметры функции.
-Это также основное место, где вы можете использовать их с **FastAPI**.
+Это также основное место, где вы будете использовать их с **FastAPI**.
-### Простые типы
+### Простые типы { #simple-types }
-Вы можете объявить все стандартные типы Python, а не только `str`.
+Вы можете объявлять все стандартные типы Python, не только `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**»-типами. И их можно объявлять, в том числе с указанием внутренних типов.
-Чтобы объявить эти типы и внутренние типы, вы можете использовать стандартный Python-модуль `typing`.
+Чтобы объявлять эти типы и их внутренние типы, вы можете использовать стандартный модуль Python `typing`. Он существует специально для поддержки подсказок типов.
-Он существует специально для поддержки подсказок этих типов.
+#### Новые версии Python { #newer-versions-of-python }
-#### `List`
+Синтаксис с использованием `typing` **совместим** со всеми версиями, от Python 3.6 до самых новых, включая Python 3.9, Python 3.10 и т.д.
-Например, давайте определим переменную как `list`, состоящий из `str`.
+По мере развития Python **новые версии** получают улучшенную поддержку этих аннотаций типов, и во многих случаях вам даже не нужно импортировать и использовать модуль `typing`, чтобы объявлять аннотации типов.
-Импортируйте `List` из `typing` (с заглавной `L`):
+Если вы можете выбрать более свежую версию Python для проекта, вы получите дополнительную простоту.
-{* ../../docs_src/python_types/tutorial006.py hl[1] *}
+Во всей документации есть примеры, совместимые с каждой версией 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 }
-Поскольку список является типом, содержащим некоторые внутренние типы, вы помещаете их в квадратные скобки:
+Например, давайте определим переменную как `list` из `str`.
-{* ../../docs_src/python_types/tutorial006.py hl[4] *}
+Объявите переменную с тем же синтаксисом двоеточия (`:`).
+
+В качестве типа укажите `list`.
+
+Так как список — это тип, содержащий внутренние типы, укажите их в квадратных скобках:
+{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
-/// tip
+/// info | Информация
Эти внутренние типы в квадратных скобках называются «параметрами типов».
-В этом случае `str` является параметром типа, передаваемым в `List`.
+В данном случае `str` — это параметр типа, передаваемый в `list`.
///
-Это означает: "переменная `items` является `list`, и каждый из элементов этого списка является `str`".
+Это означает: «переменная `items` — это `list`, и каждый элемент этого списка — `str`».
-Если вы будете так поступать, редактор может оказывать поддержку даже при обработке элементов списка:
+Таким образом, ваш редактор кода сможет помогать даже при обработке элементов списка:
-Без типов добиться этого практически невозможно.
+Без типов добиться этого почти невозможно.
-Обратите внимание, что переменная `item` является одним из элементов списка `items`.
+Обратите внимание, что переменная `item` — один из элементов списка `items`.
-И все же редактор знает, что это `str`, и поддерживает это.
+И всё же редактор кода знает, что это `str`, и даёт соответствующую поддержку.
-#### `Tuple` и `Set`
+#### Tuple и Set { #tuple-and-set }
-Вы бы сделали то же самое, чтобы объявить `tuple` и `set`:
-
-{* ../../docs_src/python_types/tutorial007.py hl[1,4] *}
+Аналогично вы бы объявили `tuple` и `set`:
+{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
Это означает:
-* Переменная `items_t` является `tuple` с 3 элементами: `int`, другим `int` и `str`.
-* Переменная `items_s` является `set` и каждый элемент имеет тип `bytes`.
-
-#### `Dict`
+* Переменная `items_t` — это `tuple` из 3 элементов: `int`, ещё один `int` и `str`.
+* Переменная `items_s` — это `set`, и каждый элемент имеет тип `bytes`.
-Чтобы определить `dict`, вы передаёте 2 параметра типов, разделённых запятыми.
+#### Dict { #dict }
-Первый параметр типа предназначен для ключей `dict`.
+Чтобы определить `dict`, вы передаёте 2 параметра типов, разделённые запятой.
-Второй параметр типа предназначен для значений `dict`:
+Первый параметр типа — для ключей `dict`.
-{* ../../docs_src/python_types/tutorial008.py hl[1,4] *}
+Второй параметр типа — для значений `dict`:
+{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
Это означает:
-* Переменная `prices` является `dict`:
- * Ключи этого `dict` имеют тип `str` (скажем, название каждого элемента).
+* Переменная `prices` — это `dict`:
+ * Ключи этого `dict` имеют тип `str` (скажем, название каждой позиции).
* Значения этого `dict` имеют тип `float` (скажем, цена каждой позиции).
-#### `Optional`
+#### Union { #union }
+
+Вы можете объявить, что переменная может быть **одним из нескольких типов**, например, `int` или `str`.
-Вы также можете использовать `Optional`, чтобы объявить, что переменная имеет тип, например, `str`, но это является «необязательным», что означает, что она также может быть `None`:
+В Python 3.6 и выше (включая Python 3.10) вы можете использовать тип `Union` из `typing` и перечислить в квадратных скобках все допустимые типы.
+
+В 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/tutorial009.py!}
+{!> ../../docs_src/python_types/tutorial008b_py39.py!}
+```
+
+////
+
+В обоих случаях это означает, что `item` может быть `int` или `str`.
+
+#### Возможно `None` { #possibly-none }
+
+Вы можете объявить, что значение может иметь определённый тип, например `str`, но также может быть и `None`.
+
+В Python 3.6 и выше (включая Python 3.10) это можно объявить, импортировав и используя `Optional` из модуля `typing`.
+
+```Python hl_lines="1 4"
+{!../../docs_src/python_types/tutorial009_py39.py!}
+```
+
+Использование `Optional[str]` вместо просто `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+ альтернативный вариант
+
+```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 допустим 🎉
```
-Использование `Optional[str]` вместо просто `str` позволит редактору помочь вам в обнаружении ошибок, в которых вы могли бы предположить, что значение всегда является `str`, хотя на самом деле это может быть и `None`.
+Хорошая новость: как только вы перейдёте на Python 3.10, об этом можно не переживать — вы сможете просто использовать `|` для объединения типов:
-#### Generic-типы
+{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
-Эти типы принимают параметры в квадратных скобках:
+И тогда вам не придётся задумываться о названиях вроде `Optional` и `Union`. 😎
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
+#### Generic-типы { #generic-types }
+
+Типы, которые принимают параметры типов в квадратных скобках, называются **Generic-типами** или **Generics**, например:
+
+//// tab | Python 3.10+
+
+Вы можете использовать те же встроенные типы как generics (с квадратными скобками и типами внутри):
+
+* `list`
+* `tuple`
+* `set`
+* `dict`
+
+И, как и в предыдущих версиях Python, из модуля `typing`:
+
+* `Union`
* `Optional`
-* ...и др.
+* ...и другие.
-называются **Generic-типами** или **Generics**.
+В Python 3.10, как альтернативу generics `Union` и `Optional`, можно использовать вертикальную черту (`|`) для объявления объединений типов — это гораздо лучше и проще.
-### Классы как типы
+////
-Вы также можете объявить класс как тип переменной.
+//// tab | Python 3.9+
-Допустим, у вас есть класс `Person` с полем `name`:
+Вы можете использовать те же встроенные типы как generics (с квадратными скобками и типами внутри):
-{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
+* `list`
+* `tuple`
+* `set`
+* `dict`
+И generics из модуля `typing`:
-Тогда вы можете объявить переменную типа `Person`:
+* `Union`
+* `Optional`
+* ...и другие.
+
+////
-{* ../../docs_src/python_types/tutorial010.py hl[6] *}
+### Классы как типы { #classes-as-types }
+Вы также можете объявлять класс как тип переменной.
-И снова вы получаете полную поддержку редактора:
+Допустим, у вас есть класс `Person` с именем:
+
+{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
+
+Тогда вы можете объявить переменную типа `Person`:
+
+{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
+
+И снова вы получите полную поддержку редактора кода:
-## Pydantic-модели
+Обратите внимание, что это означает: «`one_person` — это **экземпляр** класса `Person`».
-Pydantic является Python-библиотекой для выполнения валидации данных.
+Это не означает: «`one_person` — это **класс** с именем `Person`».
-Вы объявляете «форму» данных как классы с атрибутами.
+## Pydantic-модели { #pydantic-models }
-И каждый атрибут имеет тип.
+Pydantic — это библиотека Python для валидации данных.
-Затем вы создаете экземпляр этого класса с некоторыми значениями, и он проверяет значения, преобразует их в соответствующий тип (если все верно) и предоставляет вам объект со всеми данными.
+Вы объявляете «форму» данных как классы с атрибутами.
+
+И у каждого атрибута есть тип.
-И вы получаете полную поддержку редактора для этого итогового объекта.
+Затем вы создаёте экземпляр этого класса с некоторыми значениями — он провалидирует значения, преобразует их к соответствующему типу (если это применимо) и вернёт вам объект со всеми данными.
-Взято из официальной документации Pydantic:
+И вы получите полную поддержку редактора кода для этого результирующего объекта.
-{* ../../docs_src/python_types/tutorial011.py *}
+Пример из официальной документации Pydantic:
+{* ../../docs_src/python_types/tutorial011_py310.py *}
-/// info
+/// info | Информация
-Чтобы узнать больше о Pydantic, читайте его документацию.
+Чтобы узнать больше о Pydantic, ознакомьтесь с его документацией.
///
@@ -284,30 +403,62 @@ John Doe
Вы увидите намного больше всего этого на практике в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
-## Аннотации типов в **FastAPI**
+/// tip | Совет
+
+У Pydantic есть особое поведение, когда вы используете `Optional` или `Union[Something, None]` без значения по умолчанию. Подробнее читайте в документации Pydantic: Required Optional fields.
+
+///
+
+## Подсказки типов с аннотациями метаданных { #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** использует эти подсказки типов для выполнения нескольких задач.
-С **FastAPI** вы объявляете параметры с аннотациями типов и получаете:
+С **FastAPI** вы объявляете параметры с подсказками типов и получаете:
-* **Поддержку редактора**.
+* **Поддержку редактора кода**.
* **Проверки типов**.
-...и **FastAPI** использует тот же механизм для:
+...и **FastAPI** использует эти же объявления для:
-* **Определения требований**: из параметров пути запроса, параметров запроса, заголовков, зависимостей и т.д.
-* **Преобразования данных**: от запроса к нужному типу.
-* **Валидации данных**: исходя из каждого запроса:
- * Генерации **автоматических ошибок**, возвращаемых клиенту, когда данные не являются корректными.
+* **Определения требований**: из path-параметров пути запроса, query-параметров, HTTP-заголовков, тел запросов, зависимостей и т.д.
+* **Преобразования данных**: из HTTP-запроса к требуемому типу.
+* **Валидации данных**: приходящих с каждого HTTP-запроса:
+ * Генерации **автоматических ошибок**, возвращаемых клиенту, когда данные некорректны.
* **Документирования** API с использованием OpenAPI:
- * который затем используется пользовательскими интерфейсами автоматической интерактивной документации.
+ * что затем используется пользовательскими интерфейсами автоматической интерактивной документации.
-Всё это может показаться абстрактным. Не волнуйтесь. Вы увидите всё это в действии в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
+Всё это может звучать абстрактно. Не волнуйтесь. Вы увидите всё это в действии в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
-Важно то, что при использовании стандартных типов Python в одном месте (вместо добавления дополнительных классов, декораторов и т.д.) **FastAPI** сделает за вас большую часть работы.
+Важно то, что, используя стандартные типы Python в одном месте (вместо добавления дополнительных классов, декораторов и т.д.), **FastAPI** сделает за вас большую часть работы.
-/// info
+/// info | Информация
-Если вы уже прошли всё руководство и вернулись, чтобы узнать больше о типах, хорошим ресурсом является «шпаргалка» от `mypy`.
+Если вы уже прошли всё руководство и вернулись, чтобы узнать больше о типах, хорошим ресурсом будет «шпаргалка» от `mypy`.
///
diff --git a/docs/ru/docs/resources/index.md b/docs/ru/docs/resources/index.md
new file mode 100644
index 000000000..faf80f7f4
--- /dev/null
+++ b/docs/ru/docs/resources/index.md
@@ -0,0 +1,3 @@
+# Ресурсы { #resources }
+
+Дополнительные ресурсы, внешние ссылки и многое другое. ✈️
diff --git a/docs/ru/docs/tutorial/background-tasks.md b/docs/ru/docs/tutorial/background-tasks.md
index bf2e9dec3..8d7b7442f 100644
--- a/docs/ru/docs/tutorial/background-tasks.md
+++ b/docs/ru/docs/tutorial/background-tasks.md
@@ -1,84 +1,84 @@
-# Фоновые задачи
+# Фоновые задачи { #background-tasks }
-Вы можете создавать фоновые задачи, которые будут выполнятся *после* возвращения ответа сервером.
+Вы можете создавать фоновые задачи, которые будут выполняться после возврата ответа.
-Это может быть полезно для функций, которые должны выполниться после получения запроса, но ожидание их выполнения необязательно для пользователя.
+Это полезно для операций, которые должны произойти после HTTP-запроса, но клиенту не обязательно ждать их завершения, чтобы получить ответ.
-К примеру:
+Например:
-* Отправка писем на почту после выполнения каких-либо действий:
- * Т.к. соединение с почтовым сервером и отправка письма идут достаточно "долго" (несколько секунд), вы можете отправить ответ пользователю, а отправку письма выполнить в фоне.
+* Уведомления по электронной почте, отправляемые после выполнения действия:
+ * Так как подключение к почтовому серверу и отправка письма обычно «медленные» (несколько секунд), вы можете сразу вернуть ответ, а отправку уведомления выполнить в фоне.
* Обработка данных:
- * К примеру, если вы получаете файл, который должен пройти через медленный процесс, вы можете отправить ответ "Accepted" (HTTP 202) и отправить работу с файлом в фон.
+ * Например, если вы получаете файл, который должен пройти через медленный процесс, вы можете вернуть ответ «Accepted» (HTTP 202) и обработать файл в фоне.
-## Использование класса `BackgroundTasks`
+## Использование `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 }
-Создайте функцию, которую хотите запустить в фоне.
+Создайте функцию, которую нужно запустить как фоновую задачу.
-Это совершенно обычная функция, которая может принимать параметры.
+Это обычная функция, которая может принимать параметры.
-Она может быть как асинхронной `async def`, так и обычной `def` функцией, **FastAPI** знает, как правильно ее выполнить.
+Это может быть как `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()` у объекта *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` на разных уровнях: как параметр функции, как завимость, как подзависимость и так далее.
+Использование `BackgroundTasks` также работает с системой встраивания зависимостей, вы можете объявить параметр типа `BackgroundTasks` на нескольких уровнях: в функции‑обработчике пути, в зависимости (dependable), в подзависимости и т. д.
-**FastAPI** знает, что нужно сделать в каждом случае и как переиспользовать тот же объект `BackgroundTasks`, так чтобы все фоновые задачи собрались и запустились вместе в фоне:
+**FastAPI** знает, что делать в каждом случае и как переиспользовать один и тот же объект, так чтобы все фоновые задачи были объединены и затем выполнены в фоне:
-{* ../../docs_src/background_tasks/tutorial002_py310.py hl[11,13,20,23] *}
+{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *}
-В этом примере сообщения будут записаны в `log.txt` *после* того, как ответ сервера был отправлен.
+В этом примере сообщения будут записаны в файл `log.txt` после отправки ответа.
-Если бы в запрос был передан query-параметр `q`, он бы первыми записался в `log.txt` фоновой задачей (потому что вызывается в зависимости `get_query`).
+Если в запросе была строка запроса (query), она будет записана в лог фоновой задачей.
-После другая фоновая задача, которая была сгенерирована в функции, запишет сообщение из параметра `email`.
+Затем другая фоновая задача, созданная в функции‑обработчике пути, запишет сообщение, используя path‑параметр `email`.
-## Технические детали
+## Технические детали { #technical-details }
-Класс `BackgroundTasks` основан на `starlette.background`.
+Класс `BackgroundTasks` приходит напрямую из `starlette.background`.
-Он интегрирован в FastAPI, так что вы можете импортировать его прямо из `fastapi` и избежать случайного импорта `BackgroundTask` (без `s` на конце) из `starlette.background`.
+Он импортируется/включается прямо в FastAPI, чтобы вы могли импортировать его из `fastapi` и избежать случайного импорта альтернативного `BackgroundTask` (без `s` на конце) из `starlette.background`.
-При использовании `BackgroundTasks` (а не `BackgroundTask`), вам достаточно только определить параметр функции с типом `BackgroundTasks` и **FastAPI** сделает все за вас, также как при использовании объекта `Request`.
+Используя только `BackgroundTasks` (а не `BackgroundTask`), его можно применять как параметр функции‑обработчика пути, и **FastAPI** сделает остальное за вас, как при использовании объекта `Request` напрямую.
-Вы все равно можете использовать `BackgroundTask` из `starlette` в FastAPI, но вам придется самостоятельно создавать объект фоновой задачи и вручную обработать `Response` внутри него.
+По‑прежнему можно использовать один `BackgroundTask` в FastAPI, но тогда вам нужно создать объект в своём коде и вернуть Starlette `Response`, включающий его.
-Вы можете подробнее изучить его в Официальной документации Starlette для BackgroundTasks.
+Подробнее см. в официальной документации Starlette по фоновым задачам.
-## Предостережение
+## Предостережение { #caveat }
-Если вам нужно выполнить тяжелые вычисления в фоне, которым необязательно быть запущенными в одном процессе с приложением **FastAPI** (к примеру, вам не нужны обрабатываемые переменные или вы не хотите делиться памятью процесса и т.д.), вы можете использовать более серьезные инструменты, такие как Celery.
+Если вам нужно выполнять тяжелые вычисления в фоне, и при этом они не обязательно должны запускаться тем же процессом (например, вам не нужно делиться памятью, переменными и т. п.), вам могут подойти более мощные инструменты, такие как Celery.
-Их тяжелее настраивать, также им нужен брокер сообщений наподобие RabbitMQ или Redis, но зато они позволяют вам запускать фоновые задачи в нескольких процессах и даже на нескольких серверах.
+Они обычно требуют более сложной конфигурации, менеджера очереди сообщений/заданий (например, RabbitMQ или Redis), но позволяют запускать фоновые задачи в нескольких процессах и, что особенно важно, на нескольких серверах.
-Но если вам нужен доступ к общим переменным и объектам вашего **FastAPI** приложения или вам нужно выполнять простые фоновые задачи (наподобие отправки письма из примера) вы можете просто использовать `BackgroundTasks`.
+Но если вам нужен доступ к переменным и объектам из того же приложения **FastAPI**, или нужно выполнять небольшие фоновые задачи (например, отправку email‑уведомления), вы можете просто использовать `BackgroundTasks`.
-## Резюме
+## Резюме { #recap }
-Для создания фоновых задач вам необходимо импортировать `BackgroundTasks` и добавить его в функцию, как параметр с типом `BackgroundTasks`.
+Импортируйте и используйте `BackgroundTasks` с параметрами в функциях‑обработчиках пути и зависимостях, чтобы добавлять фоновые задачи.
diff --git a/docs/ru/docs/tutorial/bigger-applications.md b/docs/ru/docs/tutorial/bigger-applications.md
index 8b9080d39..76304523c 100644
--- a/docs/ru/docs/tutorial/bigger-applications.md
+++ b/docs/ru/docs/tutorial/bigger-applications.md
@@ -1,4 +1,4 @@
-# Большие приложения, в которых много файлов
+# Большие приложения — несколько файлов { #bigger-applications-multiple-files }
При построении приложения или веб-API нам редко удается поместить всё в один файл.
@@ -10,7 +10,7 @@
///
-## Пример структуры приложения
+## Пример структуры приложения { #an-example-file-structure }
Давайте предположим, что наше приложение имеет следующую структуру:
@@ -31,7 +31,7 @@
/// tip | Подсказка
-Обратите внимание, что в каждом каталоге и подкаталоге имеется файл `__init__.py`
+Есть несколько файлов `__init__.py`: по одному в каждом каталоге или подкаталоге.
Это как раз то, что позволяет импортировать код из одного файла в другой.
@@ -43,66 +43,63 @@ from app.routers import items
///
-* Всё помещается в каталоге `app`. В нём также находится пустой файл `app/__init__.py`. Таким образом, `app` является "Python-пакетом" (коллекцией модулей Python).
-* Он содержит файл `app/main.py`. Данный файл является частью пакета (т.е. находится внутри каталога, содержащего файл `__init__.py`), и, соответственно, он является модулем пакета: `app.main`.
+* Всё помещается в каталоге `app`. В нём также находится пустой файл `app/__init__.py`. Таким образом, `app` является "Python-пакетом" (коллекцией "Python-модулей"): `app`.
+* Он содержит файл `app/main.py`. Данный файл является частью Python-пакета (т.е. находится внутри каталога, содержащего файл `__init__.py`), и, соответственно, он является модулем этого пакета: `app.main`.
* Он также содержит файл `app/dependencies.py`, который также, как и `app/main.py`, является модулем: `app.dependencies`.
-* Здесь также находится подкаталог `app/routers/`, содержащий `__init__.py`. Он является суб-пакетом: `app.routers`.
-* Файл `app/routers/items.py` находится внутри пакета `app/routers/`. Таким образом, он является суб-модулем: `app.routers.items`.
-* Точно также `app/routers/users.py` является ещё одним суб-модулем: `app.routers.users`.
-* Подкаталог `app/internal/`, содержащий файл `__init__.py`, является ещё одним суб-пакетом: `app.internal`.
-* А файл `app/internal/admin.py` является ещё одним суб-модулем: `app.internal.admin`.
+* Здесь также находится подкаталог `app/routers/`, содержащий `__init__.py`. Он является Python-подпакетом: `app.routers`.
+* Файл `app/routers/items.py` находится внутри пакета `app/routers/`. Таким образом, он является подмодулем: `app.routers.items`.
+* Точно так же `app/routers/users.py` является ещё одним подмодулем: `app.routers.users`.
+* Подкаталог `app/internal/`, содержащий файл `__init__.py`, является ещё одним Python-подпакетом: `app.internal`.
+* А файл `app/internal/admin.py` является ещё одним подмодулем: `app.internal.admin`.
-## Подключение существующего маршрута через новый префикс (`prefix`)
+## Подключение одного и того же маршрутизатора несколько раз с разными `prefix` { #include-the-same-router-multiple-times-with-different-prefix }
-Вы можете использовать `.include_router()` несколько раз с одним и тем же маршрутом, применив различные префиксы.
+Вы можете использовать `.include_router()` несколько раз с *одним и тем же* маршрутизатором, используя разные префиксы.
-Это может быть полезным, если нужно предоставить доступ к одному и тому же API через различные префиксы, например, `/api/v1` и `/api/latest`.
+Это может быть полезно, например, чтобы предоставить доступ к одному и тому же API с разными префиксами, например `/api/v1` и `/api/latest`.
-Это продвинутый способ, который вам может и не пригодится. Мы приводим его на случай, если вдруг вам это понадобится.
+Это продвинутое использование, которое вам может и не понадобиться, но оно есть на случай, если понадобится.
-## Включение одного маршрутизатора (`APIRouter`) в другой
+## Подключение `APIRouter` в другой `APIRouter` { #include-an-apirouter-in-another }
-Точно так же, как вы включаете `APIRouter` в приложение `FastAPI`, вы можете включить `APIRouter` в другой `APIRouter`:
+Точно так же, как вы можете подключить `APIRouter` к приложению `FastAPI`, вы можете подключить `APIRouter` к другому `APIRouter`, используя:
```Python
router.include_router(other_router)
```
-Удостоверьтесь, что вы сделали это до того, как подключить маршрутизатор (`router`) к вашему `FastAPI` приложению, и *эндпоинты* маршрутизатора `other_router` были также подключены.
+Убедитесь, что вы сделали это до подключения `router` к приложению `FastAPI`, чтобы *операции пути* из `other_router` также были подключены.
diff --git a/docs/ru/docs/tutorial/body-fields.md b/docs/ru/docs/tutorial/body-fields.md
index 5ed5f59fc..cf6cf480a 100644
--- a/docs/ru/docs/tutorial/body-fields.md
+++ b/docs/ru/docs/tutorial/body-fields.md
@@ -1,12 +1,12 @@
-# Body - Поля
+# Body - Поля { #body-fields }
Таким же способом, как вы объявляете дополнительную валидацию и метаданные в параметрах *функции обработки пути* с помощью функций `Query`, `Path` и `Body`, вы можете объявлять валидацию и метаданные внутри Pydantic моделей, используя функцию `Field` из Pydantic.
-## Импорт `Field`
+## Импорт `Field` { #import-field }
Сначала вы должны импортировать его:
-{* ../../docs_src/body_fields/tutorial001_py310.py hl[2] *}
+{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *}
/// warning | Внимание
@@ -14,11 +14,11 @@
///
-## Объявление атрибутов модели
+## Объявление атрибутов модели { #declare-model-attributes }
Вы можете использовать функцию `Field` с атрибутами модели:
-{* ../../docs_src/body_fields/tutorial001_py310.py hl[9:12] *}
+{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *}
Функция `Field` работает так же, как `Query`, `Path` и `Body`, у неё такие же параметры и т.д.
@@ -26,9 +26,9 @@
На самом деле, `Query`, `Path` и другие функции, которые вы увидите в дальнейшем, создают объекты подклассов общего класса `Param`, который сам по себе является подклассом `FieldInfo` из Pydantic.
-И `Field` (из Pydantic), и `Body`, оба возвращают объекты подкласса `FieldInfo`.
+И `Field` (из Pydantic) также возвращает экземпляр `FieldInfo`.
-У класса `Body` есть и другие подклассы, с которыми вы ознакомитесь позже.
+`Body` также напрямую возвращает объекты подкласса `FieldInfo`. И есть и другие, с которыми вы познакомитесь позже, которые являются подклассами класса `Body`.
Помните, что когда вы импортируете `Query`, `Path` и другое из `fastapi`, это фактически функции, которые возвращают специальные классы.
@@ -40,12 +40,11 @@
///
-## Добавление дополнительной информации
+## Добавление дополнительной информации { #add-extra-information }
Вы можете объявлять дополнительную информацию в `Field`, `Query`, `Body` и т.п. Она будет включена в сгенерированную JSON схему.
-Вы узнаете больше о добавлении дополнительной информации позже в документации, когда будете изучать, как задавать примеры принимаемых данных.
-
+Вы узнаете больше о добавлении дополнительной информации позже в документации, когда будете изучать, как задавать примеры.
/// warning | Внимание
@@ -54,7 +53,7 @@
///
-## Резюме
+## Резюме { #recap }
Вы можете использовать функцию `Field` из Pydantic, чтобы задавать дополнительную валидацию и метаданные для атрибутов модели.
diff --git a/docs/ru/docs/tutorial/body-multiple-params.md b/docs/ru/docs/tutorial/body-multiple-params.md
index 9300aa1bd..9ae57a311 100644
--- a/docs/ru/docs/tutorial/body-multiple-params.md
+++ b/docs/ru/docs/tutorial/body-multiple-params.md
@@ -1,8 +1,8 @@
-# Body - Множество параметров
+# Body - Множество параметров { #body-multiple-parameters }
-Теперь, когда мы увидели, как использовать `Path` и `Query` параметры, давайте рассмотрим более продвинутые примеры обьявления тела запроса.
+Теперь, когда мы увидели, как использовать `Path` и `Query` параметры, давайте рассмотрим более продвинутые примеры объявления тела запроса.
-## Обьединение `Path`, `Query` и параметров тела запроса
+## Объединение `Path`, `Query` и параметров тела запроса { #mix-path-query-and-body-parameters }
Во-первых, конечно, вы можете объединять параметры `Path`, `Query` и объявления тела запроса в своих функциях обработки, **FastAPI** автоматически определит, что с ними нужно делать.
@@ -16,9 +16,9 @@
///
-## Несколько параметров тела запроса
+## Несколько параметров тела запроса { #multiple-body-parameters }
-В предыдущем примере, *операции пути* ожидали тело запроса в формате JSON-тело с параметрами, соответствующими атрибутам `Item`, например:
+В предыдущем примере, *операции пути* ожидали тело запроса в формате JSON, с параметрами, соответствующими атрибутам `Item`, например:
```JSON
{
@@ -33,7 +33,7 @@
{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *}
-В этом случае **FastAPI** заметит, что в функции есть более одного параметра тела (два параметра, которые являются моделями Pydantic).
+В этом случае **FastAPI** заметит, что в функции есть более одного параметра тела (два параметра, которые являются Pydantic-моделями).
Таким образом, имена параметров будут использоваться в качестве ключей (имён полей) в теле запроса, и будет ожидаться запрос следующего формата:
@@ -54,21 +54,21 @@
/// note | Внимание
-Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предпологается, что он находится внутри тела с ключом `item`.
+Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предполагается, что он находится внутри тела с ключом `item`.
///
-**FastAPI** сделает автоматические преобразование из запроса, так что параметр `item` получит своё конкретное содержимое, и то же самое происходит с пользователем `user`.
+**FastAPI** сделает автоматическое преобразование из запроса, так что параметр `item` получит своё конкретное содержимое, и то же самое происходит с пользователем `user`.
Произойдёт проверка составных данных, и создание документации в схеме OpenAPI и автоматических документах.
-## Отдельные значения в теле запроса
+## Отдельные значения в теле запроса { #singular-values-in-body }
Точно так же, как `Query` и `Path` используются для определения дополнительных данных для query и path параметров, **FastAPI** предоставляет аналогичный инструмент - `Body`.
Например, расширяя предыдущую модель, вы можете решить, что вам нужен еще один ключ `importance` в том же теле запроса, помимо параметров `item` и `user`.
-Если вы объявите его без указания, какой именно объект (Path, Query, Body и .т.п.) ожидаете, то, поскольку это является простым типом данных, **FastAPI** будет считать, что это query-параметр.
+Если вы объявите его без указания, какой именно объект (Path, Query, Body и т.п.) ожидаете, то, поскольку это является простым типом данных, **FastAPI** будет считать, что это query-параметр.
Но вы можете указать **FastAPI** обрабатывать его, как ещё один ключ тела запроса, используя `Body`:
@@ -94,7 +94,7 @@
И всё будет работать так же - преобразование типов данных, валидация, документирование и т.д.
-## Множество body и query параметров
+## Множество body и query параметров { #multiple-body-params-and-query }
Конечно, вы также можете объявлять query-параметры в любое время, дополнительно к любым body-параметрам.
@@ -112,7 +112,7 @@ q: str | None = None
Например:
-{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[27] *}
+{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *}
/// info | Информация
@@ -120,9 +120,9 @@ q: str | None = None
///
-## Добавление одного body-параметра
+## Вложить один body-параметр { #embed-a-single-body-parameter }
-Предположим, у вас есть только один body-параметр `item` из Pydantic модели `Item`.
+Предположим, у вас есть только один body-параметр `item` из Pydantic-модели `Item`.
По умолчанию, **FastAPI** ожидает получить тело запроса напрямую.
@@ -160,9 +160,9 @@ item: Item = Body(embed=True)
}
```
-## Резюме
+## Резюме { #recap }
-Вы можете добавлять несколько body-параметров вашей *функции операции пути*, несмотря даже на то, что запрос может содержать только одно тело.
+Вы можете добавлять несколько body-параметров вашей *функции-обработчика пути*, несмотря даже на то, что запрос может содержать только одно тело.
Но **FastAPI** справится с этим, предоставит правильные данные в вашей функции, а также сделает валидацию и документацию правильной схемы *операции пути*.
diff --git a/docs/ru/docs/tutorial/body-nested-models.md b/docs/ru/docs/tutorial/body-nested-models.md
index 430092892..4c914b97f 100644
--- a/docs/ru/docs/tutorial/body-nested-models.md
+++ b/docs/ru/docs/tutorial/body-nested-models.md
@@ -1,63 +1,42 @@
-# Body - Вложенные модели
+# Body - Вложенные модели { #body-nested-models }
-С помощью **FastAPI**, вы можете определять, валидировать, документировать и использовать модели произвольной вложенности (благодаря библиотеке Pydantic).
+С помощью **FastAPI** вы можете определять, валидировать, документировать и использовать модели произвольной глубины вложенности (благодаря Pydantic).
-## Определение полей содержащих списки
+## Поля-списки { #list-fields }
-Вы можете определять атрибут как подтип. Например, тип `list` в Python:
+Вы можете определить атрибут как подтип. Например, Python-тип `list`:
{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *}
-Это приведёт к тому, что обьект `tags` преобразуется в список, несмотря на то что тип его элементов не объявлен.
+Это приведёт к тому, что `tags` будет списком, несмотря на то, что тип его элементов не объявлен.
-## Определение полей содержащих список с определением типов его элементов
+## Поля-списки с параметром типа { #list-fields-with-type-parameter }
-Однако в Python есть способ объявления списков с указанием типов для вложенных элементов:
+В Python есть специальный способ объявлять списки с внутренними типами, или «параметрами типа»:
-### Импортируйте `List` из модуля typing
+### Объявите `list` с параметром типа { #declare-a-list-with-a-type-parameter }
-В Python 3.9 и выше вы можете использовать стандартный тип `list` для объявления аннотаций типов, как мы увидим ниже. 💡
-
-Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing` в Python:
-
-{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
-
-### Объявление `list` с указанием типов для вложенных элементов
-
-Объявление типов для элементов (внутренних типов) вложенных в такие типы как `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 для объявления типов.
Используйте этот же стандартный синтаксис для атрибутов модели с внутренними типами.
-Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как "список строк":
+Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как «список строк»:
{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}
-## Типы множеств
+## Типы множеств { #set-types }
-Но затем мы подумали и поняли, что теги не должны повторяться и, вероятно, они должны быть уникальными строками.
+Но затем мы подумали и поняли, что теги не должны повторяться, вероятно, это должны быть уникальные строки.
-И в Python есть специальный тип данных для множеств уникальных элементов - `set`.
+И в Python есть специальный тип данных для множеств уникальных элементов — `set`.
-Тогда мы можем обьявить поле `tags` как множество строк:
+Тогда мы можем объявить поле `tags` как множество строк:
{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *}
@@ -67,23 +46,23 @@ my_list: List[str]
И они также будут соответствующим образом аннотированы / задокументированы.
-## Вложенные Модели
+## Вложенные модели { #nested-models }
У каждого атрибута Pydantic-модели есть тип.
-Но этот тип может сам быть другой моделью 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 }
Также мы можем использовать эту модель как тип атрибута:
@@ -107,30 +86,30 @@ my_list: List[str]
Ещё раз: сделав такое объявление, с помощью **FastAPI** вы получите:
-* Поддержку редакторов IDE (автодополнение и т.д), даже для вложенных моделей
+* Поддержку редактора кода (автозавершение и т.д.), даже для вложенных моделей
* Преобразование данных
* Валидацию данных
* Автоматическую документацию
-## Особые типы и валидация
+## Особые типы и валидация { #special-types-and-validation }
-Помимо обычных простых типов, таких как `str`, `int`, `float`, и т.д. Вы можете использовать более сложные базовые типы, которые наследуются от типа `str`.
+Помимо обычных простых типов, таких как `str`, `int`, `float` и т.д., вы можете использовать более сложные простые типы, которые наследуются от `str`.
-Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с документацией по необычным типам Pydantic. Вы увидите некоторые примеры в следующей главе.
+Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с обзором типов Pydantic. Вы увидите некоторые примеры в следующей главе.
-Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из модуля Pydantic вместо типа `str`:
+Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из Pydantic вместо типа `str`:
{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *}
-Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON схему / OpenAPI.
+Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON Schema / OpenAPI как таковая.
-## Атрибуты, содержащие списки подмоделей
+## Атрибуты, содержащие списки подмоделей { #attributes-with-lists-of-submodels }
-Вы также можете использовать модели Pydantic в качестве типов вложенных в `list`, `set` и т.д:
+Вы также можете использовать модели Pydantic в качестве подтипов для `list`, `set` и т.д.:
{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *}
-Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д) JSON-содержимое в следующем формате:
+Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д.) JSON-содержимое в следующем формате:
```JSON hl_lines="11"
{
@@ -162,7 +141,7 @@ my_list: List[str]
///
-## Глубоко вложенные модели
+## Глубоко вложенные модели { #deeply-nested-models }
Вы можете определять модели с произвольным уровнем вложенности:
@@ -174,15 +153,9 @@ my_list: List[str]
///
-## Тела с чистыми списками элементов
-
-Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python - `list`), вы можете объявить тип в параметре функции, так же, как в моделях Pydantic:
-
-```Python
-images: List[Image]
-```
+## Тела с чистыми списками элементов { #bodies-of-pure-lists }
-в Python 3.9 и выше:
+Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python — `list`), вы можете объявить тип в параметре функции, так же как в моделях Pydantic:
```Python
images: list[Image]
@@ -192,33 +165,33 @@ images: list[Image]
{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}
-## Универсальная поддержка редактора
+## Поддержка редактора кода везде { #editor-support-everywhere }
-И вы получаете поддержку редактора везде.
+И вы получаете поддержку редактора кода везде.
Даже для элементов внутри списков:
-Вы не могли бы получить такую поддержку редактора, если бы работали напрямую с `dict`, а не с моделями Pydantic.
+Вы не могли бы получить такую поддержку редактора кода, если бы работали напрямую с `dict`, а не с моделями Pydantic.
Но вы также не должны беспокоиться об этом, входящие словари автоматически конвертируются, а ваш вывод также автоматически преобразуется в формат JSON.
-## Тела запросов с произвольными словарями (`dict` )
+## Тела запросов с произвольными словарями (`dict`) { #bodies-of-arbitrary-dicts }
-Вы также можете объявить тело запроса как `dict` с ключами определенного типа и значениями другого типа данных.
+Вы также можете объявить тело запроса как `dict` с ключами определённого типа и значениями другого типа.
Без необходимости знать заранее, какие значения являются допустимыми для имён полей/атрибутов (как это было бы в случае с моделями Pydantic).
-Это было бы полезно, если вы хотите получить ключи, которые вы еще не знаете.
+Это было бы полезно, если вы хотите получить ключи, которые вы ещё не знаете.
---
-Другой полезный случай - когда вы хотите чтобы ключи были другого типа данных, например, `int`.
+Другой полезный случай — когда вы хотите, чтобы ключи были другого типа данных, например, `int`.
Именно это мы сейчас и увидим здесь.
-В этом случае вы принимаете `dict`, пока у него есть ключи типа `int` со значениями типа `float`:
+В этом случае вы принимаете любой `dict`, пока у него есть ключи типа `int` со значениями типа `float`:
{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}
@@ -228,19 +201,19 @@ images: list[Image]
Но Pydantic обеспечивает автоматическое преобразование данных.
-Это значит, что даже если пользователи вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматический преобразует и валидирует эти данные.
+Это значит, что даже если клиенты вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматически преобразует и валидирует эти данные.
-А `dict`, с именем `weights`, который вы получите в качестве ответа Pydantic, действительно будет иметь ключи типа `int` и значения типа `float`.
+А `dict`, который вы получите как `weights`, действительно будет иметь ключи типа `int` и значения типа `float`.
///
-## Резюме
+## Резюме { #recap }
С помощью **FastAPI** вы получаете максимальную гибкость, предоставляемую моделями Pydantic, сохраняя при этом простоту, краткость и элегантность вашего кода.
И дополнительно вы получаете:
-* Поддержку редактора (автодополнение доступно везде!)
+* Поддержку редактора кода (автозавершение доступно везде!)
* Преобразование данных (также известно как парсинг / сериализация)
* Валидацию данных
* Документацию схемы данных
diff --git a/docs/ru/docs/tutorial/body-updates.md b/docs/ru/docs/tutorial/body-updates.md
index 99f475a41..4a7adb255 100644
--- a/docs/ru/docs/tutorial/body-updates.md
+++ b/docs/ru/docs/tutorial/body-updates.md
@@ -1,16 +1,16 @@
-# Body - Обновления
+# Body - Обновления { #body-updates }
-## Полное обновление с помощью `PUT`
+## Обновление с заменой при помощи `PUT` { #update-replacing-with-put }
-Для полного обновления элемента можно воспользоваться операцией HTTP `PUT`.
+Чтобы обновить элемент, вы можете использовать операцию HTTP `PUT`.
-Вы можете использовать функцию `jsonable_encoder` для преобразования входных данных в JSON, так как нередки случаи, когда работать можно только с простыми типами данных (например, для хранения в NoSQL-базе данных).
+Вы можете использовать `jsonable_encoder`, чтобы преобразовать входные данные в данные, которые можно сохранить как JSON (например, в NoSQL-базе данных). Например, преобразование `datetime` в `str`.
{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *}
-`PUT` используется для получения данных, которые должны полностью заменить существующие данные.
+`PUT` используется для получения данных, которые должны заменить существующие данные.
-### Предупреждение о замене
+### Предупреждение о замене { #warning-about-replacing }
Это означает, что если вы хотите обновить элемент `bar`, используя `PUT` с телом, содержащим:
@@ -24,11 +24,11 @@
поскольку оно не включает уже сохраненный атрибут `"tax": 20.2`, входная модель примет значение по умолчанию `"tax": 10.5`.
-И данные будут сохранены с этим "новым" `tax`, равным `10,5`.
+И данные будут сохранены с этим «новым» `tax`, равным `10.5`.
-## Частичное обновление с помощью `PATCH`
+## Частичное обновление с помощью `PATCH` { #partial-updates-with-patch }
-Также можно использовать HTTP `PATCH` операцию для *частичного* обновления данных.
+Также можно использовать операцию HTTP `PATCH` для *частичного* обновления данных.
Это означает, что можно передавать только те данные, которые необходимо обновить, оставляя остальные нетронутыми.
@@ -44,36 +44,38 @@
///
-### Использование параметра `exclude_unset` в Pydantic
+### Использование параметра `exclude_unset` в Pydantic { #using-pydantics-exclude-unset-parameter }
-Если необходимо выполнить частичное обновление, то очень полезно использовать параметр `exclude_unset` в методе `.dict()` модели Pydantic.
+Если вы хотите получать частичные обновления, очень полезно использовать параметр `exclude_unset` в `.model_dump()` модели Pydantic.
-Например, `item.dict(exclude_unset=True)`.
+Например, `item.model_dump(exclude_unset=True)`.
-В результате будет сгенерирован словарь, содержащий только те данные, которые были заданы при создании модели `item`, без учета значений по умолчанию. Затем вы можете использовать это для создания словаря только с теми данными, которые были установлены (отправлены в запросе), опуская значения по умолчанию:
+В результате будет сгенерирован `dict`, содержащий только те данные, которые были заданы при создании модели `item`, без учета значений по умолчанию.
+
+Затем вы можете использовать это для создания `dict` только с теми данными, которые были установлены (отправлены в запросе), опуская значения по умолчанию:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *}
-### Использование параметра `update` в Pydantic
+### Использование параметра `update` в Pydantic { #using-pydantics-update-parameter }
-Теперь можно создать копию существующей модели, используя `.copy()`, и передать параметр `update` с `dict`, содержащим данные для обновления.
+Теперь можно создать копию существующей модели, используя `.model_copy()`, и передать параметр `update` с `dict`, содержащим данные для обновления.
-Например, `stored_item_model.copy(update=update_data)`:
+Например, `stored_item_model.model_copy(update=update_data)`:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
-### Кратко о частичном обновлении
+### Кратко о частичном обновлении { #partial-updates-recap }
В целом, для применения частичных обновлений необходимо:
* (Опционально) использовать `PATCH` вместо `PUT`.
* Извлечь сохранённые данные.
-* Поместить эти данные в Pydantic модель.
+* Поместить эти данные в Pydantic-модель.
* Сгенерировать `dict` без значений по умолчанию из входной модели (с использованием `exclude_unset`).
- * Таким образом, можно обновлять только те значения, которые действительно установлены пользователем, вместо того чтобы переопределять значения, уже сохраненные в модели по умолчанию.
+ * Таким образом, можно обновлять только те значения, которые действительно установлены пользователем, вместо того чтобы переопределять уже сохраненные значения значениями по умолчанию из вашей модели.
* Создать копию хранимой модели, обновив ее атрибуты полученными частичными обновлениями (с помощью параметра `update`).
* Преобразовать скопированную модель в то, что может быть сохранено в вашей БД (например, с помощью `jsonable_encoder`).
- * Это сравнимо с повторным использованием метода модели `.dict()`, но при этом происходит проверка (и преобразование) значений в типы данных, которые могут быть преобразованы в JSON, например, `datetime` в `str`.
+ * Это сравнимо с повторным использованием метода модели `.model_dump()`, но при этом происходит проверка (и преобразование) значений в типы данных, которые могут быть преобразованы в JSON, например, `datetime` в `str`.
* Сохранить данные в своей БД.
* Вернуть обновленную модель.
@@ -81,7 +83,7 @@
/// tip | Подсказка
-Эту же технику можно использовать и для операции HTTP `PUT`.
+На самом деле эту же технику можно использовать и для операции HTTP `PUT`.
Но в приведенном примере используется `PATCH`, поскольку он был создан именно для таких случаев использования.
diff --git a/docs/ru/docs/tutorial/body.md b/docs/ru/docs/tutorial/body.md
index 2c9110226..537d7ebc9 100644
--- a/docs/ru/docs/tutorial/body.md
+++ b/docs/ru/docs/tutorial/body.md
@@ -1,40 +1,41 @@
-# Тело запроса
+# Тело запроса { #request-body }
-Когда вам необходимо отправить данные из клиента (допустим, браузера) в ваш API, вы отправляете их как **тело запроса**.
+Когда вам необходимо отправить данные из клиента (например, браузера) в ваш API, вы отправляете их как **тело запроса**.
-Тело **запроса** --- это данные, отправляемые клиентом в ваш API. Тело **ответа** --- это данные, которые ваш API отправляет клиенту.
+Тело **запроса** — это данные, отправляемые клиентом в ваш API. Тело **ответа** — это данные, которые ваш API отправляет клиенту.
-Ваш API почти всегда отправляет тело **ответа**. Но клиентам не обязательно всегда отправлять тело **запроса**.
+Ваш API почти всегда должен отправлять тело **ответа**. Но клиентам не обязательно всегда отправлять **тело запроса**: иногда они запрашивают только путь, возможно с некоторыми параметрами запроса, но без тела.
-Чтобы объявить тело **запроса**, необходимо использовать модели Pydantic, со всей их мощью и преимуществами.
+Чтобы объявить тело **запроса**, используйте модели Pydantic, со всей их мощью и преимуществами.
/// info | Информация
-Чтобы отправить данные, необходимо использовать один из методов: `POST` (обычно), `PUT`, `DELETE` или `PATCH`.
+Чтобы отправить данные, используйте один из методов: `POST` (чаще всего), `PUT`, `DELETE` или `PATCH`.
-Отправка тела с запросом `GET` имеет неопределенное поведение в спецификациях, тем не менее, оно поддерживается FastAPI только для очень сложных/экстремальных случаев использования.
+Отправка тела с запросом `GET` имеет неопределённое поведение в спецификациях, тем не менее это поддерживается FastAPI, но только для очень сложных/крайних случаев использования.
-Поскольку это не рекомендуется, интерактивная документация со Swagger UI не будет отображать информацию для тела при использовании метода GET, а промежуточные прокси-серверы могут не поддерживать такой вариант запроса.
+Поскольку это не рекомендуется, интерактивная документация со Swagger UI не будет отображать информацию для тела при использовании `GET`, а промежуточные прокси-серверы могут не поддерживать такой вариант запроса.
///
-## Импортирование `BaseModel` из Pydantic
+## Импортируйте `BaseModel` из Pydantic { #import-pydantics-basemodel }
-Первое, что вам необходимо сделать, это импортировать `BaseModel` из пакета `pydantic`:
+Первое, что нужно сделать, — импортировать `BaseModel` из пакета `pydantic`:
-{* ../../docs_src/body/tutorial001.py hl[4] *}
+{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
-## Создание вашей собственной модели
+## Создайте модель данных { #create-your-data-model }
-После этого вы описываете вашу модель данных как класс, наследующий от `BaseModel`.
+Затем опишите свою модель данных как класс, наследующийся от `BaseModel`.
-Используйте аннотации типов Python для всех атрибутов:
+Используйте стандартные типы Python для всех атрибутов:
-{* ../../docs_src/body/tutorial001.py hl[7:11] *}
+{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
-Также как и при описании параметров запроса, когда атрибут модели имеет значение по умолчанию, он является необязательным. Иначе он обязателен. Используйте `None`, чтобы сделать его необязательным без использования конкретных значений по умолчанию.
-Например, модель выше описывает вот такой JSON "объект" (или словарь Python):
+Так же, как при объявлении параметров запроса: когда атрибут модели имеет значение по умолчанию, он не обязателен. Иначе он обязателен. Используйте `None`, чтобы сделать его просто необязательным.
+
+Например, модель выше описывает такой JSON "`object`" (или Python `dict`):
```JSON
{
@@ -45,7 +46,7 @@
}
```
-...поскольку `description` и `tax` являются необязательными (с `None` в качестве значения по умолчанию), вот такой JSON "объект" также подходит:
+...так как `description` и `tax` являются необязательными (со значением по умолчанию `None`), такой JSON "`object`" тоже будет корректным:
```JSON
{
@@ -54,109 +55,112 @@
}
```
-## Объявление как параметра функции
+## Объявите её как параметр { #declare-it-as-a-parameter }
-Чтобы добавить параметр к вашему *обработчику*, объявите его также, как вы объявляли параметры пути или параметры запроса:
+Чтобы добавить её в вашу *операцию пути*, объявите её так же, как вы объявляли параметры пути и параметры запроса:
-{* ../../docs_src/body/tutorial001.py hl[18] *}
+{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
-...и укажите созданную модель в качестве типа параметра, `Item`.
+...и укажите тип параметра как созданную вами модель, `Item`.
-## Результаты
+## Результаты { #results }
-Всего лишь с помощью аннотации типов Python, **FastAPI**:
+Всего лишь с этой аннотацией типов Python **FastAPI**:
-* Читает тело запроса как JSON.
-* Приводит к соответствующим типам (если есть необходимость).
-* Проверяет корректность данных.
- * Если данные некорректны, будет возращена читаемая и понятная ошибка, показывающая что именно и в каком месте некорректно в данных.
-* Складывает полученные данные в параметр `item`.
- * Поскольку внутри функции вы объявили его с типом `Item`, то теперь у вас есть поддержка со стороны редактора (автодополнение и т.п.) для всех атрибутов и их типов.
-* Генерирует декларативное описание модели в виде JSON Schema, так что вы можете его использовать где угодно, если это имеет значение для вашего проекта.
-* Эти схемы являются частью сгенерированной схемы OpenAPI и используются для автоматического документирования UI.
+* Считает тело запроса как JSON.
+* Приведёт данные к соответствующим типам (если потребуется).
+* Проведёт валидацию данных.
+ * Если данные некорректны, вернёт понятную и наглядную ошибку, указывающую, где именно и что было некорректно.
+* Передаст полученные данные в параметр `item`.
+ * Поскольку внутри функции вы объявили его с типом `Item`, у вас будет поддержка со стороны редактора кода (автозавершение и т. п.) для всех атрибутов и их типов.
+* Сгенерирует определения JSON Schema для вашей модели; вы можете использовать их и в других местах, если это имеет смысл для вашего проекта.
+* Эти схемы будут частью сгенерированной схемы OpenAPI и будут использоваться автоматической документацией UIs.
-## Автоматическое документирование
+## Автоматическая документация { #automatic-docs }
-Схема JSON ваших моделей будет частью сгенерированной схемы OpenAPI и будет отображена в интерактивной документации API:
+JSON Schema ваших моделей будет частью сгенерированной схемы OpenAPI и будет отображаться в интерактивной документации API:
-Также она будет указана в документации по API внутри каждой *операции пути*, в которой используются:
+А также они будут использоваться в документации API внутри каждой *операции пути*, где это требуется:
-## Поддержка редактора
+## Поддержка редактора кода { #editor-support }
-В вашем редакторе внутри вашей функции у вас будут подсказки по типам и автодополнение (это не будет работать, если вы получаете словарь вместо модели Pydantic):
+В вашем редакторе кода внутри функции вы получите подсказки по типам и автозавершение повсюду (этого бы не было, если бы вы получали `dict` вместо модели Pydantic):
-Также вы будете получать ошибки в случае несоответствия типов:
+Также вы получите проверку ошибок при некорректных операциях с типами:
-Это не случайно, весь фреймворк построен вокруг такого дизайна.
+Это не случайность — весь фреймворк построен вокруг такого дизайна.
-И это все тщательно протестировано еще на этапе разработки дизайна, до реализации, чтобы это работало со всеми редакторами.
+И это было тщательно протестировано ещё на этапе проектирования, до реализации, чтобы убедиться, что всё будет работать со всеми редакторами.
-Для поддержки этого даже были внесены некоторые изменения в сам Pydantic.
+В сам Pydantic даже были внесены некоторые изменения для поддержки этого.
-На всех предыдущих скриншотах используется Visual Studio Code.
+Предыдущие скриншоты сделаны в Visual Studio Code.
-Но у вас будет такая же поддержка и с PyCharm, и вообще с любым редактором Python:
+Но вы получите такую же поддержку редактора кода в PyCharm и большинстве других редакторов Python:
-/// tip | Подсказка
+/// tip | Совет
-Если вы используете PyCharm в качестве редактора, то вам стоит попробовать плагин Pydantic PyCharm Plugin.
+Если вы используете PyCharm в качестве редактора кода, вы можете использовать плагин Pydantic PyCharm Plugin.
-Он улучшает поддержку редактором моделей Pydantic в части:
+Он улучшает поддержку моделей Pydantic в редакторе кода, включая:
-* автодополнения,
-* проверки типов,
-* рефакторинга,
-* поиска,
-* инспектирования.
+* автозавершение
+* проверки типов
+* рефакторинг
+* поиск
+* инспекции
///
-## Использование модели
+## Использование модели { #use-the-model }
Внутри функции вам доступны все атрибуты объекта модели напрямую:
-{* ../../docs_src/body/tutorial002.py hl[21] *}
+{* ../../docs_src/body/tutorial002_py310.py *}
+
+## Тело запроса + параметры пути { #request-body-path-parameters }
-## Тело запроса + параметры пути
+Вы можете одновременно объявить параметры пути и тело запроса.
-Вы можете одновременно объявлять параметры пути и тело запроса.
+**FastAPI** распознает, что параметры функции, соответствующие параметрам пути, должны быть **получены из пути**, а параметры функции, объявленные как модели Pydantic, должны быть **получены из тела запроса**.
-**FastAPI** распознает, какие параметры функции соответствуют параметрам пути и должны быть **получены из пути**, а какие параметры функции, объявленные как модели Pydantic, должны быть **получены из тела запроса**.
+{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
-{* ../../docs_src/body/tutorial003.py hl[17:18] *}
-## Тело запроса + параметры пути + параметры запроса
+## Тело запроса + параметры пути + параметры запроса { #request-body-path-query-parameters }
-Вы также можете одновременно объявить параметры для **пути**, **запроса** и **тела запроса**.
+Вы также можете одновременно объявить параметры **тела**, **пути** и **запроса**.
-**FastAPI** распознает каждый из них и возьмет данные из правильного источника.
+**FastAPI** распознает каждый из них и возьмёт данные из правильного источника.
-{* ../../docs_src/body/tutorial004.py hl[18] *}
+{* ../../docs_src/body/tutorial004_py310.py hl[16] *}
-Параметры функции распознаются следующим образом:
+Параметры функции будут распознаны следующим образом:
-* Если параметр также указан в **пути**, то он будет использоваться как параметр пути.
-* Если аннотация типа параметра содержит **примитивный тип** (`int`, `float`, `str`, `bool` и т.п.), он будет интерпретирован как параметр **запроса**.
-* Если аннотация типа параметра представляет собой **модель Pydantic**, он будет интерпретирован как параметр **тела запроса**.
+* Если параметр также объявлен в **пути**, он будет использоваться как path-параметр.
+* Если параметр имеет **скалярный тип** (например, `int`, `float`, `str`, `bool` и т. п.), он будет интерпретирован как параметр **запроса**.
+* Если параметр объявлен как тип **модели Pydantic**, он будет интерпретирован как **тело** запроса.
/// note | Заметка
-FastAPI понимает, что значение параметра `q` не является обязательным, потому что имеет значение по умолчанию `= None`.
+FastAPI понимает, что значение `q` не является обязательным из-за значения по умолчанию `= None`.
+
+Аннотации типов `str | None` (Python 3.10+) или `Union` в `Union[str, None]` (Python 3.9+) не используются FastAPI для определения обязательности; он узнает, что параметр не обязателен, потому что у него есть значение по умолчанию `= None`.
-Аннотация `Optional` в `Optional[str]` не используется FastAPI, но помогает вашему редактору лучше понимать ваш код и обнаруживать ошибки.
+Но добавление аннотаций типов позволит вашему редактору кода лучше вас поддерживать и обнаруживать ошибки.
///
-## Без Pydantic
+## Без Pydantic { #without-pydantic }
-Если вы не хотите использовать модели Pydantic, вы все еще можете использовать параметры **тела запроса**. Читайте в документации раздел [Тело - Несколько параметров: Единичные значения в теле](body-multiple-params.md#_2){.internal-link target=_blank}.
+Если вы не хотите использовать модели Pydantic, вы также можете использовать параметры **Body**. См. раздел документации [Тело запроса - Несколько параметров: Единичные значения в теле](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
diff --git a/docs/ru/docs/tutorial/cookie-param-models.md b/docs/ru/docs/tutorial/cookie-param-models.md
index 3a57443bb..182813afd 100644
--- a/docs/ru/docs/tutorial/cookie-param-models.md
+++ b/docs/ru/docs/tutorial/cookie-param-models.md
@@ -1,4 +1,4 @@
-# Модели параметров cookie
+# Модели параметров cookie { #cookie-parameter-models }
Если у вас есть группа **cookies**, которые связаны между собой, вы можете создать **Pydantic-модель** для их объявления. 🍪
@@ -16,7 +16,7 @@
///
-## Pydantic-модель для cookies
+## Pydantic-модель для cookies { #cookies-with-a-pydantic-model }
Объявите параметры **cookie**, которые вам нужны, в **Pydantic-модели**, а затем объявите параметр как `Cookie`:
@@ -24,7 +24,7 @@
**FastAPI** **извлечёт** данные для **каждого поля** из **cookies**, полученных в запросе, и выдаст вам объявленную Pydantic-модель.
-## Проверка сгенерированной документации
+## Проверка сгенерированной документации { #check-the-docs }
Вы можете посмотреть объявленные cookies в графическом интерфейсе Документации по пути `/docs`:
@@ -42,7 +42,7 @@
///
-## Запрет дополнительных cookies
+## Запрет дополнительных cookies { #forbid-extra-cookies }
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** cookies, которые вы хотите получать.
@@ -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**, то в ответ он получит **ошибку**.
@@ -65,12 +65,12 @@
"type": "extra_forbidden",
"loc": ["cookie", "santa_tracker"],
"msg": "Extra inputs are not permitted",
- "input": "good-list-please"
+ "input": "good-list-please",
}
]
}
```
-## Заключение
+## Заключение { #summary }
Вы можете использовать **Pydantic-модели** для объявления **cookies** в **FastAPI**. 😎
diff --git a/docs/ru/docs/tutorial/cookie-params.md b/docs/ru/docs/tutorial/cookie-params.md
index d1ed943d7..2d2eff8d7 100644
--- a/docs/ru/docs/tutorial/cookie-params.md
+++ b/docs/ru/docs/tutorial/cookie-params.md
@@ -1,20 +1,20 @@
-# Параметры Cookie
+# Параметры Cookie { #cookie-parameters }
Вы можете задать параметры Cookie таким же способом, как `Query` и `Path` параметры.
-## Импорт `Cookie`
+## Импорт `Cookie` { #import-cookie }
Сначала импортируйте `Cookie`:
-{* ../../docs_src/cookie_params/tutorial001_py310.py hl[1] *}
+{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
-## Объявление параметров `Cookie`
+## Объявление параметров `Cookie` { #declare-cookie-parameters }
Затем объявляйте параметры cookie, используя ту же структуру, что и с `Path` и `Query`.
-Первое значение - это значение по умолчанию, вы можете передать все дополнительные параметры проверки или аннотации:
+Вы можете задать значение по умолчанию, а также все дополнительные параметры валидации или аннотации:
-{* ../../docs_src/cookie_params/tutorial001_py310.py hl[7] *}
+{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *}
/// note | Технические детали
@@ -30,6 +30,16 @@
///
-## Резюме
+/// info | Дополнительная информация
+
+Имейте в виду, что, поскольку браузеры обрабатывают cookies особым образом и «за кулисами», они не позволяют JavaScript просто так получать к ним доступ.
+
+Если вы откроете интерфейс документации API на `/docs`, вы сможете увидеть документацию по cookies для ваших операций пути.
+
+Но даже если вы заполните данные и нажмёте «Execute», поскольку UI документации работает с JavaScript, cookies отправлены не будут, и вы увидите сообщение об ошибке, как будто вы не указали никаких значений.
+
+///
+
+## Резюме { #recap }
Объявляйте cookies с помощью `Cookie`, используя тот же общий шаблон, что и `Query`, и `Path`.
diff --git a/docs/ru/docs/tutorial/cors.md b/docs/ru/docs/tutorial/cors.md
index e8bf04576..d09a31e2c 100644
--- a/docs/ru/docs/tutorial/cors.md
+++ b/docs/ru/docs/tutorial/cors.md
@@ -1,10 +1,10 @@
-# CORS (Cross-Origin Resource Sharing)
+# CORS (Cross-Origin Resource Sharing) { #cors-cross-origin-resource-sharing }
Понятие 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,69 +12,72 @@
* `https://localhost`
* `http://localhost:8080`
-Даже если они все расположены в `localhost`, они используют разные протоколы и порты, а значит, являются разными источниками.
+Даже если они все расположены в `localhost`, они используют разные протоколы или порты, а значит, являются разными источниками.
-## Шаги
+## Шаги { #steps }
-Допустим, у вас есть фронтенд, запущенный в браузере по адресу `http://localhost:8080`, и его JavaScript-код пытается взаимодействовать с бэкендом, запущенным по адресу `http://localhost` (поскольку мы не указали порт, браузер по умолчанию будет использовать порт `80`).
+Допустим, у вас есть фронтенд, запущенный в браузере по адресу `http://localhost:8080`, и его JavaScript-код пытается взаимодействовать с бэкендом, запущенным по адресу `http://localhost` (поскольку мы не указали порт, браузер по умолчанию будет использовать порт `80`).
-Затем браузер отправит бэкенду HTTP-запрос `OPTIONS`, и если бэкенд вернёт соответствующие заголовки для авторизации взаимодействия с другим источником (`http://localhost:8080`), то браузер разрешит JavaScript-коду на фронтенде отправить запрос на этот бэкенд.
+Затем браузер отправит на бэкенд на `:80` HTTP-запрос `OPTIONS`, и если бэкенд вернёт соответствующие HTTP-заголовки, авторизующие взаимодействие с другим источником (`http://localhost:8080`), то браузер на `:8080` разрешит JavaScript на фронтенде отправить свой запрос на бэкенд на `:80`.
-Чтобы это работало, у бэкенда должен быть список "разрешённых источников" ("allowed origins").
+Чтобы это работало, у бэкенда на `:80` должен быть список "разрешённых источников" ("allowed origins").
-В таком случае этот список должен содержать `http://localhost:8080`, чтобы фронтенд работал корректно.
+В таком случае этот список должен содержать `http://localhost:8080`, чтобы фронтенд на `:8080` работал корректно.
-## Подстановочный символ `"*"`
+## Подстановочный символ "*" { #wildcards }
В качестве списка источников можно указать подстановочный символ `"*"` ("wildcard"), чтобы разрешить любые источники.
-Но тогда не будут разрешены некоторые виды взаимодействия, включая всё связанное с учётными данными: куки, заголовки Authorization с Bearer-токенами наподобие тех, которые мы использовали ранее и т.п.
+Но тогда будут разрешены только некоторые виды взаимодействия, и всё, что связано с учётными данными, будет исключено: куки, HTTP-заголовки Authorization, как при использовании Bearer-токенов, и т.п.
Поэтому, чтобы всё работало корректно, лучше явно указывать список разрешённых источников.
-## Использование `CORSMiddleware`
+## Использование `CORSMiddleware` { #use-corsmiddleware }
-Вы можете настроить этот механизм в вашем **FastAPI** приложении, используя `CORSMiddleware`.
+Вы можете настроить это в вашем **FastAPI**-приложении, используя `CORSMiddleware`.
* Импортируйте `CORSMiddleware`.
* Создайте список разрешённых источников (в виде строк).
-* Добавьте его как "middleware" к вашему **FastAPI** приложению.
+* Добавьте его как "middleware" (промежуточный слой) к вашему **FastAPI**-приложению.
Вы также можете указать, разрешает ли ваш бэкенд использование:
-* Учётных данных (включая заголовки Authorization, куки и т.п.).
+* Учётных данных (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` использует "запрещающие" значения по умолчанию, поэтому вам нужно явным образом разрешить использование отдельных источников, методов или заголовков, чтобы браузеры могли использовать их в кросс-доменном контексте.
Поддерживаются следующие аргументы:
* `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`.
+* `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`, `allow_methods` и `allow_headers` не может быть установлен в `['*']`, если `allow_credentials` имеет значение `True`. Все они должны быть указаны явно.
+
+* `expose_headers` - Указывает любые заголовки ответа, которые должны быть доступны браузеру. По умолчанию `[]`.
+* `max_age` - Устанавливает максимальное время в секундах, в течение которого браузер кэширует CORS-ответы. По умолчанию `600`.
`CORSMiddleware` отвечает на два типа HTTP-запросов...
-### CORS-запросы с предварительной проверкой
+### CORS-запросы с предварительной проверкой { #cors-preflight-requests }
-Это любые `OPTIONS` запросы с заголовками `Origin` и `Access-Control-Request-Method`.
+Это любые `OPTIONS`-запросы с заголовками `Origin` и `Access-Control-Request-Method`.
В этом случае middleware перехватит входящий запрос и отправит соответствующие CORS-заголовки в ответе, а также ответ `200` или `400` в информационных целях.
-### Простые запросы
+### Простые запросы { #simple-requests }
Любые запросы с заголовком `Origin`. В этом случае middleware передаст запрос дальше как обычно, но добавит соответствующие CORS-заголовки к ответу.
-## Больше информации
+## Больше информации { #more-info }
-Для получения более подробной информации о CORS, обратитесь к Документации CORS от Mozilla.
+Для получения более подробной информации о CORS обратитесь к документации CORS от Mozilla.
/// note | Технические детали
diff --git a/docs/ru/docs/tutorial/debugging.md b/docs/ru/docs/tutorial/debugging.md
index 05806f087..51955835e 100644
--- a/docs/ru/docs/tutorial/debugging.md
+++ b/docs/ru/docs/tutorial/debugging.md
@@ -1,14 +1,14 @@
-# Отладка
+# Отладка { #debugging }
Вы можете подключить отладчик в своем редакторе, например, в Visual Studio Code или PyCharm.
-## Вызов `uvicorn`
+## Вызов `uvicorn` { #call-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`.
@@ -78,7 +78,7 @@ from myapp import app
///
-## Запуск вашего кода с помощью отладчика
+## Запуск вашего кода с помощью отладчика { #run-your-code-with-your-debugger }
Так как вы запускаете сервер Uvicorn непосредственно из вашего кода, вы можете вызвать Python программу (ваше FastAPI приложение) напрямую из отладчика.
diff --git a/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md
index 8037872b9..a38e885d4 100644
--- a/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md
+++ b/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md
@@ -1,28 +1,30 @@
-# Классы как зависимости
+# Классы как зависимости { #classes-as-dependencies }
Прежде чем углубиться в систему **Внедрения Зависимостей**, давайте обновим предыдущий пример.
-## `Словарь` из предыдущего примера
+## `dict` из предыдущего примера { #a-dict-from-the-previous-example }
-В предыдущем примере мы возвращали `словарь` из нашей зависимости:
+В предыдущем примере мы возвращали `dict` из нашей зависимости:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *}
-Но затем мы получаем `словарь` в параметре `commons` *функции операции пути*. И мы знаем, что редакторы не могут обеспечить достаточную поддержку для `словаря`, поскольку они не могут знать их ключи и типы значений.
+Но затем мы получаем `dict` в параметре `commons` *функции-обработчика пути*.
+
+И мы знаем, что редакторы кода не могут обеспечить достаточную поддержку (например, автозавершение) для `dict`, поскольку они не могут знать их ключи и типы значений.
Мы можем сделать лучше...
-## Что делает зависимость
+## Что делает зависимость { #what-makes-a-dependency }
До сих пор вы видели зависимости, объявленные как функции.
-Но это не единственный способ объявления зависимостей (хотя, вероятно, более распространенный).
+Но это не единственный способ объявления зависимостей (хотя он, вероятно, более распространенный).
-Ключевым фактором является то, что зависимость должна быть "вызываемой".
+Ключевым фактором является то, что зависимость должна быть «вызываемой».
-В Python "**вызываемый**" - это все, что Python может "вызвать", как функцию.
+В Python «**вызываемый**» — это всё, что Python может «вызвать», как функцию.
-Так, если у вас есть объект `something` (который может _не_ быть функцией) и вы можете "вызвать" его (выполнить) как:
+Так, если у вас есть объект `something` (который может и _не_ быть функцией) и вы можете «вызвать» его (выполнить) так:
```Python
something()
@@ -34,9 +36,9 @@ something()
something(some_argument, some_keyword_argument="foo")
```
-в таком случае он является "вызываемым".
+в таком случае он является «вызываемым».
-## Классы как зависимости
+## Классы как зависимости { #classes-as-dependencies_1 }
Вы можете заметить, что для создания экземпляра класса в Python используется тот же синтаксис.
@@ -53,17 +55,17 @@ fluffy = Cat(name="Mr Fluffy")
В данном случае `fluffy` является экземпляром класса `Cat`.
-А чтобы создать `fluffy`, вы "вызываете" `Cat`.
+А чтобы создать `fluffy`, вы «вызываете» `Cat`.
Таким образом, класс в Python также является **вызываемым**.
Тогда в **FastAPI** в качестве зависимости можно использовать класс Python.
-На самом деле FastAPI проверяет, что переданный объект является "вызываемым" (функция, класс или что-либо еще) и указаны необходимые для его вызова параметры.
+На самом деле FastAPI проверяет, что переданный объект является «вызываемым» (функция, класс или что-либо еще) и какие параметры у него определены.
-Если вы передаёте что-то, что можно "вызывать" в качестве зависимости в **FastAPI**, то он будет анализировать параметры, необходимые для "вызова" этого объекта и обрабатывать их так же, как параметры *функции операции пути*. Включая подзависимости.
+Если вы передаёте «вызываемый» объект в качестве зависимости в **FastAPI**, он проанализирует параметры, необходимые для этого «вызываемого» объекта, и обработает их так же, как параметры *функции-обработчика пути*. Включая подзависимости.
-Это относится и к вызываемым объектам без параметров. Работа с ними происходит точно так же, как и для *функций операции пути* без параметров.
+Это относится и к вызываемым объектам без параметров. Работа с ними происходит точно так же, как и для *функций-обработчиков пути* без параметров.
Теперь мы можем изменить зависимость `common_parameters`, указанную выше, на класс `CommonQueryParams`:
@@ -73,55 +75,55 @@ fluffy = Cat(name="Mr Fluffy")
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *}
-...имеет те же параметры, что и ранее используемая функция `common_parameters`:
+...он имеет те же параметры, что и ранее используемая функция `common_parameters`:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *}
-Эти параметры и будут использоваться **FastAPI** для "решения" зависимости.
+Эти параметры и будут использоваться **FastAPI** для «решения» зависимости.
В обоих случаях она будет иметь:
* Необязательный параметр запроса `q`, представляющий собой `str`.
* Параметр запроса `skip`, представляющий собой `int`, по умолчанию `0`.
-* Параметр запроса `limit`, представляющий собой `int`, по умолчанию равный `100`.
+* Параметр запроса `limit`, представляющий собой `int`, по умолчанию `100`.
-В обоих случаях данные будут конвертированы, валидированы, документированы по схеме OpenAPI и т.д.
+В обоих случаях данные будут конвертированы, валидированы, задокументированы в схеме OpenAPI и т.д.
-## Как это использовать
+## Как это использовать { #use-it }
Теперь вы можете объявить свою зависимость, используя этот класс.
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *}
-**FastAPI** вызывает класс `CommonQueryParams`. При этом создается "экземпляр" этого класса, который будет передан в качестве параметра `commons` в вашу функцию.
+**FastAPI** вызывает класс `CommonQueryParams`. При этом создается «экземпляр» этого класса, который будет передан в качестве параметра `commons` в вашу функцию.
-## Аннотация типа или `Depends`
+## Аннотация типа и `Depends` { #type-annotation-vs-depends }
Обратите внимание, что в приведенном выше коде мы два раза пишем `CommonQueryParams`:
-//// tab | Python 3.6+ без Annotated
-
-/// tip | Подсказка
-
-Рекомендуется использовать версию с `Annotated` если возможно.
-
-///
+//// tab | Python 3.9+
```Python
-commons: CommonQueryParams = Depends(CommonQueryParams)
+commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
-//// tab | Python 3.6+
+//// tab | Python 3.9+ non-Annotated
+
+/// tip | Подсказка
+
+Рекомендуется использовать версию с `Annotated`, если возможно.
+
+///
```Python
-commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
+commons: CommonQueryParams = Depends(CommonQueryParams)
```
////
-Последний параметр `CommonQueryParams`, в:
+Последний `CommonQueryParams`, в:
```Python
... Depends(CommonQueryParams)
@@ -129,13 +131,13 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
...это то, что **FastAPI** будет использовать, чтобы узнать, что является зависимостью.
-Из него FastAPI извлечёт объявленные параметры и именно их будет вызывать.
+Из него FastAPI извлечёт объявленные параметры, и именно его FastAPI будет вызывать.
---
В этом случае первый `CommonQueryParams`, в:
-//// tab | Python 3.6+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, ...
@@ -143,11 +145,11 @@ commons: Annotated[CommonQueryParams, ...
////
-//// tab | Python 3.6+ без Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Подсказка
-Рекомендуется использовать версию с `Annotated` если возможно.
+Рекомендуется использовать версию с `Annotated`, если возможно.
///
@@ -161,7 +163,7 @@ commons: CommonQueryParams ...
На самом деле можно написать просто:
-//// tab | Python 3.6+
+//// tab | Python 3.9+
```Python
commons: Annotated[Any, Depends(CommonQueryParams)]
@@ -169,11 +171,11 @@ commons: Annotated[Any, Depends(CommonQueryParams)]
////
-//// tab | Python 3.6+ без Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Подсказка
-Рекомендуется использовать версию с `Annotated` если возможно.
+Рекомендуется использовать версию с `Annotated`, если возможно.
///
@@ -187,42 +189,43 @@ commons = Depends(CommonQueryParams)
{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *}
-Но объявление типа приветствуется, так как в этом случае ваш редактор будет знать, что будет передано в качестве параметра `commons`, и тогда он сможет помочь вам с автодополнением, проверкой типов и т.д:
+Но объявление типа приветствуется, так как в этом случае ваш редактор кода будет знать, что будет передано в качестве параметра `commons`, и тогда он сможет помочь вам с автозавершением, проверкой типов и т.д.:
-## Сокращение
+## Сокращение { #shortcut }
Но вы видите, что здесь мы имеем некоторое повторение кода, дважды написав `CommonQueryParams`:
-//// tab | Python 3.6+ без Annotated
-
-/// tip | Подсказка
-
-Рекомендуется использовать версию с `Annotated` если возможно.
-
-///
+//// tab | Python 3.9+
```Python
-commons: CommonQueryParams = Depends(CommonQueryParams)
+commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
-//// tab | Python 3.6+
+//// tab | Python 3.9+ non-Annotated
+
+/// tip | Подсказка
+
+Рекомендуется использовать версию с `Annotated`, если возможно.
+
+///
```Python
-commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
+commons: CommonQueryParams = Depends(CommonQueryParams)
```
////
-Для случаев, когда зависимостью является *конкретный* класс, который **FastAPI** "вызовет" для создания экземпляра этого класса, можно использовать укороченную запись.
+**FastAPI** предоставляет сокращение для таких случаев, когда зависимость — это *конкретный* класс, который **FastAPI** будет «вызывать» для создания экземпляра этого класса.
+Для этих конкретных случаев вы можете сделать следующее.
Вместо того чтобы писать:
-//// tab | Python 3.6+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -230,11 +233,11 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
-//// tab | Python 3.6+ без Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Подсказка
-Рекомендуется использовать версию с `Annotated` если возможно.
+Рекомендуется использовать версию с `Annotated`, если возможно.
///
@@ -246,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
...следует написать:
-//// tab | Python 3.6+
+//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends()]
@@ -254,11 +257,11 @@ commons: Annotated[CommonQueryParams, Depends()]
////
-//// tab | Python 3.6 без Annotated
+//// tab | Python 3.9+ non-Annotated
/// tip | Подсказка
-Рекомендуется использовать версию с `Annotated` если возможно.
+Рекомендуется использовать версию с `Annotated`, если возможно.
///
@@ -278,7 +281,7 @@ commons: CommonQueryParams = Depends()
/// tip | Подсказка
-Если это покажется вам более запутанным, чем полезным, не обращайте внимания, это вам не *нужно*.
+Если это покажется вам более запутанным, чем полезным, не обращайте внимания — это вам не *нужно*.
Это просто сокращение. Потому что **FastAPI** заботится о том, чтобы помочь вам свести к минимуму повторение кода.
diff --git a/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
index 0e4eb95be..ef5664448 100644
--- a/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
+++ b/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
@@ -1,4 +1,4 @@
-# Зависимости в декораторах операции пути
+# Зависимости в декораторах операции пути { #dependencies-in-path-operation-decorators }
В некоторых случаях, возвращаемое значение зависимости не используется внутри *функции операции пути*.
@@ -8,7 +8,7 @@
Для таких ситуаций, вместо объявления *функции операции пути* с параметром `Depends`, вы можете добавить список зависимостей `dependencies` в *декоратор операции пути*.
-## Добавление `dependencies` в *декоратор операции пути*
+## Добавление `dependencies` (зависимостей) в *декоратор операции пути* { #add-dependencies-to-the-path-operation-decorator }
*Декоратор операции пути* получает необязательный аргумент `dependencies`.
@@ -36,23 +36,23 @@
///
-## Исключения в dependencies и возвращаемые значения
+## Исключения в Зависимостях и возвращаемые значения { #dependencies-errors-and-return-values }
Вы можете использовать те же *функции* зависимостей, что и обычно.
-### Требования к зависимостям
+### Требования к зависимостям { #dependency-requirements }
Они могут объявлять требования к запросу (например заголовки) или другие подзависимости:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *}
-### Вызов исключений
+### Вызов исключений { #raise-exceptions }
Зависимости из dependencies могут вызывать исключения с помощью `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 для группы *операций путей*
+## Зависимости для группы *операций путей* { #dependencies-for-a-group-of-path-operations }
-Позже, читая о том как структурировать большие приложения ([Bigger Applications - Multiple Files](../../tutorial/bigger-applications.md){.internal-link target=_blank}), возможно, многофайловые, вы узнаете как объявить единый параметр `dependencies` для всей группы *операций путей*.
+Позже, читая о том как структурировать большие приложения ([Большие приложения — несколько файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}), возможно, многофайловые, вы узнаете как объявить единый параметр `dependencies` для всей группы *операций путей*.
-## Глобальный Dependencies
+## Глобальные Зависимости { #global-dependencies }
Далее мы увидим, как можно добавить dependencies для всего `FastAPI` приложения, так чтобы они применялись к каждой *операции пути*.
diff --git a/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md
index e64f6777c..dc202db61 100644
--- a/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md
+++ b/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md
@@ -1,125 +1,139 @@
-# Зависимости с yield
+# Зависимости с yield { #dependencies-with-yield }
-FastAPI поддерживает зависимости, которые выполняют некоторые дополнительные действия после завершения работы.
+FastAPI поддерживает зависимости, которые выполняют некоторые дополнительные шаги после завершения.
-Для этого используйте `yield` вместо `return`, а дополнительный код напишите после него.
+Для этого используйте `yield` вместо `return`, а дополнительные шаги (код) напишите после него.
/// tip | Подсказка
-Обязательно используйте `yield` один-единственный раз.
+Убедитесь, что используете `yield` только один раз на одну зависимость.
///
/// note | Технические детали
-Любая функция, с которой может работать:
+Любая функция, с которой можно корректно использовать:
* `@contextlib.contextmanager` или
* `@contextlib.asynccontextmanager`
-будет корректно использоваться в качестве **FastAPI**-зависимости.
+будет корректной для использования в качестве зависимости **FastAPI**.
-На самом деле, FastAPI использует эту пару декораторов "под капотом".
+На самом деле, FastAPI использует эти два декоратора внутренне.
///
-## Зависимость базы данных с помощью `yield`
+## Зависимость базы данных с помощью `yield` { #a-database-dependency-with-yield }
-Например, с его помощью можно создать сессию работы с базой данных и закрыть его после завершения.
+Например, с его помощью можно создать сессию работы с базой данных и закрыть её после завершения.
-Перед созданием ответа будет выполнен только код до и включая `yield`.
+Перед созданием ответа будет выполнен только код до и включая оператор `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`, выполняется после доставки ответа:
+Код, следующий за оператором `yield`, выполняется после ответа:
-{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *}
/// tip | Подсказка
-Можно использовать как `async` так и обычные функции.
+Можно использовать как `async`, так и обычные функции.
-**FastAPI** это корректно обработает, и в обоих случаях будет делать то же самое, что и с обычными зависимостями.
+**FastAPI** корректно обработает каждый вариант, так же как и с обычными зависимостями.
///
-## Зависимость с `yield` и `try` одновременно
+## Зависимость с `yield` и `try` { #a-dependency-with-yield-and-try }
-Если использовать блок `try` в зависимости с `yield`, то будет получено всякое исключение, которое было выброшено при использовании зависимости.
+Если использовать блок `try` в зависимости с `yield`, то вы получите любое исключение, которое было выброшено при использовании зависимости.
-Например, если какой-то код в какой-то момент в середине, в другой зависимости или в *функции операции пути*, сделал "откат" транзакции базы данных или создал любую другую ошибку, то вы получите исключение в своей зависимости.
+Например, если какой-то код в какой-то момент в середине, в другой зависимости или в *операции пути*, сделал "откат" транзакции базы данных или создал любую другую ошибку, то вы получите это исключение в своей зависимости.
Таким образом, можно искать конкретное исключение внутри зависимости с помощью `except SomeException`.
-Таким же образом можно использовать `finally`, чтобы убедиться, что обязательные шаги при выходе выполнены, независимо от того, было ли исключение или нет.
+Точно так же можно использовать `finally`, чтобы убедиться, что обязательные шаги при выходе выполнены независимо от того, было ли исключение или нет.
-{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
+{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *}
-## Подзависимости с `yield`
+## Подзависимости с `yield` { #sub-dependencies-with-yield }
Вы можете иметь подзависимости и "деревья" подзависимостей любого размера и формы, и любая из них или все они могут использовать `yield`.
-**FastAPI** будет следить за тем, чтобы "код по выходу" в каждой зависимости с `yield` выполнялся в правильном порядке.
+**FastAPI** проследит за тем, чтобы «код выхода» в каждой зависимости с `yield` выполнялся в правильном порядке.
-Например, `dependency_c` может иметь зависимость от `dependency_b`, а `dependency_b` от `dependency_a`:
+Например, `dependency_c` может зависеть от `dependency_b`, а `dependency_b` — от `dependency_a`:
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *}
И все они могут использовать `yield`.
-В этом случае `dependency_c` для выполнения своего кода выхода нуждается в том, чтобы значение из `dependency_b` (здесь `dep_b`) было еще доступно.
+В этом случае `dependency_c` для выполнения своего кода выхода нуждается в том, чтобы значение из `dependency_b` (здесь `dep_b`) всё ещё было доступно.
-И, в свою очередь, `dependency_b` нуждается в том, чтобы значение из `dependency_a` (здесь `dep_a`) было доступно для ее завершающего кода.
+И, в свою очередь, `dependency_b` нуждается в том, чтобы значение из `dependency_a` (здесь `dep_a`) было доступно для её кода выхода.
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
-Точно так же можно иметь часть зависимостей с `yield`, часть с `return`, и какие-то из них могут зависеть друг от друга.
+Точно так же можно иметь часть зависимостей с `yield`, часть — с `return`, и какие-то из них могут зависеть друг от друга.
Либо у вас может быть одна зависимость, которая требует несколько других зависимостей с `yield` и т.д.
-Комбинации зависимостей могут быть какими вам угодно.
+Комбинации зависимостей могут быть какими угодно.
-**FastAPI** проследит за тем, чтобы все выполнялось в правильном порядке.
+**FastAPI** проследит за тем, чтобы всё выполнялось в правильном порядке.
/// note | Технические детали
-Это работает благодаря Контекстным менеджерам в Python.
+Это работает благодаря менеджерам контекста в Python.
+
+**FastAPI** использует их внутренне для достижения этого.
///
- **FastAPI** использует их "под капотом" с этой целью.
+## Зависимости с `yield` и `HTTPException` { #dependencies-with-yield-and-httpexception }
-## Зависимости с `yield` и `HTTPException`
+Вы видели, что можно использовать зависимости с `yield` и иметь блоки `try`, которые пытаются выполнить некоторый код, а затем запускают код выхода в `finally`.
-Вы видели, что можно использовать зависимости с `yield` совместно с блоком `try`, отлавливающие исключения.
+Также вы можете использовать `except`, чтобы поймать вызванное исключение и что-то с ним сделать.
-Таким же образом вы можете поднять исключение `HTTPException` или что-то подобное в завершающем коде, после `yield`.
+Например, вы можете вызвать другое исключение, например `HTTPException`.
-Код выхода в зависимостях с `yield` выполняется *после* отправки ответа, поэтому [Обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} уже будет запущен. В коде выхода (после `yield`) нет ничего, перехватывающего исключения, брошенные вашими зависимостями.
+/// tip | Подсказка
-Таким образом, если после `yield` возникает `HTTPException`, то стандартный (или любой пользовательский) обработчик исключений, который перехватывает `HTTPException` и возвращает ответ HTTP 400, уже не сможет перехватить это исключение.
+Это довольно продвинутая техника, и в большинстве случаев она вам не понадобится, так как вы можете вызывать исключения (включая `HTTPException`) в остальном коде вашего приложения, например, в *функции-обработчике пути*.
-Благодаря этому все, что установлено в зависимости (например, сеанс работы с БД), может быть использовано, например, фоновыми задачами.
+Но если понадобится — возможность есть. 🤓
-Фоновые задачи выполняются *после* отправки ответа. Поэтому нет возможности поднять `HTTPException`, так как нет даже возможности изменить уже отправленный ответ.
+///
-Но если фоновая задача создает ошибку в БД, то, по крайней мере, можно сделать откат или чисто закрыть сессию в зависимости с помощью `yield`, а также, возможно, занести ошибку в журнал или сообщить о ней в удаленную систему отслеживания.
+{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *}
-Если у вас есть код, который, как вы знаете, может вызвать исключение, сделайте самую обычную/"питонячью" вещь и добавьте блок `try` в этот участок кода.
+Если вы хотите перехватывать исключения и формировать на их основе пользовательский ответ, создайте [Пользовательский обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
-Если у вас есть пользовательские исключения, которые вы хотите обрабатывать *до* возврата ответа и, возможно, модифицировать ответ, даже вызывая `HTTPException`, создайте [Cобственный обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
+## Зависимости с `yield` и `except` { #dependencies-with-yield-and-except }
-/// tip | Подсказка
+Если вы ловите исключение с помощью `except` в зависимости с `yield` и не вызываете его снова (или не вызываете новое исключение), FastAPI не сможет заметить, что было исключение — так же, как это происходит в обычном Python:
-Вы все еще можете вызывать исключения, включая `HTTPException`, *до* `yield`. Но не после.
+{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *}
-///
+В этом случае клиент получит *HTTP 500 Internal Server Error*, как и должно быть, поскольку мы не вызываем `HTTPException` или что-то подобное, но на сервере **не будет никаких логов** или других указаний на то, какая была ошибка. 😱
+
+### Всегда делайте `raise` в зависимостях с `yield` и `except` { #always-raise-in-dependencies-with-yield-and-except }
+
+Если вы ловите исключение в зависимости с `yield`, то, если вы не вызываете другой `HTTPException` или что-то подобное, вам следует повторно вызвать исходное исключение.
-Последовательность выполнения примерно такая, как на этой схеме. Время течет сверху вниз. А каждый столбец - это одна из частей, взаимодействующих с кодом или выполняющих код.
+Вы можете повторно вызвать то же самое исключение с помощью `raise`:
+
+{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *}
+
+Теперь клиент получит тот же *HTTP 500 Internal Server Error*, но на сервере в логах будет наше пользовательское `InternalError`. 😎
+
+## Выполнение зависимостей с `yield` { #execution-of-dependencies-with-yield }
+
+Последовательность выполнения примерно такая, как на этой схеме. Время течёт сверху вниз. А каждый столбец — это одна из частей, взаимодействующих с кодом или выполняющих код.
```mermaid
sequenceDiagram
@@ -130,85 +144,101 @@ 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
+ Note over client,operation: Can raise exceptions, including HTTPException
client ->> dep: Start request
Note over dep: Run code up to yield
- opt raise
- dep -->> handler: Raise HTTPException
+ opt raise Exception
+ dep -->> handler: Raise Exception
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
+ operation -->> dep: Raise Exception (e.g. HTTPException)
+ opt handle
+ dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception
+ end
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.
+ tasks -->> tasks: Handle exceptions in the background task code
end
```
/// info | Дополнительная информация
-Клиенту будет отправлен только **один ответ**. Это может быть один из ответов об ошибке или это будет ответ от *операции пути*.
+Клиенту будет отправлен только **один ответ**. Это может быть один из ответов об ошибке или ответ от *операции пути*.
-После отправки одного из этих ответов никакой другой ответ не может быть отправлен.
+После отправки одного из этих ответов никакой другой ответ отправить нельзя.
///
/// tip | Подсказка
-На этой диаграмме показано "HttpException", но вы также можете вызвать любое другое исключение, для которого вы создаете [Пользовательский обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
-
-Если вы создадите какое-либо исключение, оно будет передано зависимостям с yield, включая `HttpException`, а затем **снова** обработчикам исключений. Если для этого исключения нет обработчика исключений, то оно будет обработано внутренним "ServerErrorMiddleware" по умолчанию, возвращающим код состояния HTTP 500, чтобы уведомить клиента, что на сервере произошла ошибка.
+Если вы вызовете какое-либо исключение в коде из *функции-обработчика пути*, оно будет передано зависимостям с `yield`, включая `HTTPException`. В большинстве случаев вы захотите повторно вызвать то же самое исключение или новое из зависимости с `yield`, чтобы убедиться, что оно корректно обработано.
///
-## Зависимости с `yield`, `HTTPException` и фоновыми задачами
+## Ранний выход и `scope` { #early-exit-and-scope }
-/// warning | Внимание
+Обычно «код выхода» зависимостей с `yield` выполняется **после того, как ответ** отправлен клиенту.
-Скорее всего, вам не нужны эти технические подробности, вы можете пропустить этот раздел и продолжить ниже.
+Но если вы знаете, что не будете использовать зависимость после возврата из *функции-обработчика пути*, вы можете использовать `Depends(scope="function")`, чтобы сообщить FastAPI, что он должен закрыть зависимость после возврата из *функции-обработчика пути*, но **до того**, как **ответ будет отправлен**.
-Эти подробности полезны, главным образом, если вы использовали версию FastAPI до 0.106.0 и использовали ресурсы из зависимостей с `yield` в фоновых задачах.
+{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *}
-///
+`Depends()` принимает параметр `scope`, который может быть:
-До версии FastAPI 0.106.0 вызывать исключения после `yield` было невозможно, код выхода в зависимостях с `yield` выполнялся *после* отправки ответа, поэтому [Обработчик Ошибок](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} уже был бы запущен.
+* `"function"`: начать зависимость до *функции-обработчика пути*, которая обрабатывает запрос, завершить зависимость после окончания *функции-обработчика пути*, но **до того**, как ответ будет отправлен обратно клиенту. То есть функция зависимости будет выполнена **вокруг** *функции-обработчика пути*.
+* `"request"`: начать зависимость до *функции-обработчика пути*, которая обрабатывает запрос (как и при использовании `"function"`), но завершить **после** того, как ответ будет отправлен обратно клиенту. То есть функция зависимости будет выполнена **вокруг** цикла запроса (**request**) и ответа.
-Это было сделано главным образом для того, чтобы позволить использовать те же объекты, "отданные" зависимостями, внутри фоновых задач, поскольку код выхода будет выполняться после завершения фоновых задач.
+Если не указано и в зависимости есть `yield`, по умолчанию будет `scope` со значением `"request"`.
-Тем не менее, поскольку это означало бы ожидание ответа в сети, а также ненужное удержание ресурса в зависимости от доходности (например, соединение с базой данных), это было изменено в FastAPI 0.106.0.
+### `scope` для подзависимостей { #scope-for-sub-dependencies }
-/// tip | Подсказка
+Когда вы объявляете зависимость с `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`.
+## Зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами { #dependencies-with-yield-httpexception-except-and-background-tasks }
-Например, вместо того чтобы использовать ту же сессию базы данных, вы создадите новую сессию базы данных внутри фоновой задачи и будете получать объекты из базы данных с помощью этой новой сессии. А затем, вместо того чтобы передавать объект из базы данных в качестве параметра в функцию фоновой задачи, вы передадите идентификатор этого объекта, а затем снова получите объект в функции фоновой задачи.
+Зависимости с `yield` со временем эволюционировали, чтобы покрыть разные сценарии и исправить некоторые проблемы.
-## Контекстные менеджеры
+Если вы хотите посмотреть, что менялось в разных версиях FastAPI, вы можете прочитать об этом подробнее в продвинутом руководстве: [Продвинутые зависимости — зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}.
+## Контекстные менеджеры { #context-managers }
-### Что такое "контекстные менеджеры"
+### Что такое «контекстные менеджеры» { #what-are-context-managers }
-"Контекстные менеджеры" - это любые объекты Python, которые можно использовать в операторе `with`.
+«Контекстные менеджеры» — это любые объекты Python, которые можно использовать в операторе `with`.
Например, можно использовать `with` для чтения файла:
@@ -218,41 +248,41 @@ with open("./somefile.txt") as f:
print(contents)
```
-Под капотом" open("./somefile.txt") создаёт объект называемый "контекстным менеджером".
+Под капотом вызов `open("./somefile.txt")` создаёт объект, называемый «контекстным менеджером».
Когда блок `with` завершается, он обязательно закрывает файл, даже если были исключения.
-Когда вы создаете зависимость с помощью `yield`, **FastAPI** внутренне преобразует ее в контекстный менеджер и объединяет с некоторыми другими связанными инструментами.
+Когда вы создаёте зависимость с `yield`, **FastAPI** внутренне создаёт для неё менеджер контекста и сочетает его с некоторыми другими связанными инструментами.
-### Использование менеджеров контекста в зависимостях с помощью `yield`
+### Использование менеджеров контекста в зависимостях с `yield` { #using-context-managers-in-dependencies-with-yield }
/// warning | Внимание
-Это более или менее "продвинутая" идея.
+Это, более или менее, «продвинутая» идея.
Если вы только начинаете работать с **FastAPI**, то лучше пока пропустить этот пункт.
///
-В Python для создания менеджеров контекста можно создать класс с двумя методами: `__enter__()` и `__exit__()`.
+В Python можно создавать менеджеры контекста, создав класс с двумя методами: `__enter__()` и `__exit__()`.
-Вы также можете использовать их внутри зависимостей **FastAPI** с `yield`, используя операторы
+Их также можно использовать внутри зависимостей **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 | Подсказка
-Другой способ создания контекстного менеджера - с помощью:
+Другой способ создания менеджера контекста — с помощью:
* `@contextlib.contextmanager` или
* `@contextlib.asynccontextmanager`
-используйте их для оформления функции с одним `yield`.
+оформив ими функцию с одним `yield`.
-Это то, что **FastAPI** использует внутри себя для зависимостей с `yield`.
+Именно это **FastAPI** использует внутренне для зависимостей с `yield`.
-Но использовать декораторы для зависимостей FastAPI не обязательно (да и не стоит).
+Но использовать эти декораторы для зависимостей FastAPI не обязательно (и не стоит).
FastAPI сделает это за вас на внутреннем уровне.
diff --git a/docs/ru/docs/tutorial/dependencies/global-dependencies.md b/docs/ru/docs/tutorial/dependencies/global-dependencies.md
index 5d2e70f6e..2347c6dd8 100644
--- a/docs/ru/docs/tutorial/dependencies/global-dependencies.md
+++ b/docs/ru/docs/tutorial/dependencies/global-dependencies.md
@@ -1,15 +1,15 @@
-# Глобальные зависимости
+# Глобальные зависимости { #global-dependencies }
Для некоторых типов приложений может потребоваться добавить зависимости ко всему приложению.
-Подобно тому, как вы можете [добавлять зависимости через параметр `dependencies` в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, вы можете добавлять зависимости сразу ко всему `FastAPI` приложению.
+Подобно тому, как вы можете [добавлять `dependencies` (зависимости) в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, вы можете добавлять зависимости сразу ко всему `FastAPI` приложению.
В этом случае они будут применяться ко всем *операциям пути* в приложении:
-{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[16] *}
+{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[17] *}
-Все способы [добавления зависимостей в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} по-прежнему применимы, но в данном случае зависимости применяются ко всем *операциям пути* приложения.
+Все способы [добавления `dependencies` (зависимостей) в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} по-прежнему применимы, но в данном случае зависимости применяются ко всем *операциям пути* приложения.
-## Зависимости для групп *операций пути*
+## Зависимости для групп *операций пути* { #dependencies-for-groups-of-path-operations }
-Позднее, читая о том, как структурировать более крупные [приложения, содержащие много файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}, вы узнаете, как объявить один параметр dependencies для целой группы *операций пути*.
+Позднее, читая о том, как структурировать более крупные [приложения, содержащие много файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}, вы узнаете, как объявить один параметр `dependencies` для целой группы *операций пути*.
diff --git a/docs/ru/docs/tutorial/dependencies/index.md b/docs/ru/docs/tutorial/dependencies/index.md
index 28790bd5a..98b0d59c6 100644
--- a/docs/ru/docs/tutorial/dependencies/index.md
+++ b/docs/ru/docs/tutorial/dependencies/index.md
@@ -1,91 +1,97 @@
-# Зависимости
+# Зависимости { #dependencies }
-**FastAPI** имеет очень мощную и интуитивную систему **Dependency Injection**.
+**FastAPI** имеет очень мощную, но интуитивную систему **Инъекция зависимостей**.
-Она проектировалась таким образом, чтобы быть простой в использовании и облегчить любому разработчику интеграцию других компонентов с **FastAPI**.
+Она спроектирована так, чтобы быть очень простой в использовании и облегчать любому разработчику интеграцию других компонентов с **FastAPI**.
-## Что такое "Dependency Injection" (инъекция зависимости)
+## Что такое инъекция зависимостей («Dependency Injection») { #what-is-dependency-injection }
-**"Dependency Injection"** в программировании означает, что у вашего кода (в данном случае, вашей *функции обработки пути*) есть способы объявить вещи, которые запрашиваются для работы и использования: "зависимости".
+В программировании **«Dependency Injection»** означает, что у вашего кода (в данном случае у ваших *функций обработки пути*) есть способ объявить вещи, которые требуются для его работы и использования: «зависимости».
-И потом эта система (в нашем случае **FastAPI**) организует всё, что требуется, чтобы обеспечить ваш код этой зависимостью (сделать "инъекцию" зависимости).
+И затем эта система (в нашем случае **FastAPI**) позаботится о том, чтобы сделать всё необходимое для предоставления вашему коду этих зависимостей (сделать «инъекцию» зависимостей).
Это очень полезно, когда вам нужно:
* Обеспечить общую логику (один и тот же алгоритм снова и снова).
-* Общее соединение с базой данных.
-* Обеспечение безопасности, аутентификации, запроса роли и т.п.
-* И многое другое.
+* Разделять соединения с базой данных.
+* Обеспечить безопасность, аутентификацию, требования к ролям и т. п.
+* И многое другое...
-Всё это минимизирует повторение кода.
+Всё это при минимизации повторения кода.
-## Первые шаги
+## Первые шаги { #first-steps }
-Давайте рассмотрим очень простой пример. Он настолько простой, что на данный момент почти бесполезный.
+Давайте рассмотрим очень простой пример. Он настолько простой, что пока не очень полезен.
-Но таким способом мы можем сфокусироваться на том, как же всё таки работает система **Dependency Injection**.
+Но так мы сможем сосредоточиться на том, как работает система **Dependency Injection**.
-### Создание зависимости или "зависимого"
-Давайте для начала сфокусируемся на зависимостях.
+### Создайте зависимость, или «dependable» (от чего что-то зависит) { #create-a-dependency-or-dependable }
+
+Сначала сосредоточимся на зависимости.
+
+Это просто функция, которая может принимать те же параметры, что и *функция обработки пути*:
-Это просто функция, которая может принимать все те же параметры, что и *функции обработки пути*:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *}
-**И всё.**
+И всё.
**2 строки.**
-И теперь она той же формы и структуры, что и все ваши *функции обработки пути*.
+И она имеет ту же форму и структуру, что и все ваши *функции обработки пути*.
-Вы можете думать об *функции обработки пути* как о функции без "декоратора" (без `@app.get("/some-path")`).
+Можно думать о ней как о *функции обработки пути* без «декоратора» (без `@app.get("/some-path")`).
-И она может возвращать всё, что требуется.
+И она может возвращать что угодно.
-В этом случае, эта зависимость ожидает:
+В этом случае эта зависимость ожидает:
-* Необязательный query-параметр `q` с типом `str`
-* Необязательный query-параметр `skip` с типом `int`, и значением по умолчанию `0`
-* Необязательный query-параметр `limit` с типом `int`, и значением по умолчанию `100`
+* Необязательный query-параметр `q` типа `str`.
+* Необязательный query-параметр `skip` типа `int`, по умолчанию `0`.
+* Необязательный query-параметр `limit` типа `int`, по умолчанию `100`.
-И в конце она возвращает `dict`, содержащий эти значения.
+А затем просто возвращает `dict`, содержащий эти значения.
/// info | Информация
-**FastAPI** добавил поддержку для `Annotated` (и начал её рекомендовать) в версии 0.95.0.
+FastAPI добавил поддержку `Annotated` (и начал рекомендовать его использование) в версии 0.95.0.
- Если у вас более старая версия, будут ошибки при попытке использовать `Annotated`.
+Если у вас более старая версия, вы получите ошибки при попытке использовать `Annotated`.
-Убедитесь, что вы [Обновили FastAPI версию](../../deployment/versions.md#fastapi_2){.internal-link target=_blank} до, как минимум 0.95.1, перед тем как использовать `Annotated`.
+Убедитесь, что вы [обновили версию FastAPI](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1, прежде чем использовать `Annotated`.
///
-### Import `Depends`
+### Импорт `Depends` { #import-depends }
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *}
-### Объявите зависимость в "зависимом"
+### Объявите зависимость в «зависимом» { #declare-the-dependency-in-the-dependant }
-Точно так же, как вы использовали `Body`, `Query` и т.д. с вашей *функцией обработки пути* для параметров, используйте `Depends` с новым параметром:
+Точно так же, как вы используете `Body`, `Query` и т. д. с параметрами вашей *функции обработки пути*, используйте `Depends` с новым параметром:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *}
-`Depends` работает немного иначе. Вы передаёте в `Depends` одиночный параметр, который будет похож на функцию.
+Хотя вы используете `Depends` в параметрах вашей функции так же, как `Body`, `Query` и т. д., `Depends` работает немного иначе.
+
+В `Depends` вы передаёте только один параметр.
+
+Этот параметр должен быть чем-то вроде функции.
-Вы **не вызываете его** на месте (не добавляете скобочки в конце: 👎 *your_best_func()*👎), просто передаёте как параметр в `Depends()`.
+Вы **не вызываете её** напрямую (не добавляйте круглые скобки в конце), просто передаёте её как параметр в `Depends()`.
-И потом функция берёт параметры так же, как *функция обработки пути*.
+И эта функция принимает параметры так же, как *функции обработки пути*.
/// tip | Подсказка
-В следующей главе вы увидите, какие другие вещи, помимо функций, можно использовать в качестве зависимостей.
+В следующей главе вы увидите, какие ещё «вещи», помимо функций, можно использовать в качестве зависимостей.
///
-Каждый раз, когда новый запрос приходит, **FastAPI** позаботится о:
+Каждый раз, когда приходит новый запрос, **FastAPI** позаботится о:
-* Вызове вашей зависимости ("зависимого") функции с корректными параметрами.
+* Вызове вашей зависимости («dependable») с корректными параметрами.
* Получении результата из вашей функции.
-* Назначении результата в параметр в вашей *функции обработки пути*.
+* Присваивании этого результата параметру в вашей *функции обработки пути*.
```mermaid
graph TB
@@ -98,121 +104,121 @@ common_parameters --> read_items
common_parameters --> read_users
```
-Таким образом, вы пишете общий код один раз, и **FastAPI** позаботится о его вызове для ваших *операций с путями*.
+Таким образом, вы пишете общий код один раз, а **FastAPI** позаботится о его вызове для ваших *операций пути*.
/// check | Проверка
-Обратите внимание, что вы не создаёте специальный класс и не передаёте его куда-то в **FastAPI** для регистрации, или что-то в этом роде.
+Обратите внимание, что вам не нужно создавать специальный класс и передавать его куда-то в **FastAPI**, чтобы «зарегистрировать» его или что-то подобное.
-Вы просто передаёте это в `Depends`, и **FastAPI** знает, что делать дальше.
+Вы просто передаёте его в `Depends`, и **FastAPI** знает, что делать дальше.
///
-## Объединяем с `Annotated` зависимостями
+## Использование зависимости с `Annotated` в нескольких местах { #share-annotated-dependencies }
-В приведенном выше примере есть небольшое **повторение кода**.
+В приведённых выше примерах есть небольшое **повторение кода**.
-Когда вам нужно использовать `common_parameters()` зависимость, вы должны написать весь параметр с аннотацией типов и `Depends()`:
+Когда вам нужно использовать зависимость `common_parameters()`, вы должны написать весь параметр с аннотацией типа и `Depends()`:
```Python
commons: Annotated[dict, Depends(common_parameters)]
```
-Но потому что мы используем `Annotated`, мы можем хранить `Annotated` значение в переменной и использовать его в нескольких местах:
+Но поскольку мы используем `Annotated`, мы можем сохранить это значение `Annotated` в переменную и использовать его в нескольких местах:
{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *}
/// tip | Подсказка
-Это стандартный синтаксис python и называется "type alias", это не особенность **FastAPI**.
+Это стандартный Python, это называется «type alias», и это не особенность **FastAPI**.
-Но потому что **FastAPI** базируется на стандартах Python, включая `Annotated`, вы можете использовать этот трюк в вашем коде. 😎
+Но поскольку **FastAPI** основан на стандартах Python, включая `Annotated`, вы можете использовать этот трюк в своём коде. 😎
///
-Зависимости продолжат работу как ожидалось, и **лучшая часть** в том, что **информация о типе будет сохранена**. Это означает, что ваш редактор кода будет корректно обрабатывать **автодополнения**, **встроенные ошибки** и так далее. То же самое относится и к инструментам, таким как `mypy`.
+Зависимости продолжат работать как ожидалось, и **лучшая часть** в том, что **информация о типах будет сохранена**, а значит, ваш редактор кода продолжит предоставлять **автозавершение**, **встроенные ошибки** и т.д. То же относится и к другим инструментам, таким как `mypy`.
-Это очень полезно, когда вы интегрируете это в **большую кодовую базу**, используя **одинаковые зависимости** снова и снова во **многих** ***операциях пути***.
+Это особенно полезно, когда вы используете это в **большой кодовой базе**, где вы используете **одни и те же зависимости** снова и снова во **многих *операциях пути***.
-## Использовать `async` или не `async`
+## Использовать `async` или не `async` { #to-async-or-not-to-async }
-Для зависимостей, вызванных **FastAPI** (то же самое, что и ваши *функции обработки пути*), те же правила, что приняты для определения ваших функций.
+Поскольку зависимости также вызываются **FastAPI** (как и ваши *функции обработки пути*), применяются те же правила при определении ваших функций.
Вы можете использовать `async def` или обычное `def`.
-Вы также можете объявить зависимости с `async def` внутри обычной `def` *функции обработки пути*, или `def` зависимости внутри `async def` *функции обработки пути*, и так далее.
+И вы можете объявлять зависимости с `async def` внутри обычных *функций обработки пути* `def`, или зависимости `def` внутри *функций обработки пути* `async def` и т. д.
-Это всё не важно. **FastAPI** знает, что нужно сделать. 😎
+Это не важно. **FastAPI** знает, что делать.
-/// note | Информация
+/// note | Примечание
-Если вам эта тема не знакома, прочтите [Async: *"In a hurry?"*](../../async.md){.internal-link target=_blank} раздел о `async` и `await` в документации.
+Если вы не уверены, посмотрите раздел [Async: *"In a hurry?"*](../../async.md#in-a-hurry){.internal-link target=_blank} о `async` и `await` в документации.
///
-## Интеграция с OpenAPI
+## Интеграция с OpenAPI { #integrated-with-openapi }
-Все заявления о запросах, валидаторы, требования ваших зависимостей (и подзависимостей) будут интегрированы в соответствующую OpenAPI-схему.
+Все объявления запросов, проверки и требования ваших зависимостей (и подзависимостей) будут интегрированы в ту же схему OpenAPI.
-В интерактивной документации будет вся информация по этим зависимостям тоже:
+Поэтому в интерактивной документации будет вся информация и из этих зависимостей:
-## Простое использование
+## Простое использование { #simple-usage }
-Если вы посмотрите на фото, *функция обработки пути* объявляется каждый раз, когда вычисляется путь, и тогда **FastAPI** позаботится о вызове функции с корректными параметрами, извлекая информацию из запроса.
+Если посмотреть, *функции обработки пути* объявляются для использования всякий раз, когда *путь* и *операция* совпадают, и тогда **FastAPI** заботится о вызове функции с корректными параметрами, извлекая данные из запроса.
-На самом деле, все (или большинство) веб-фреймворков работают по схожему сценарию.
+На самом деле все (или большинство) веб-фреймворков работают таким же образом.
-Вы никогда не вызываете эти функции на месте. Их вызовет ваш фреймворк (в нашем случае, **FastAPI**).
+Вы никогда не вызываете эти функции напрямую. Их вызывает ваш фреймворк (в нашем случае **FastAPI**).
-С системой Dependency Injection, вы можете сообщить **FastAPI**, что ваша *функция обработки пути* "зависит" от чего-то ещё, что должно быть извлечено перед вашей *функцией обработки пути*, и **FastAPI** позаботится об извлечении и инъекции результата.
+С системой **Dependency Injection** вы также можете сообщить **FastAPI**, что ваша *функция обработки пути* «зависит» от чего-то, что должно быть выполнено перед вашей *функцией обработки пути*, и **FastAPI** позаботится о его выполнении и «инъекции» результатов.
-Другие распространённые термины для описания схожей идеи "dependency injection" являются:
+Другие распространённые термины для описания той же идеи «dependency injection»:
-- ресурсность
-- доставка
-- сервисность
-- инъекция
-- компонентность
+* ресурсы
+* провайдеры
+* сервисы
+* внедряемые зависимости
+* компоненты
-## **FastAPI** подключаемые модули
+## Плагины **FastAPI** { #fastapi-plug-ins }
-Инъекции и модули могут быть построены с использованием системы **Dependency Injection**. Но на самом деле, **нет необходимости создавать новые модули**, просто используя зависимости, можно объявить бесконечное количество интеграций и взаимодействий, которые доступны вашей *функции обработки пути*.
+Интеграции и «плагины» могут быть построены с использованием системы **Dependency Injection**. Но на самом деле **нет необходимости создавать «плагины»**, так как, используя зависимости, можно объявить бесконечное количество интеграций и взаимодействий, которые становятся доступными вашим *функциям обработки пути*.
-И зависимости могут быть созданы очень простым и интуитивным способом, что позволяет вам просто импортировать нужные пакеты Python и интегрировать их в API функции за пару строк.
+И зависимости можно создавать очень простым и интуитивным способом, который позволяет просто импортировать нужные пакеты Python и интегрировать их с вашими API-функциями в пару строк кода, *буквально*.
Вы увидите примеры этого в следующих главах о реляционных и NoSQL базах данных, безопасности и т.д.
-## Совместимость с **FastAPI**
+## Совместимость с **FastAPI** { #fastapi-compatibility }
-Простота Dependency Injection делает **FastAPI** совместимым с:
+Простота системы **Dependency Injection** делает **FastAPI** совместимым с:
-- всеми реляционными базами данных
-- NoSQL базами данных
-- внешними пакетами
-- внешними API
-- системами авторизации, аутентификации
-- системами мониторинга использования API
-- системами ввода данных ответов
-- и так далее.
+* всеми реляционными базами данных
+* NoSQL базами данных
+* внешними пакетами
+* внешними API
+* системами аутентификации и авторизации
+* системами мониторинга использования API
+* системами инъекции данных в ответы
+* и т.д.
-## Просто и сильно
+## Просто и мощно { #simple-and-powerful }
-Хотя иерархическая система Dependency Injection очень проста для описания и использования, она по-прежнему очень мощная.
+Хотя иерархическая система dependency injection очень проста для определения и использования, она по-прежнему очень мощная.
-Вы можете описывать зависимости в очередь, и они уже будут вызываться друг за другом.
+Вы можете определять зависимости, которые, в свою очередь, могут иметь собственные зависимости.
-Когда иерархическое дерево построено, система **Dependency Injection** берет на себя решение всех зависимостей для вас (и их подзависимостей) и обеспечивает (инъектирует) результат на каждом шаге.
+В итоге строится иерархическое дерево зависимостей, и система **Dependency Injection** берёт на себя решение всех этих зависимостей (и их подзависимостей) и предоставляет (инъектирует) результаты на каждом шаге.
Например, у вас есть 4 API-эндпоинта (*операции пути*):
-- `/items/public/`
-- `/items/private/`
-- `/users/{user_id}/activate`
-- `/items/pro/`
+* `/items/public/`
+* `/items/private/`
+* `/users/{user_id}/activate`
+* `/items/pro/`
-Тогда вы можете требовать разные права для каждого из них, используя зависимости и подзависимости:
+тогда вы можете добавить разные требования к правам для каждого из них только с помощью зависимостей и подзависимостей:
```mermaid
graph TB
@@ -237,8 +243,8 @@ admin_user --> activate_user
paying_user --> pro_items
```
-## Интегрировано с **OpenAPI**
+## Интегрировано с **OpenAPI** { #integrated-with-openapi_1 }
-Все эти зависимости, объявляя свои требования, также добавляют параметры, проверки и т.д. к вашим операциям *path*.
+Все эти зависимости, объявляя свои требования, также добавляют параметры, проверки и т.д. к вашим *операциям пути*.
-**FastAPI** позаботится о добавлении всего этого в схему открытого API, чтобы это отображалось в системах интерактивной документации.
+**FastAPI** позаботится о добавлении всего этого в схему OpenAPI, чтобы это отображалось в системах интерактивной документации.
diff --git a/docs/ru/docs/tutorial/dependencies/sub-dependencies.md b/docs/ru/docs/tutorial/dependencies/sub-dependencies.md
index 5e8de0c4a..da31a6682 100644
--- a/docs/ru/docs/tutorial/dependencies/sub-dependencies.md
+++ b/docs/ru/docs/tutorial/dependencies/sub-dependencies.md
@@ -1,4 +1,4 @@
-# Подзависимости
+# Подзависимости { #sub-dependencies }
Вы можете создавать зависимости, которые имеют **подзависимости**.
@@ -6,7 +6,7 @@
**FastAPI** сам займётся их управлением.
-## Провайдер зависимости
+## Первая зависимость { #first-dependency-dependable }
Можно создать первую зависимость следующим образом:
@@ -16,9 +16,9 @@
Это довольно просто (хотя и не очень полезно), но поможет нам сосредоточиться на том, как работают подзависимости.
-## Вторая зависимость
+## Вторая зависимость, «зависимость» и «зависимая» { #second-dependency-dependable-and-dependant }
-Затем можно создать еще одну функцию зависимости, которая в то же время содержит внутри себя первую зависимость (таким образом, она тоже является "зависимой"):
+Затем можно создать еще одну функцию зависимости, которая одновременно объявляет свою собственную зависимость (таким образом, она тоже является «зависимой»):
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *}
@@ -29,7 +29,7 @@
* Она также объявляет необязательный куки-параметр `last_query` в виде строки.
* Если пользователь не указал параметр `q` в запросе, то мы используем последний использованный запрос, который мы ранее сохранили в куки-параметре `last_query`.
-## Использование зависимости
+## Использование зависимости { #use-the-dependency }
Затем мы можем использовать зависимость вместе с:
@@ -54,7 +54,7 @@ read_query["/items/"]
query_extractor --> query_or_cookie_extractor --> read_query
```
-## Использование одной и той же зависимости несколько раз
+## Использование одной и той же зависимости несколько раз { #using-the-same-dependency-multiple-times }
Если одна из ваших зависимостей объявлена несколько раз для одной и той же *функции операции пути*, например, несколько зависимостей имеют общую подзависимость, **FastAPI** будет знать, что вызывать эту подзависимость нужно только один раз за запрос.
@@ -62,7 +62,7 @@ query_extractor --> query_or_cookie_extractor --> read_query
В расширенном сценарии, когда вы знаете, что вам нужно, чтобы зависимость вызывалась на каждом шаге (возможно, несколько раз) в одном и том же запросе, вместо использования "кэшированного" значения, вы можете установить параметр `use_cache=False` при использовании `Depends`:
-//// tab | Python 3.6+
+//// 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.6+ без Annotated
+//// tab | Python 3.9+ без Annotated
/// tip | Подсказка
@@ -86,7 +86,7 @@ async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False
////
-## Резюме
+## Резюме { #recap }
Помимо всех этих умных слов, используемых здесь, система внедрения зависимостей довольно проста.
diff --git a/docs/ru/docs/tutorial/encoder.md b/docs/ru/docs/tutorial/encoder.md
index 4ed5039b3..16981f79d 100644
--- a/docs/ru/docs/tutorial/encoder.md
+++ b/docs/ru/docs/tutorial/encoder.md
@@ -1,4 +1,4 @@
-# JSON кодировщик
+# JSON-совместимый кодировщик { #json-compatible-encoder }
В некоторых случаях может потребоваться преобразование типа данных (например, Pydantic-модели) в тип, совместимый с JSON (например, `dict`, `list` и т.д.).
@@ -6,7 +6,7 @@
Для этого **FastAPI** предоставляет функцию `jsonable_encoder()`.
-## Использование `jsonable_encoder`
+## Использование `jsonable_encoder` { #using-the-jsonable-encoder }
Представим, что у вас есть база данных `fake_db`, которая принимает только JSON-совместимые данные.
@@ -14,21 +14,21 @@
В таком случае объект `datetime` следует преобразовать в строку соответствующую формату ISO.
-Точно так же эта база данных не может принять Pydantic модель (объект с атрибутами), а только `dict`.
+Точно так же эта база данных не может принять Pydantic-модель (объект с атрибутами), а только `dict`.
Для этого можно использовать функцию `jsonable_encoder`.
-Она принимает объект, например, модель Pydantic, и возвращает его версию, совместимую с JSON:
+Она принимает объект, например, Pydantic-модель, и возвращает его версию, совместимую с JSON:
{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *}
-В данном примере она преобразует Pydantic модель в `dict`, а `datetime` - в `str`.
+В данном примере она преобразует Pydantic-модель в `dict`, а `datetime` - в `str`.
Результатом её вызова является объект, который может быть закодирован с помощью функции из стандартной библиотеки Python – `json.dumps()`.
Функция не возвращает большой `str`, содержащий данные в формате JSON (в виде строки). Она возвращает стандартную структуру данных Python (например, `dict`) со значениями и подзначениями, которые совместимы с JSON.
-/// note | Технические детали
+/// note | Примечание
`jsonable_encoder` фактически используется **FastAPI** внутри системы для преобразования данных. Однако он полезен и во многих других сценариях.
diff --git a/docs/ru/docs/tutorial/extra-data-types.md b/docs/ru/docs/tutorial/extra-data-types.md
index 6d6d4aa9f..3b52b5d74 100644
--- a/docs/ru/docs/tutorial/extra-data-types.md
+++ b/docs/ru/docs/tutorial/extra-data-types.md
@@ -1,4 +1,4 @@
-# Дополнительные типы данных
+# Дополнительные типы данных { #extra-data-types }
До сих пор вы использовали простые типы данных, такие как:
@@ -9,15 +9,15 @@
Но вы также можете использовать и более сложные типы.
-При этом у вас останутся те же возможности , что и до сих пор:
+При этом у вас останутся те же возможности, что и до сих пор:
-* Отличная поддержка редактора.
+* Отличная поддержка редактора кода.
* Преобразование данных из входящих запросов.
* Преобразование данных для ответа.
* Валидация данных.
* Автоматическая аннотация и документация.
-## Другие типы данных
+## Другие типы данных { #other-data-types }
Ниже перечислены некоторые из дополнительных типов данных, которые вы можете использовать:
@@ -36,7 +36,7 @@
* `datetime.timedelta`:
* Встроенный в Python `datetime.timedelta`.
* В запросах и ответах будет представлен в виде общего количества секунд типа `float`.
- * Pydantic также позволяет представить его как "Кодировку разницы во времени ISO 8601", см. документацию для получения дополнительной информации.
+ * Pydantic также позволяет представить его как "Кодировку разницы во времени ISO 8601", см. документацию для получения дополнительной информации.
* `frozenset`:
* В запросах и ответах обрабатывается так же, как и `set`:
* В запросах будет прочитан список, исключены дубликаты и преобразован в `set`.
@@ -49,14 +49,14 @@
* `Decimal`:
* Встроенный в Python `Decimal`.
* В запросах и ответах обрабатывается так же, как и `float`.
-* Вы можете проверить все допустимые типы данных pydantic здесь: Типы данных Pydantic.
+* Вы можете проверить все допустимые типы данных Pydantic здесь: Типы данных Pydantic.
-## Пример
+## Пример { #example }
Вот пример *операции пути* с параметрами, который демонстрирует некоторые из вышеперечисленных типов.
-{* ../../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] *}
Обратите внимание, что параметры внутри функции имеют свой естественный тип данных, и вы, например, можете выполнять обычные манипуляции с датами, такие как:
-{* ../../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/ru/docs/tutorial/extra-models.md b/docs/ru/docs/tutorial/extra-models.md
index 5b51aa402..03156f2b4 100644
--- a/docs/ru/docs/tutorial/extra-models.md
+++ b/docs/ru/docs/tutorial/extra-models.md
@@ -1,4 +1,4 @@
-# Дополнительные модели
+# Дополнительные модели { #extra-models }
В продолжение прошлого примера будет уже обычным делом иметь несколько связанных между собой моделей.
@@ -16,19 +16,19 @@
///
-## Множественные модели
+## Множественные модели { #multiple-models }
Ниже изложена основная идея того, как могут выглядеть эти модели с полями для паролей, а также описаны места, где они используются:
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
-### Про `**user_in.dict()`
+### Про `**user_in.model_dump()` { #about-user-in-model-dump }
-#### `.dict()` из Pydantic
+#### `.model_dump()` из Pydantic { #pydantics-model-dump }
-`user_in` - это Pydantic-модель класса `UserIn`.
+`user_in` — это Pydantic-модель класса `UserIn`.
-У Pydantic-моделей есть метод `.dict()`, который возвращает `dict` с данными модели.
+У Pydantic-моделей есть метод `.model_dump()`, который возвращает `dict` с данными модели.
Поэтому, если мы создадим Pydantic-объект `user_in` таким способом:
@@ -39,10 +39,10 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com
и затем вызовем:
```Python
-user_dict = user_in.dict()
+user_dict = user_in.model_dump()
```
-то теперь у нас есть `dict` с данными модели в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели).
+то теперь у нас есть `dict` с данными в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели).
И если мы вызовем:
@@ -50,7 +50,7 @@ user_dict = user_in.dict()
print(user_dict)
```
-мы можем получить `dict` с такими данными:
+мы получим Python `dict` с:
```Python
{
@@ -61,9 +61,9 @@ print(user_dict)
}
```
-#### Распаковка `dict`
+#### Распаковка `dict` { #unpacking-a-dict }
-Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python распакует его. Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение.
+Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python его "распакует". Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение.
Поэтому, продолжая описанный выше пример с `user_dict`, написание такого кода:
@@ -71,7 +71,7 @@ print(user_dict)
UserInDB(**user_dict)
```
-Будет работать так же, как примерно такой код:
+будет эквивалентно:
```Python
UserInDB(
@@ -82,7 +82,7 @@ UserInDB(
)
```
-Или, если для большей точности мы напрямую используем `user_dict` с любым потенциальным содержимым, то этот пример будет выглядеть так:
+Или, более точно, если использовать `user_dict` напрямую, с любым содержимым, которое он может иметь в будущем:
```Python
UserInDB(
@@ -93,34 +93,34 @@ UserInDB(
)
```
-#### Pydantic-модель из содержимого другой модели
+#### Pydantic-модель из содержимого другой { #a-pydantic-model-from-the-contents-of-another }
-Как в примере выше мы получили `user_dict` из `user_in.dict()`, этот код:
+Как в примере выше мы получили `user_dict` из `user_in.model_dump()`, этот код:
```Python
-user_dict = user_in.dict()
+user_dict = user_in.model_dump()
UserInDB(**user_dict)
```
будет равнозначен такому:
```Python
-UserInDB(**user_in.dict())
+UserInDB(**user_in.model_dump())
```
-...потому что `user_in.dict()` - это `dict`, и затем мы указываем, чтобы Python его "распаковал", когда передаём его в `UserInDB` и ставим перед ним `**`.
+...потому что `user_in.model_dump()` — это `dict`, и затем мы указываем, чтобы Python его "распаковал", когда передаём его в `UserInDB` с префиксом `**`.
Таким образом мы получаем Pydantic-модель на основе данных из другой Pydantic-модели.
-#### Распаковка `dict` и дополнительные именованные аргументы
+#### Распаковка `dict` и дополнительные именованные аргументы { #unpacking-a-dict-and-extra-keywords }
И затем, если мы добавим дополнительный именованный аргумент `hashed_password=hashed_password` как здесь:
```Python
-UserInDB(**user_in.dict(), hashed_password=hashed_password)
+UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
```
-... то мы получим что-то подобное:
+...то в итоге получится что-то подобное:
```Python
UserInDB(
@@ -134,13 +134,13 @@ UserInDB(
/// warning | Предупреждение
-Цель использованных в примере вспомогательных функций - не более чем демонстрация возможных операций с данными, но, конечно, они не обеспечивают настоящую безопасность.
+Вспомогательные дополнительные функции `fake_password_hasher` и `fake_save_user` используются только для демонстрации возможного потока данных и, конечно, не обеспечивают настоящую безопасность.
///
-## Сократите дублирование
+## Сократите дублирование { #reduce-duplication }
-Сокращение дублирования кода - это одна из главных идей **FastAPI**.
+Сокращение дублирования кода — это одна из главных идей **FastAPI**.
Поскольку дублирование кода повышает риск появления багов, проблем с безопасностью, проблем десинхронизации кода (когда вы обновляете код в одном месте, но не обновляете в другом), и т.д.
@@ -156,23 +156,23 @@ UserInDB(
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
-## `Union` или `anyOf`
+## `Union` или `anyOf` { #union-or-anyof }
-Вы можете определить ответ как `Union` из двух типов. Это означает, что ответ должен соответствовать одному из них.
+Вы можете объявить HTTP-ответ как `Union` из двух или более типов. Это означает, что HTTP-ответ может быть любым из них.
Он будет определён в OpenAPI как `anyOf`.
-Для этого используйте стандартные аннотации типов в Python `typing.Union`:
+Для этого используйте стандартную аннотацию типов в Python `typing.Union`:
/// note | Примечание
-При объявлении `Union`, сначала указывайте наиболее детальные типы, затем менее детальные. В примере ниже более детальный `PlaneItem` стоит перед `CarItem` в `Union[PlaneItem, CarItem]`.
+При объявлении `Union` сначала указывайте наиболее специфичный тип, затем менее специфичный. В примере ниже более специфичный `PlaneItem` стоит перед `CarItem` в `Union[PlaneItem, CarItem]`.
///
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
-### `Union` в Python 3.10
+### `Union` в Python 3.10 { #union-in-python-3-10 }
В этом примере мы передаём `Union[PlaneItem, CarItem]` в качестве значения аргумента `response_model`.
@@ -184,19 +184,19 @@ UserInDB(
some_variable: PlaneItem | CarItem
```
-Но если мы помещаем его в `response_model=PlaneItem | CarItem` мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа.
+Но если мы поместим это в присваивание `response_model=PlaneItem | CarItem`, мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа.
-## Список моделей
+## Список моделей { #list-of-models }
-Таким же образом вы можете определять ответы как списки объектов.
+Таким же образом вы можете объявлять HTTP-ответы, возвращающие списки объектов.
-Для этого используйте `typing.List` из стандартной библиотеки Python (или просто `list` в Python 3.9 и выше):
+Для этого используйте стандартный `typing.List` в Python (или просто `list` в Python 3.9 и выше):
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
-## Ответ с произвольным `dict`
+## Ответ с произвольным `dict` { #response-with-arbitrary-dict }
-Вы также можете определить ответ, используя произвольный одноуровневый `dict` и определяя только типы ключей и значений без использования Pydantic-моделей.
+Вы также можете объявить HTTP-ответ, используя обычный произвольный `dict`, объявив только тип ключей и значений, без использования Pydantic-модели.
Это полезно, если вы заранее не знаете корректных названий полей/атрибутов (которые будут нужны при использовании Pydantic-модели).
@@ -204,8 +204,8 @@ some_variable: PlaneItem | CarItem
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
-## Резюме
+## Резюме { #recap }
-Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждой из них.
+Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждого случая.
-Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояния с полями `password`, `password_hash` и без пароля.
+Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояние, включающее `password`, `password_hash` и отсутствие пароля.
diff --git a/docs/ru/docs/tutorial/first-steps.md b/docs/ru/docs/tutorial/first-steps.md
index cb3d19a71..798c03d51 100644
--- a/docs/ru/docs/tutorial/first-steps.md
+++ b/docs/ru/docs/tutorial/first-steps.md
@@ -1,106 +1,122 @@
-# Первые шаги
+# Первые шаги { #first-steps }
-Самый простой FastAPI файл может выглядеть так:
+Самый простой файл FastAPI может выглядеть так:
-{* ../../docs_src/first_steps/tutorial001.py *}
+{* ../../docs_src/first_steps/tutorial001_py39.py *}
-Скопируйте в файл `main.py`.
+Скопируйте это в файл `main.py`.
Запустите сервер в режиме реального времени:
get операцию
+* по пути `/`
+* с использованием get операции
-/// info | `@decorator` Дополнительная информация
+/// info | Информация о `@decorator`
-Синтаксис `@something` в Python называется "декоратор".
+Синтаксис `@something` в Python называется «декоратор».
-Вы помещаете его над функцией. Как красивую декоративную шляпу (думаю, что оттуда и происходит этот термин).
+Его размещают над функцией. Как красивая декоративная шляпа (кажется, отсюда и пошёл термин).
-"Декоратор" принимает функцию ниже и выполняет с ней какое-то действие.
+«Декоратор» берёт функцию ниже и делает с ней что-то.
-В нашем случае, этот декоратор сообщает **FastAPI**, что функция ниже соответствует **пути** `/` и **операции** `get`.
+В нашем случае этот декоратор сообщает **FastAPI**, что функция ниже соответствует **пути** `/` с **операцией** `get`.
-Это и есть "**декоратор операции пути**".
+Это и есть «декоратор операции пути».
///
-Можно также использовать операции:
+Можно также использовать другие операции:
* `@app.post()`
* `@app.put()`
@@ -278,58 +302,79 @@ https://example.com/items/foo
/// tip | Подсказка
-Вы можете использовать каждую операцию (HTTP-метод) по своему усмотрению.
+Вы можете использовать каждый метод (HTTP-операцию) так, как считаете нужным.
-**FastAPI** не навязывает определенного значения для каждого метода.
+**FastAPI** не навязывает какого-либо конкретного смысла.
-Информация здесь представлена как рекомендация, а не требование.
+Эта информация дана как рекомендация, а не требование.
-Например, при использовании GraphQL обычно все действия выполняются только с помощью POST операций.
+Например, при использовании GraphQL обычно все действия выполняются только с помощью POST-операций.
///
-### Шаг 4: определите **функцию операции пути**
+### Шаг 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** будет вызывать её каждый раз при получении `GET` запроса к URL "`/`".
+**FastAPI** будет вызывать её каждый раз, когда получает запрос к URL «`/`» с операцией `GET`.
-В данном случае это асинхронная функция.
+В данном случае это асинхронная (`async`) функция.
---
-Вы также можете определить ее как обычную функцию вместо `async def`:
+Вы также можете определить её как обычную функцию вместо `async def`:
-{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
+{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *}
-/// note | Технические детали
+/// note | Примечание
-Если не знаете в чём разница, посмотрите [Конкурентность: *"Нет времени?"*](../async.md#_1){.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` и т.д.
-Также можно вернуть модели Pydantic (рассмотрим это позже).
+Также можно вернуть модели Pydantic (подробнее об этом позже).
+
+Многие другие объекты и модели будут автоматически преобразованы в JSON (включая ORM и т. п.). Попробуйте использовать те, что вам привычнее, с высокой вероятностью они уже поддерживаются.
+
+### Шаг 6: разверните приложение { #step-6-deploy-it }
+
+Разверните приложение в **FastAPI Cloud** одной командой: `fastapi deploy`. 🎉
+
+#### О FastAPI Cloud { #about-fastapi-cloud }
+
+**FastAPI Cloud** создан тем же автором и командой, что и **FastAPI**.
+
+Он упрощает процесс **создания образа**, **развертывания** и **доступа** к API с минимальными усилиями.
+
+Он переносит тот же **опыт разработчика** при создании приложений с FastAPI на их **развертывание** в облаке. 🎉
+
+FastAPI Cloud — основной спонсор и источник финансирования для open-source проектов «FastAPI и друзья». ✨
+
+#### Развертывание у других облачных провайдеров { #deploy-to-other-cloud-providers }
+
+FastAPI — open-source и основан на стандартах. Вы можете развернуть приложения FastAPI у любого облачного провайдера по вашему выбору.
-Многие объекты и модели будут автоматически преобразованы в JSON (включая ORM). Пробуйте использовать другие объекты, которые предпочтительней для Вас, вероятно, они уже поддерживаются.
+Следуйте руководствам вашего облачного провайдера, чтобы развернуть приложения FastAPI у них. 🤓
-## Резюме
+## Резюме { #recap }
-* Импортируем `FastAPI`.
-* Создаём экземпляр `app`.
-* Пишем **декоратор операции пути** (такой как `@app.get("/")`).
-* Пишем **функцию операции пути** (`def root(): ...`).
-* Запускаем сервер в режиме разработки (`uvicorn main:app --reload`).
+* Импортируйте `FastAPI`.
+* Создайте экземпляр `app`.
+* Напишите **декоратор операции пути**, например `@app.get("/")`.
+* Определите **функцию операции пути**; например, `def root(): ...`.
+* Запустите сервер разработки командой `fastapi dev`.
+* При желании разверните приложение командой `fastapi deploy`.
diff --git a/docs/ru/docs/tutorial/handling-errors.md b/docs/ru/docs/tutorial/handling-errors.md
index c596abe1f..2e00d7075 100644
--- a/docs/ru/docs/tutorial/handling-errors.md
+++ b/docs/ru/docs/tutorial/handling-errors.md
@@ -1,4 +1,4 @@
-# Обработка ошибок
+# Обработка ошибок { #handling-errors }
Существует множество ситуаций, когда необходимо сообщить об ошибке клиенту, использующему ваш API.
@@ -19,15 +19,15 @@
Помните ли ошибки **"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.
@@ -39,9 +39,9 @@
В данном примере, когда клиент запрашивает элемент по несуществующему 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:
@@ -69,7 +69,7 @@
///
-## Добавление пользовательских заголовков
+## Добавление пользовательских заголовков { #add-custom-headers }
В некоторых ситуациях полезно иметь возможность добавлять пользовательские заголовки к ошибке HTTP. Например, для некоторых типов безопасности.
@@ -77,11 +77,11 @@
Но в случае, если это необходимо для продвинутого сценария, можно добавить пользовательские заголовки:
-{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
+{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *}
-## Установка пользовательских обработчиков исключений
+## Установка пользовательских обработчиков исключений { #install-custom-exception-handlers }
-Вы можете добавить пользовательские обработчики исключений с помощью то же самое исключение - утилиты от Starlette.
+Вы можете добавить пользовательские обработчики исключений с помощью тех же утилит обработки исключений из Starlette.
Допустим, у вас есть пользовательское исключение `UnicornException`, которое вы (или используемая вами библиотека) можете `вызвать`.
@@ -89,7 +89,7 @@
Можно добавить собственный обработчик исключений с помощью `@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`.
@@ -109,7 +109,7 @@
///
-## Переопределение стандартных обработчиков исключений
+## Переопределение стандартных обработчиков исключений { #override-the-default-exception-handlers }
**FastAPI** имеет некоторые обработчики исключений по умолчанию.
@@ -117,7 +117,7 @@
Вы можете переопределить эти обработчики исключений на свои собственные.
-### Переопределение исключений проверки запроса
+### Переопределение обработчика исключений проверки запроса { #override-request-validation-exceptions }
Когда запрос содержит недопустимые данные, **FastAPI** внутренне вызывает ошибку `RequestValidationError`.
@@ -127,7 +127,7 @@
Обработчик исключения получит объект `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-ошибки с:
@@ -149,36 +149,17 @@
вы получите текстовую версию:
```
-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`
-
-/// warning | Внимание
-
-Это технические детали, которые можно пропустить, если они не важны для вас сейчас.
-
-///
-
-`RequestValidationError` является подклассом Pydantic `ValidationError`.
-
-**FastAPI** использует его для того, чтобы, если вы используете Pydantic-модель в `response_model`, и ваши данные содержат ошибку, вы увидели ошибку в журнале.
-
-Но клиент/пользователь этого не увидит. Вместо этого клиент получит сообщение "Internal Server Error" с кодом состояния HTTP `500`.
-
-Так и должно быть, потому что если в вашем *ответе* или где-либо в вашем коде (не в *запросе* клиента) возникает Pydantic `ValidationError`, то это действительно ошибка в вашем коде.
-
-И пока вы не устраните ошибку, ваши клиенты/пользователи не должны иметь доступа к внутренней информации о ней, так как это может привести к уязвимости в системе безопасности.
-
-### Переопределите обработчик ошибок `HTTPException`
+### Переопределите обработчик ошибок `HTTPException` { #override-the-httpexception-error-handler }
Аналогичным образом можно переопределить обработчик `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 | Технические детали
@@ -188,13 +169,21 @@ path -> item_id
///
-### Используйте тело `RequestValidationError`
+/// warning | Внимание
+
+Имейте в виду, что `RequestValidationError` содержит информацию об имени файла и строке, где произошла ошибка валидации, чтобы вы могли при желании отобразить её в логах с релевантными данными.
+
+Но это означает, что если вы просто преобразуете её в строку и вернёте эту информацию напрямую, вы можете допустить небольшую утечку информации о своей системе, поэтому здесь код извлекает и показывает каждую ошибку отдельно.
+
+///
+
+### Используйте тело `RequestValidationError` { #use-the-requestvalidationerror-body }
Ошибка `RequestValidationError` содержит полученное `тело` с недопустимыми данными.
Вы можете использовать его при разработке приложения для регистрации тела и его отладки, возврата пользователю и т.д.
-{* ../../docs_src/handling_errors/tutorial005.py hl[14] *}
+{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *}
Теперь попробуйте отправить недействительный элемент, например:
@@ -226,21 +215,19 @@ path -> item_id
}
```
-#### `HTTPException` в FastAPI или в Starlette
+#### `HTTPException` в FastAPI или в Starlette { #fastapis-httpexception-vs-starlettes-httpexception }
**FastAPI** имеет собственный `HTTPException`.
Класс ошибок **FastAPI** `HTTPException` наследует от класса ошибок Starlette `HTTPException`.
-Единственное отличие заключается в том, что `HTTPException` от **FastAPI** позволяет добавлять заголовки, которые будут включены в ответ.
-
-Он необходим/используется внутри системы для OAuth 2.0 и некоторых утилит безопасности.
+Единственное отличие состоит в том, что `HTTPException` в **FastAPI** принимает любые данные, пригодные для преобразования в JSON, в поле `detail`, тогда как `HTTPException` в Starlette принимает для него только строки.
Таким образом, вы можете продолжать вызывать `HTTPException` от **FastAPI** как обычно в своем коде.
Но когда вы регистрируете обработчик исключений, вы должны зарегистрировать его для `HTTPException` от Starlette.
-Таким образом, если какая-либо часть внутреннего кода Starlette, расширение или плагин Starlette вызовет исключение Starlette `HTTPException`, ваш обработчик сможет перехватить и обработать его.
+Таким образом, если какая-либо часть внутреннего кодa Starlette, расширение или плагин Starlette вызовет исключение Starlette `HTTPException`, ваш обработчик сможет перехватить и обработать его.
В данном примере, чтобы иметь возможность использовать оба `HTTPException` в одном коде, исключения Starlette переименованы в `StarletteHTTPException`:
@@ -248,10 +235,10 @@ path -> item_id
from starlette.exceptions import HTTPException as StarletteHTTPException
```
-### Переиспользование обработчиков исключений **FastAPI**
+### Переиспользование обработчиков исключений **FastAPI** { #reuse-fastapis-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] *}
В этом примере вы просто `выводите в терминал` ошибку с очень выразительным сообщением, но идея вам понятна. Вы можете использовать исключение, а затем просто повторно использовать стандартные обработчики исключений.
diff --git a/docs/ru/docs/tutorial/header-param-models.md b/docs/ru/docs/tutorial/header-param-models.md
index 4f54e3e15..fc1fb06d6 100644
--- a/docs/ru/docs/tutorial/header-param-models.md
+++ b/docs/ru/docs/tutorial/header-param-models.md
@@ -1,4 +1,4 @@
-# Модели Header-параметров
+# Модели Header-параметров { #header-parameter-models }
Если у вас есть группа связанных **header-параметров**, то вы можете объединить их в одну **Pydantic-модель**.
@@ -10,7 +10,7 @@
///
-## Header-параметры в виде Pydantic-модели
+## Header-параметры в виде Pydantic-модели { #header-parameters-with-a-pydantic-model }
Объявите нужные **header-параметры** в **Pydantic-модели** и затем аннотируйте параметр как `Header`:
@@ -18,7 +18,7 @@
**FastAPI** **извлечёт** данные для **каждого поля** из **заголовков** запроса и выдаст заданную вами Pydantic-модель.
-## Проверьте документацию
+## Проверьте документацию { #check-the-docs }
Вы можете посмотреть нужные header-параметры в графическом интерфейсе сгенерированной документации по пути `/docs`:
@@ -26,7 +26,7 @@
-## Как запретить дополнительные заголовки
+## Как запретить дополнительные заголовки { #forbid-extra-headers }
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** заголовки, которые вы хотите получать.
@@ -51,7 +51,7 @@
}
```
-## Как отключить автоматическое преобразование подчеркиваний
+## Как отключить автоматическое преобразование подчеркиваний { #disable-convert-underscores }
Как и в случае с обычными заголовками, если у вас в именах параметров имеются символы подчеркивания, они **автоматически преобразовываются в дефис**.
@@ -67,6 +67,6 @@
///
-## Резюме
+## Резюме { #summary }
Вы можете использовать **Pydantic-модели** для объявления **header-параметров** в **FastAPI**. 😎
diff --git a/docs/ru/docs/tutorial/header-params.md b/docs/ru/docs/tutorial/header-params.md
index e892cfc07..5ce5cb129 100644
--- a/docs/ru/docs/tutorial/header-params.md
+++ b/docs/ru/docs/tutorial/header-params.md
@@ -1,14 +1,14 @@
-# Header-параметры
+# Header-параметры { #header-parameters }
Вы можете определить параметры заголовка таким же образом, как вы определяете параметры `Query`, `Path` и `Cookie`.
-## Импорт `Header`
+## Импорт `Header` { #import-header }
Сперва импортируйте `Header`:
{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *}
-## Объявление параметров `Header`
+## Объявление параметров `Header` { #declare-header-parameters }
Затем объявите параметры заголовка, используя ту же структуру, что и с `Path`, `Query` и `Cookie`.
@@ -24,13 +24,13 @@
///
-/// info | Дополнительная информация
+/// info | Информация
Чтобы объявить заголовки, важно использовать `Header`, иначе параметры интерпретируются как query-параметры.
///
-## Автоматическое преобразование
+## Автоматическое преобразование { #automatic-conversion }
`Header` обладает небольшой дополнительной функциональностью в дополнение к тому, что предоставляют `Path`, `Query` и `Cookie`.
@@ -54,7 +54,7 @@
///
-## Повторяющиеся заголовки
+## Повторяющиеся заголовки { #duplicate-headers }
Есть возможность получать несколько заголовков с одним и тем же именем, но разными значениями.
@@ -84,7 +84,7 @@ X-Token: bar
}
```
-## Резюме
+## Резюме { #recap }
Объявляйте заголовки с помощью `Header`, используя тот же общий шаблон, как при `Query`, `Path` и `Cookie`.
diff --git a/docs/ru/docs/tutorial/index.md b/docs/ru/docs/tutorial/index.md
index ddca2fbb1..6674c6720 100644
--- a/docs/ru/docs/tutorial/index.md
+++ b/docs/ru/docs/tutorial/index.md
@@ -1,83 +1,95 @@
-# Учебник - Руководство пользователя
+# Учебник - Руководство пользователя { #tutorial-user-guide }
-В этом руководстве шаг за шагом показано, как использовать **FastApi** с большинством его функций.
+В этом руководстве шаг за шагом показано, как использовать **FastAPI** с большинством его функций.
-Каждый раздел постепенно основывается на предыдущих, но он структурирован по отдельным темам, так что вы можете перейти непосредственно к конкретной теме для решения ваших конкретных потребностей в API.
+Каждый раздел постепенно основывается на предыдущих, но структура разделяет темы, так что вы можете сразу перейти к нужной теме для решения ваших конкретных задач по API.
-Он также создан для использования в качестве будущего справочника.
+Он также создан как справочник на будущее, чтобы вы могли вернуться и посмотреть именно то, что вам нужно.
-Так что вы можете вернуться и посмотреть именно то, что вам нужно.
+## Запустите код { #run-the-code }
-## Запустите код
+Все блоки кода можно копировать и использовать напрямую (это действительно протестированные файлы Python).
-Все блоки кода можно копировать и использовать напрямую (на самом деле это проверенные файлы Python).
-
-Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `uvicorn` с параметрами:
+Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `fastapi dev` с:
contact| Параметр | Тип | Описание |
|---|---|---|
name | str | Идентификационное имя контактного лица/организации. |
url | str | URL указывающий на контактную информацию. ДОЛЖЕН быть в формате URL. |
email | str | Email адрес контактного лица/организации. ДОЛЖЕН быть в формате email адреса. |
license_info| Параметр | Тип | Описание |
|---|---|---|
name | str | ОБЯЗАТЕЛЬНО (если установлен параметр license_info). Название лицензии, используемой для API |
url | str | URL, указывающий на лицензию, используемую для API. ДОЛЖЕН быть в формате URL. |
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_py39.py hl[31] *}
+
+## Метаданные для тегов { #metadata-for-tags }
Вы также можете добавить дополнительные метаданные для различных тегов, используемых для группировки ваших операций пути с помощью параметра `openapi_tags`.
@@ -43,13 +52,13 @@
* `description`: `str`-значение с кратким описанием для внешней документации.
* `url` (**обязательно**): `str`-значение с URL-адресом для внешней документации.
-### Создание метаданных для тегов
+### Создание метаданных для тегов { #create-metadata-for-tags }
Давайте попробуем сделать это на примере с тегами для `users` и `items`.
Создайте метаданные для ваших тегов и передайте их в параметре `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_).
@@ -59,30 +68,31 @@
///
-### Используйте собственные теги
+### Используйте собственные теги { #use-your-tags }
+
Используйте параметр `tags` с вашими *операциями пути* (и `APIRouter`ами), чтобы присвоить им различные теги:
-{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
+{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *}
/// info | Дополнительная информация
-Узнайте больше о тегах в [Конфигурации операции пути](path-operation-configuration.md#_3){.internal-link target=_blank}.
+Узнайте больше о тегах в [Конфигурации операции пути](path-operation-configuration.md#tags){.internal-link target=_blank}.
///
-### Проверьте документацию
+### Проверьте документацию { #check-the-docs }
Теперь, если вы проверите документацию, вы увидите всю дополнительную информацию:
-### Порядок расположения тегов
+### Порядок расположения тегов { #order-of-tags }
-Порядок расположения словарей метаданных для каждого тега определяет также порядок, отображаемый в документах UI
+Порядок расположения словарей метаданных для каждого тега определяет также порядок, отображаемый в UI документации.
К примеру, несмотря на то, что `users` будут идти после `items` в алфавитном порядке, они отображаются раньше, потому что мы добавляем свои метаданные в качестве первого словаря в списке.
-## URL-адреса OpenAPI
+## URL-адрес OpenAPI { #openapi-url }
По умолчанию схема OpenAPI отображена по адресу `/openapi.json`.
@@ -90,13 +100,13 @@
К примеру, чтобы задать её отображение по адресу `/api/v1/openapi.json`:
-{* ../../docs_src/metadata/tutorial002.py hl[3] *}
+{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *}
-Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые его использует.
+Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые её используют.
-## URL-адреса документации
+## URL-адреса документации { #docs-urls }
-Вы можете изменить конфигурацию двух пользовательских интерфейсов документации, среди которых
+Вы можете изменить конфигурацию двух пользовательских интерфейсов документации, которые включены:
* **Swagger UI**: отображаемый по адресу `/docs`.
* Вы можете задать его URL с помощью параметра `docs_url`.
@@ -107,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/ru/docs/tutorial/middleware.md b/docs/ru/docs/tutorial/middleware.md
index 845e881e1..a83d3c011 100644
--- a/docs/ru/docs/tutorial/middleware.md
+++ b/docs/ru/docs/tutorial/middleware.md
@@ -1,4 +1,4 @@
-# Middleware (Промежуточный слой)
+# Middleware (Промежуточный слой) { #middleware }
Вы можете добавить промежуточный слой (middleware) в **FastAPI** приложение.
@@ -17,11 +17,11 @@
Если у вас есть зависимости с `yield`, то код выхода (код после `yield`) будет выполняться *после* middleware.
-Если у вас имеются некие фоновые задачи (см. документацию), то они будут запущены после middleware.
+Если были какие‑либо фоновые задачи (рассматриваются в разделе [Фоновые задачи](background-tasks.md){.internal-link target=_blank}, вы увидите это позже), они будут запущены *после* всех middleware.
///
-## Создание middleware
+## Создание middleware { #create-a-middleware }
Для создания middleware используйте декоратор `@app.middleware("http")`.
@@ -33,13 +33,13 @@
* Затем она возвращает ответ `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-'.
-Если же вы хотите добавить собственные заголовки, которые клиент сможет увидеть в браузере, то вам потребуется добавить их в настройки CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, см. документацию Starlette's CORS docs.
+Если же вы хотите добавить собственные заголовки, которые клиент сможет увидеть в браузере, то вам потребуется добавить их в настройки CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, см. документацию Starlette's CORS docs.
///
@@ -51,7 +51,7 @@
///
-### До и после `response`
+### До и после `response` { #before-and-after-the-response }
Вы можете добавить код, использующий `request` до передачи его какой-либо *операции пути*.
@@ -59,7 +59,7 @@
Например, вы можете добавить собственный заголовок `X-Process-Time`, содержащий время в секундах, необходимое для обработки запроса и генерации ответа:
-{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
+{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *}
/// tip | Примечание
@@ -67,8 +67,31 @@
///
-## Другие middleware
+## Порядок выполнения нескольких middleware { #multiple-middleware-execution-order }
+
+Когда вы добавляете несколько middleware с помощью декоратора `@app.middleware()` или метода `app.add_middleware()`, каждое новое middleware оборачивает приложение, формируя стек. Последнее добавленное middleware — самое внешнее (*outermost*), а первое — самое внутреннее (*innermost*).
+
+На пути обработки запроса сначала выполняется самое внешнее middleware.
+
+На пути формирования ответа оно выполняется последним.
+
+Например:
+
+```Python
+app.add_middleware(MiddlewareA)
+app.add_middleware(MiddlewareB)
+```
+
+Это приводит к следующему порядку выполнения:
+
+* **Запрос**: MiddlewareB → MiddlewareA → маршрут
+
+* **Ответ**: маршрут → MiddlewareA → MiddlewareB
+
+Такое стековое поведение обеспечивает предсказуемый и управляемый порядок выполнения middleware.
+
+## Другие middleware { #other-middlewares }
О других middleware вы можете узнать больше в разделе [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}.
-В следующем разделе вы можете прочитать, как настроить CORS с помощью middleware.
+В следующем разделе вы можете прочитать, как настроить CORS с помощью middleware.
diff --git a/docs/ru/docs/tutorial/path-operation-configuration.md b/docs/ru/docs/tutorial/path-operation-configuration.md
index af471ca69..96a54ffea 100644
--- a/docs/ru/docs/tutorial/path-operation-configuration.md
+++ b/docs/ru/docs/tutorial/path-operation-configuration.md
@@ -1,4 +1,4 @@
-# Конфигурация операций пути
+# Конфигурация операций пути { #path-operation-configuration }
Существует несколько параметров, которые вы можете передать вашему *декоратору операций пути* для его настройки.
@@ -8,7 +8,7 @@
///
-## Коды состояния
+## Статус-код ответа { #response-status-code }
Вы можете определить (HTTP) `status_code`, который будет использован в ответах вашей *операции пути*.
@@ -18,7 +18,7 @@
{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *}
-Этот код состояния будет использован в ответе и будет добавлен в схему OpenAPI.
+Этот статус-код будет использован в ответе и будет добавлен в схему OpenAPI.
/// note | Технические детали
@@ -28,7 +28,7 @@
///
-## Теги
+## Теги { #tags }
Вы можете добавлять теги к вашим *операциям пути*, добавив параметр `tags` с `list` заполненным `str`-значениями (обычно в нём только одна строка):
@@ -38,7 +38,7 @@
-### Теги с перечислениями
+### Теги с перечислениями { #tags-with-enums }
Если у вас большое приложение, вы можете прийти к необходимости добавить **несколько тегов**, и возможно, вы захотите убедиться в том, что всегда используете **один и тот же тег** для связанных *операций пути*.
@@ -46,15 +46,15 @@
**FastAPI** поддерживает это так же, как и в случае с обычными строками:
-{* ../../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] *}
-## Краткое и развёрнутое содержание
+## Краткое и развёрнутое содержание { #summary-and-description }
Вы можете добавить параметры `summary` и `description`:
{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *}
-## Описание из строк документации
+## Описание из строк документации { #description-from-docstring }
Так как описания обычно длинные и содержат много строк, вы можете объявить описание *операции пути* в функции строки документации и **FastAPI** прочитает её отсюда.
@@ -66,7 +66,7 @@
-## Описание ответа
+## Описание ответа { #response-description }
Вы можете указать описание ответа с помощью параметра `response_description`:
@@ -78,7 +78,7 @@
///
-/// check | Технические детали
+/// check
OpenAPI указывает, что каждой *операции пути* необходимо описание ответа.
@@ -88,11 +88,11 @@ OpenAPI указывает, что каждой *операции пути* не
-## Обозначение *операции пути* как устаревшей
+## Обозначение *операции пути* как устаревшей { #deprecate-a-path-operation }
Если вам необходимо пометить *операцию пути* как устаревшую, при этом не удаляя её, передайте параметр `deprecated`:
-{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *}
+{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *}
Он будет четко помечен как устаревший в интерактивной документации:
@@ -102,6 +102,6 @@ OpenAPI указывает, что каждой *операции пути* не
-## Резюме
+## Резюме { #recap }
Вы можете легко конфигурировать и добавлять метаданные в ваши *операции пути*, передавая параметры *декораторам операций пути*.
diff --git a/docs/ru/docs/tutorial/path-params-numeric-validations.md b/docs/ru/docs/tutorial/path-params-numeric-validations.md
index dca267f78..f0fe78805 100644
--- a/docs/ru/docs/tutorial/path-params-numeric-validations.md
+++ b/docs/ru/docs/tutorial/path-params-numeric-validations.md
@@ -1,8 +1,8 @@
-# Path-параметры и валидация числовых данных
+# Path-параметры и валидация числовых данных { #path-parameters-and-numeric-validations }
Так же, как с помощью `Query` вы можете добавлять валидацию и метаданные для query-параметров, так и с помощью `Path` вы можете добавлять такую же валидацию и метаданные для path-параметров.
-## Импорт Path
+## Импорт `Path` { #import-path }
Сначала импортируйте `Path` из `fastapi`, а также импортируйте `Annotated`:
@@ -14,11 +14,11 @@
Если вы используете более старую версию, вы столкнётесь с ошибками при попытке использовать `Annotated`.
-Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#fastapi_2){.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`.
@@ -28,15 +28,11 @@
/// note | Примечание
-Path-параметр всегда является обязательным, поскольку он составляет часть пути.
-
-Поэтому следует объявить его с помощью `...`, чтобы обозначить, что этот параметр обязательный.
-
-Тем не менее, даже если вы объявите его как `None` или установите для него значение по умолчанию, это ни на что не повлияет и параметр останется обязательным.
+Path-параметр всегда является обязательным, поскольку он должен быть частью пути. Даже если вы объявите его как `None` или зададите значение по умолчанию, это ни на что не повлияет — параметр всё равно будет обязательным.
///
-## Задайте нужный вам порядок параметров
+## Задайте нужный вам порядок параметров { #order-the-parameters-as-you-need }
/// tip | Подсказка
@@ -58,25 +54,13 @@ Path-параметр всегда является обязательным, п
Поэтому вы можете определить функцию так:
-//// tab | Python 3.8 без Annotated
-
-/// tip | Подсказка
-
-Рекомендуется использовать версию с `Annotated` если возможно.
-
-///
-
-```Python hl_lines="7"
-{!> ../../docs_src/path_params_numeric_validations/tutorial002.py!}
-```
-
-////
+{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *}
-Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете `Query()` или `Path()` в качестве значения по умолчанию для параметра функции.
+Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете значения по умолчанию параметров функции для `Query()` или `Path()`.
-{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py hl[10] *}
+{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
-## Задайте нужный вам порядок параметров, полезные приёмы
+## Задайте нужный вам порядок параметров, полезные приёмы { #order-the-parameters-as-you-need-tricks }
/// tip | Подсказка
@@ -97,25 +81,25 @@ Path-параметр всегда является обязательным, п
Передайте `*` в качестве первого параметра функции.
-Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как kwargs, даже если у них нет значений по умолчанию.
+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` (и других классов, которые мы разберём позже) вы можете добавлять ограничения для числовых данных.
-В этом примере при указании `ge=1`, параметр `item_id` должен быть больше или равен `1` ("`g`reater than or `e`qual").
+В этом примере при указании `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 }
То же самое применимо к:
@@ -124,19 +108,19 @@ Python не будет ничего делать с `*`, но он будет з
{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
-## Валидация числовых данных: числа с плавающей точкой, больше и меньше
+## Валидация числовых данных: числа с плавающей точкой, больше и меньше { #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}.
@@ -149,7 +133,7 @@ Python не будет ничего делать с `*`, но он будет з
/// info | Информация
-`Query`, `Path` и другие классы, которые мы разберём позже, являются наследниками общего класса `Param`.
+`Query`, `Path` и другие классы, которые вы разберёте позже, являются наследниками общего класса `Param`.
Все они используют те же параметры для дополнительной валидации и метаданных, которые вы видели ранее.
diff --git a/docs/ru/docs/tutorial/path-params.md b/docs/ru/docs/tutorial/path-params.md
index 5c2d82a65..83a7ed3ff 100644
--- a/docs/ru/docs/tutorial/path-params.md
+++ b/docs/ru/docs/tutorial/path-params.md
@@ -1,8 +1,8 @@
-# Path-параметры
+# 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`.
@@ -12,21 +12,21 @@
{"item_id":"foo"}
```
-## Параметры пути с типами
+## Параметры пути с типами { #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`.
/// check | Заметка
-Это обеспечит поддержку редактора внутри функции (проверка ошибок, автодополнение и т.п.).
+Это обеспечит поддержку редактора кода внутри функции (проверка ошибок, автозавершение и т.п.).
///
-## Преобразование данных
+## Преобразование данных { #data-conversion }
Если запустите этот пример и перейдёте по адресу: http://127.0.0.1:8000/items/3, то увидите ответ:
@@ -38,44 +38,45 @@
Обратите внимание на значение `3`, которое получила (и вернула) функция. Это целочисленный Python `int`, а не строка `"3"`.
-Используя определения типов, **FastAPI** выполняет автоматический "парсинг" запросов.
+Используя такое объявление типов, **FastAPI** выполняет автоматический "парсинг" запросов.
///
-## Проверка данных
+## Валидация данных { #data-validation }
Если откроете браузер по адресу 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` имеет значение `"foo"`, которое не является типом `int`.
+из-за того, что параметр пути `item_id` имеет значение `"foo"`, которое не является типом `int`.
-Та же ошибка возникнет, если вместо `int` передать `float` , например: http://127.0.0.1:8000/items/4.2
+Та же ошибка возникнет, если вместо `int` передать `float`, например: http://127.0.0.1:8000/items/4.2
/// check | Заметка
-**FastAPI** обеспечивает проверку типов, используя всё те же определения типов.
+**FastAPI** обеспечивает валидацию данных, используя всё те же определения типов.
-Обратите внимание, что в тексте ошибки явно указано место не прошедшее проверку.
+Обратите внимание, что в тексте ошибки явно указано место, не прошедшее проверку.
Это очень полезно при разработке и отладке кода, который взаимодействует с API.
///
-## Документация
+## Документация { #documentation }
И теперь, когда откроете браузер по адресу: http://127.0.0.1:8000/docs, то увидите вот такую автоматически сгенерированную документацию API:
@@ -89,17 +90,17 @@
///
-## Преимущества стандартизации, альтернативная документация
+## Преимущества стандартизации, альтернативная документация { #standards-based-benefits-alternative-documentation }
-Поскольку сгенерированная схема соответствует стандарту OpenAPI, её можно использовать со множеством совместимых инструментов.
+Поскольку сгенерированная схема соответствует стандарту 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. Поэтому вы можете быть уверены в качестве обработки данных.
@@ -107,7 +108,7 @@
Некоторые из них рассматриваются в следующих главах данного руководства.
-## Порядок имеет значение
+## Порядок имеет значение { #order-matters }
При создании *операций пути* можно столкнуться с ситуацией, когда путь является фиксированным.
@@ -117,22 +118,21 @@
Поскольку *операции пути* выполняются в порядке их объявления, необходимо, чтобы путь для `/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] *}
Первый будет выполняться всегда, так как путь совпадает первым.
-## Предопределенные значения
+## Предопределенные значения { #predefined-values }
Что если нам нужно заранее определить допустимые *параметры пути*, которые *операция пути* может принимать? В таком случае можно использовать стандартное перечисление `Enum` Python.
-### Создание класса `Enum`
+### Создание класса `Enum` { #create-an-enum-class }
Импортируйте `Enum` и создайте подкласс, который наследуется от `str` и `Enum`.
@@ -140,47 +140,41 @@
Затем создайте атрибуты класса с фиксированными допустимыми значениями:
-{* ../../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 | Подсказка
-Если интересно, то "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 }
Поскольку доступные значения *параметра пути* определены заранее, интерактивная документация может наглядно их отображать:
-### Работа с *перечислениями* в Python
+### Работа с *перечислениями* в Python { #working-with-python-enumerations }
Значение *параметра пути* будет *элементом перечисления*.
-#### Сравнение *элементов перечисления*
+#### Сравнение *элементов перечисления* { #compare-enumeration-members }
Вы можете сравнить это значение с *элементом перечисления* класса `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 | Подсказка
@@ -188,13 +182,13 @@
///
-#### Возврат *элементов перечисления*
+#### Возврат *элементов перечисления* { #return-enumeration-members }
Из *операции пути* можно вернуть *элементы перечисления*, даже вложенные в тело 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
@@ -204,7 +198,7 @@
}
```
-## Path-параметры, содержащие пути
+## Path-параметры, содержащие пути { #path-parameters-containing-paths }
Предположим, что есть *операция пути* с путем `/files/{file_path}`.
@@ -212,7 +206,7 @@
Тогда URL для этого файла будет такой: `/files/home/johndoe/myfile.txt`.
-### Поддержка OpenAPI
+### Поддержка OpenAPI { #openapi-support }
OpenAPI не поддерживает способов объявления *параметра пути*, содержащего внутри *путь*, так как это может привести к сценариям, которые сложно определять и тестировать.
@@ -220,7 +214,7 @@ OpenAPI не поддерживает способов объявления *п
Документация по-прежнему будет работать, хотя и не добавит никакой информации о том, что параметр должен содержать путь.
-### Конвертер пути
+### Конвертер пути { #path-convertor }
Благодаря одной из опций Starlette, можете объявить *параметр пути*, содержащий *путь*, используя URL вроде:
@@ -232,7 +226,7 @@ OpenAPI не поддерживает способов объявления *п
Можете использовать так:
-{* ../../docs_src/path_params/tutorial004.py hl[6] *}
+{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *}
/// tip | Подсказка
@@ -242,13 +236,14 @@ OpenAPI не поддерживает способов объявления *п
///
-## Резюме
+## Резюме { #recap }
+
Используя **FastAPI** вместе со стандартными объявлениями типов Python (короткими и интуитивно понятными), вы получаете:
-* Поддержку редактора (проверку ошибок, автозаполнение и т.п.)
+* Поддержку редактора кода (проверку ошибок, автозавершение и т.п.)
* "Парсинг" данных
* Валидацию данных
-* Автоматическую документацию API с указанием типов параметров.
+* Аннотации API и автоматическую документацию
И объявлять типы достаточно один раз.
diff --git a/docs/ru/docs/tutorial/query-param-models.md b/docs/ru/docs/tutorial/query-param-models.md
index 4d76d09e8..5ad7f1d99 100644
--- a/docs/ru/docs/tutorial/query-param-models.md
+++ b/docs/ru/docs/tutorial/query-param-models.md
@@ -1,4 +1,4 @@
-# Модели Query-Параметров
+# Модели Query-Параметров { #query-parameter-models }
Если у вас есть группа связанных **query-параметров**, то вы можете объединить их в одну **Pydantic-модель**.
@@ -10,7 +10,7 @@
///
-## Pydantic-Модель для Query-Параметров
+## Pydantic-Модель для Query-Параметров { #query-parameters-with-a-pydantic-model }
Объявите нужные **query-параметры** в **Pydantic-модели**, а после аннотируйте параметр как `Query`:
@@ -18,7 +18,7 @@
**FastAPI извлечёт** данные соответствующие **каждому полю модели** из **query-параметров** запроса и выдаст вам объявленную Pydantic-модель заполненную ими.
-## Проверьте Сгенерированную Документацию
+## Проверьте Сгенерированную Документацию { #check-the-docs }
Вы можете посмотреть query-параметры в графическом интерфейсе сгенерированной документации по пути `/docs`:
@@ -26,7 +26,7 @@
-## Запретить Дополнительные Query-Параметры
+## Запретить Дополнительные Query-Параметры { #forbid-extra-query-parameters }
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** query-параметры, которые вы хотите получить.
@@ -57,12 +57,12 @@ https://example.com/items/?limit=10&tool=plumbus
}
```
-## Заключение
+## Заключение { #summary }
Вы можете использовать **Pydantic-модели** для объявления **query-параметров** в **FastAPI**. 😎
/// tip | Совет
-Спойлер: вы также можете использовать Pydantic-модели для группировки кук (cookies) и заголовков (headers), но об этом вы прочитаете позже. 🤫
+Спойлер: вы также можете использовать Pydantic-модели, чтобы объявлять cookies и HTTP-заголовки, но об этом вы прочитаете позже. 🤫
///
diff --git a/docs/ru/docs/tutorial/query-params-str-validations.md b/docs/ru/docs/tutorial/query-params-str-validations.md
index 13b7015db..2bc2fb22c 100644
--- a/docs/ru/docs/tutorial/query-params-str-validations.md
+++ b/docs/ru/docs/tutorial/query-params-str-validations.md
@@ -1,61 +1,51 @@
-# Query-параметры и валидация строк
+# Query-параметры и валидация строк { #query-parameters-and-string-validations }
-**FastAPI** позволяет определять дополнительную информацию и валидацию для ваших параметров.
+**FastAPI** позволяет определять дополнительную информацию и выполнять валидацию для ваших параметров.
-Давайте рассмотрим следующий пример:
+Рассмотрим это приложение в качестве примера:
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
-Query-параметр `q` имеет тип `Union[str, None]` (или `str | None` в Python 3.10). Это означает, что входной параметр будет типа `str`, но может быть и `None`. Ещё параметр имеет значение по умолчанию `None`, из-за чего FastAPI определит параметр как необязательный.
+Query-параметр `q` имеет тип `str | None`, это означает, что он имеет тип `str`, но также может быть `None`. Значение по умолчанию действительно `None`, поэтому FastAPI будет знать, что он не обязателен.
-/// note | Технические детали
+/// note | Примечание
-FastAPI определит параметр `q` как необязательный, потому что его значение по умолчанию `= None`.
+FastAPI поймёт, что значение `q` не обязательно, из‑за значения по умолчанию `= None`.
-`Union` в `Union[str, None]` позволит редактору кода оказать вам лучшую поддержку и найти ошибки.
+Аннотация `str | None` позволит вашему редактору кода обеспечить лучшую поддержку и находить ошибки.
///
-## Расширенная валидация
+## Дополнительная валидация { #additional-validation }
-Добавим дополнительное условие валидации параметра `q` - **длина строки не более 50 символов** (условие проверяется всякий раз, когда параметр `q` не является `None`).
+Мы собираемся добавить ограничение: хотя `q` и необязателен, когда он передан, **его длина не должна превышать 50 символов**.
-### Импорт `Query` и `Annotated`
+### Импорт `Query` и `Annotated` { #import-query-and-annotated }
-Чтобы достичь этого, первым делом нам нужно импортировать:
+Чтобы сделать это, сначала импортируйте:
-* `Query` из пакета `fastapi`:
-* `Annotated` из пакета `typing` (или из `typing_extensions` для Python ниже 3.9)
+* `Query` из `fastapi`
+* `Annotated` из `typing`
-//// tab | Python 3.10+
-
-В Python 3.9 или выше, `Annotated` является частью стандартной библиотеки, таким образом вы можете импортировать его из `typing`.
-
-```Python hl_lines="1 3"
-{!> ../../docs_src/query_params_str_validations/tutorial002_an_py310.py!}
-```
-
-////
+{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *}
-//// tab | Python 3.8+
+/// info | Дополнительная информация
-В версиях Python ниже Python 3.9 `Annotation` импортируется из `typing_extensions`.
+Поддержка `Annotated` (и рекомендация использовать его) появилась в FastAPI версии 0.95.0.
-Эта библиотека будет установлена вместе с FastAPI.
+Если у вас более старая версия, при попытке использовать `Annotated` вы получите ошибки.
-```Python hl_lines="3-4"
-{!> ../../docs_src/query_params_str_validations/tutorial002_an.py!}
-```
+Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1 перед использованием `Annotated`.
-////
+///
-## `Annotated` как тип для query-параметра `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}?
+Помните, я уже говорил, что `Annotated` можно использовать для добавления метаданных к параметрам в разделе [Введение в типы Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}?
-Пришло время использовать их в FastAPI. 🚀
+Пришло время использовать его с FastAPI. 🚀
-У нас была аннотация следующего типа:
+У нас была такая аннотация типа:
//// tab | Python 3.10+
@@ -65,7 +55,7 @@ q: str | None = None
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
q: Union[str, None] = None
@@ -73,7 +63,7 @@ q: Union[str, None] = None
////
-Вот что мы получим, если обернём это в `Annotated`:
+Мы «обернём» это в `Annotated`, и получится:
//// tab | Python 3.10+
@@ -83,7 +73,7 @@ q: Annotated[str | None] = None
////
-//// tab | Python 3.8+
+//// tab | Python 3.9+
```Python
q: Annotated[Union[str, None]] = None
@@ -91,173 +81,147 @@ q: Annotated[Union[str, None]] = None
////
-Обе эти версии означают одно и тоже. `q` - это параметр, который может быть `str` или `None`, и по умолчанию он будет принимать `None`.
+Обе версии означают одно и то же: `q` — параметр, который может быть `str` или `None`, и по умолчанию равен `None`.
-Давайте повеселимся. 🎉
+А теперь к самому интересному. 🎉
-## Добавим `Query` в `Annotated` для query-параметра `q`
+## Добавим `Query` в `Annotated` для параметра `q` { #add-query-to-annotated-in-the-q-parameter }
-Теперь, когда у нас есть `Annotated`, где мы можем добавить больше метаданных, добавим `Query` со значением параметра `max_length` равным 50:
+Теперь, когда у нас есть `Annotated`, куда можно поместить дополнительную информацию (в нашем случае — дополнительные правила валидации), добавим `Query` внутрь `Annotated` и установим параметр `max_length` равным `50`:
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *}
-Обратите внимание, что значение по умолчанию всё ещё `None`, так что параметр остаётся необязательным.
+Обратите внимание, что значение по умолчанию по‑прежнему `None`, то есть параметр остаётся необязательным.
-Однако теперь, имея `Query(max_length=50)` внутри `Annotated`, мы говорим FastAPI, что мы хотим извлечь это значение из параметров query-запроса (что произойдёт в любом случае 🤷), и что мы хотим иметь **дополнительные условия валидации** для этого значения (для чего мы и делаем это - чтобы получить дополнительную валидацию). 😎
+Но теперь, добавив `Query(max_length=50)` внутрь `Annotated`, мы говорим FastAPI, что этому значению нужна **дополнительная валидация** — максимум 50 символов. 😎
-Теперь FastAPI:
+/// tip | Совет
-* **Валидирует** (проверяет), что полученные данные состоят максимум из 50 символов
-* Показывает **исчерпывающую ошибку** (будет описание местонахождения ошибки и её причины) для клиента в случаях, когда данные не валидны
-* **Задокументирует** параметр в схему OpenAPI *операции пути* (что будет отображено в **UI автоматической документации**)
+Здесь мы используем `Query()`, потому что это **query-параметр**. Позже мы увидим другие — `Path()`, `Body()`, `Header()` и `Cookie()`, — они также принимают те же аргументы, что и `Query()`.
-## Альтернативный (устаревший) способ задать `Query` как значение по умолчанию
+///
-В предыдущих версиях FastAPI (ниже 0.95.0) необходимо было использовать `Query` как значение по умолчанию для query-параметра. Так было вместо размещения его в `Annotated`, так что велика вероятность, что вам встретится такой код. Сейчас объясню.
+Теперь FastAPI будет:
-/// tip | Подсказка
+* **валидировать** данные, удостоверяясь, что максимальная длина — 50 символов;
+* показывать **понятную ошибку** клиенту, если данные невалидны;
+* **документировать** параметр в *операции пути* схемы OpenAPI (он будет показан в **UI автоматической документации**).
-При написании нового кода и везде где это возможно, используйте `Annotated`, как было описано ранее. У этого способа есть несколько преимуществ (о них дальше) и никаких недостатков. 🍰
+## Альтернатива (устаревшее): `Query` как значение по умолчанию { #alternative-old-query-as-the-default-value }
-///
+В предыдущих версиях FastAPI (до 0.95.0) требовалось использовать `Query` как значение по умолчанию для параметра вместо помещения его в `Annotated`. Скорее всего вы ещё встретите такой код, поэтому поясню.
-Вот как вы могли бы использовать `Query()` в качестве значения по умолчанию параметра вашей функции, установив для параметра `max_length` значение 50:
+/// tip | Подсказка
-{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *}
+Для нового кода и везде, где это возможно, используйте `Annotated`, как описано выше. У этого есть несколько преимуществ (см. ниже) и нет недостатков. 🍰
-В таком случае (без использования `Annotated`), мы заменили значение по умолчанию с `None` на `Query()` в функции. Теперь нам нужно установить значение по умолчанию для query-параметра `Query(default=None)`, что необходимо для тех же целей, как когда ранее просто указывалось значение по умолчанию (по крайней мере, для FastAPI).
+///
-Таким образом:
+Вот как можно использовать `Query()` как значение по умолчанию для параметра функции, установив `max_length` равным 50:
-```Python
-q: Union[str, None] = Query(default=None)
-```
-
-...делает параметр необязательным со значением по умолчанию `None`, также как это делает:
+{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *}
-```Python
-q: Union[str, None] = None
-```
+Так как в этом случае (без `Annotated`) мы заменяем в функции значение по умолчанию `None` на `Query()`, теперь нужно указать значение по умолчанию через параметр `Query(default=None)`, это служит той же цели — задать значение по умолчанию (по крайней мере для FastAPI).
-И для Python 3.10 и выше:
+Итак:
```Python
q: str | None = Query(default=None)
```
-...делает параметр необязательным со значением по умолчанию `None`, также как это делает:
+...делает параметр необязательным со значением по умолчанию `None`, так же как:
```Python
q: str | None = None
```
-Но он явно объявляет его как query-параметр.
+Но вариант с `Query` явно объявляет его как query-параметр.
-/// info | Дополнительная информация
-
-Запомните, важной частью объявления параметра как необязательного является:
-
-```Python
-= None
-```
-
-или:
-
-```Python
-= Query(default=None)
-```
-
-так как `None` указан в качестве значения по умолчанию, параметр будет **необязательным**.
-
-`Union[str, None]` позволит редактору кода оказать вам лучшую поддержку. Но это не то, на что обращает внимание FastAPI для определения необязательности параметра.
-
-///
-
-Теперь, мы можем указать больше параметров для `Query`. В данном случае, параметр `max_length` применяется к строкам:
+Затем мы можем передать и другие параметры в `Query`. В данном случае — параметр `max_length`, применимый к строкам:
```Python
-q: Union[str, None] = Query(default=None, max_length=50)
+q: str | None = Query(default=None, max_length=50)
```
-Входные данные будут проверены. Если данные недействительны, тогда будет указано на ошибку в запросе (будет описание местонахождения ошибки и её причины). Кроме того, параметр задокументируется в схеме OpenAPI данной *операции пути*.
+Это провалидирует данные, покажет понятную ошибку, если данные невалидны, и задокументирует параметр в *операции пути* схемы OpenAPI.
-### Использовать `Query` как значение по умолчанию или добавить в `Annotated`
+### `Query` как значение по умолчанию или внутри `Annotated` { #query-as-the-default-value-or-in-annotated }
-Когда `Query` используется внутри `Annotated`, вы не можете использовать параметр `default` у `Query`.
+Помните, что при использовании `Query` внутри `Annotated` нельзя указывать параметр `default` у `Query`.
-Вместо этого, используйте обычное указание значения по умолчанию для параметра функции. Иначе, это будет несовместимо.
+Вместо этого используйте обычное значение по умолчанию параметра функции. Иначе это будет неоднозначно.
-Следующий пример не рабочий:
+Например, так делать нельзя:
```Python
q: Annotated[str, Query(default="rick")] = "morty"
```
-...потому что нельзя однозначно определить, что именно должно быть значением по умолчанию: `"rick"` или `"morty"`.
+...потому что непонятно, какое значение должно быть по умолчанию: `"rick"` или `"morty"`.
-Вам следует использовать (предпочтительно):
+Следовательно, используйте (предпочтительно):
```Python
q: Annotated[str, Query()] = "rick"
```
-...или как в старом коде, который вам может попасться:
+...или в старой кодовой базе вы увидите:
```Python
q: str = Query(default="rick")
```
-### Преимущества `Annotated`
+### Преимущества `Annotated` { #advantages-of-annotated }
-**Рекомендуется использовать `Annotated`** вместо значения по умолчанию в параметрах функции, потому что так **лучше** по нескольким причинам. 🤓
+**Рекомендуется использовать `Annotated`** вместо задания значения по умолчанию в параметрах функции — так **лучше** по нескольким причинам. 🤓
-Значение **по умолчанию** у **параметров функции** - это **действительно значение по умолчанию**, что более интуитивно понятно для пользователей Python. 😌
+**Значение по умолчанию** у **параметра функции** — это **настоящее значение по умолчанию**, что более интуитивно для Python. 😌
-Вы можете **вызвать** ту же функцию в **иных местах** без FastAPI, и она **сработает как ожидается**. Если это **обязательный** параметр (без значения по умолчанию), ваш **редактор кода** сообщит об ошибке. **Python** также укажет на ошибку, если вы вызовете функцию без передачи ей обязательного параметра.
+Вы можете **вызвать** эту же функцию в **других местах** без FastAPI, и она будет **работать как ожидается**. Если есть **обязательный** параметр (без значения по умолчанию), ваш **редактор** сообщит об ошибке, **Python** тоже пожалуется, если вы запустите её без передачи обязательного параметра.
-Если вы вместо `Annotated` используете **(устаревший) стиль значений по умолчанию**, тогда при вызове этой функции без FastAPI в **другом месте** вам необходимо **помнить** о передаче аргументов функции, чтобы она работала корректно. В противном случае, значения будут отличаться от тех, что вы ожидаете (например, `QueryInfo` или что-то подобное вместо `str`). И ни ваш редактор кода, ни Python не будут жаловаться на работу этой функции, только когда вычисления внутри дадут сбой.
+Если вы не используете `Annotated`, а применяете **(устаревший) стиль со значением по умолчанию**, то при вызове этой функции без FastAPI в **других местах** вам нужно **помнить** о том, что надо передать аргументы, чтобы всё работало корректно, иначе значения будут не такими, как вы ожидаете (например, вместо `str` будет `QueryInfo` или что-то подобное). И ни редактор, ни Python не будут ругаться при самом вызове функции — ошибка проявится лишь при операциях внутри.
-Так как `Annotated` может принимать более одной аннотации метаданных, то теперь вы можете использовать ту же функцию с другими инструментами, например Typer. 🚀
+Так как `Annotated` может содержать больше одной аннотации метаданных, теперь вы можете использовать ту же функцию и с другими инструментами, например с Typer. 🚀
-## Больше валидации
+## Больше валидаций { #add-more-validations }
-Вы также можете добавить параметр `min_length`:
+Можно также добавить параметр `min_length`:
{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *}
-## Регулярные выражения
+## Регулярные выражения { #add-regular-expressions }
-Вы можете определить регулярное выражение, которому должен соответствовать параметр:
+Вы можете определить регулярное выражение `pattern`, которому должен соответствовать параметр:
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
-Данное регулярное выражение проверяет, что полученное значение параметра:
+Данный шаблон регулярного выражения проверяет, что полученное значение параметра:
-* `^`: начало строки.
-* `fixedquery`: в точности содержит строку `fixedquery`.
-* `$`: конец строки, не имеет символов после `fixedquery`.
+* `^`: начинается с следующих символов, до них нет символов.
+* `fixedquery`: имеет точное значение `fixedquery`.
+* `$`: заканчивается здесь, после `fixedquery` нет никаких символов.
-Не переживайте, если **"регулярное выражение"** вызывает у вас трудности. Это достаточно сложная тема для многих людей. Вы можете сделать множество вещей без использования регулярных выражений.
+Если вы теряетесь во всех этих идеях про **«регулярные выражения»**, не переживайте. Это сложная тема для многих. Многое можно сделать и без них.
-Но когда они вам понадобятся, и вы закончите их освоение, то не будет проблемой использовать их в **FastAPI**.
+Теперь вы знаете, что когда они понадобятся, вы сможете использовать их в **FastAPI**.
-## Значения по умолчанию
+## Значения по умолчанию { #default-values }
-Вы точно также можете указать любое значение `по умолчанию`, как ранее указывали `None`.
+Конечно, можно использовать и другие значения по умолчанию, не только `None`.
-Например, вы хотите для параметра запроса `q` указать, что он должен состоять минимум из 3 символов (`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`, делает параметр необязательным.
///
-## Обязательный параметр
+## Обязательные параметры { #required-parameters }
-Когда вам не требуется дополнительная валидация или дополнительные метаданные для параметра запроса, вы можете сделать параметр `q` обязательным просто не указывая значения по умолчанию. Например:
+Когда не требуется объявлять дополнительные проверки или метаданные, можно сделать query-параметр `q` обязательным, просто не указывая значение по умолчанию, например:
```Python
q: str
@@ -266,60 +230,42 @@ q: str
вместо:
```Python
-q: Union[str, None] = None
-```
-
-Но у нас query-параметр определён как `Query`. Например:
-
-//// tab | Annotated
-
-```Python
-q: Annotated[Union[str, None], Query(min_length=3)] = None
+q: str | None = None
```
-////
-
-//// tab | без Annotated
+Но сейчас мы объявляем его через `Query`, например так:
```Python
-q: Union[str, None] = Query(default=None, min_length=3)
+q: Annotated[str | None, Query(min_length=3)] = None
```
-////
-
-В таком случае, чтобы сделать query-параметр `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` как валидный тип для параметра запроса, но также указать `default=...`:
+Для этого объявите, что `None` — валидный тип, но просто не задавайте значение по умолчанию:
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
-/// tip | Подсказка
-
-Pydantic, мощь которого используется в FastAPI для валидации и сериализации, имеет специальное поведение для `Optional` или `Union[Something, None]` без значения по умолчанию. Вы можете узнать об этом больше в документации Pydantic, раздел Обязательные Опциональные поля.
-
-///
-
-## Множество значений для query-параметра
+## Query-параметр - список / несколько значений { #query-parameter-list-multiple-values }
-Для query-параметра `Query` можно указать, что он принимает список значений (множество значений).
+Когда вы явно объявляете 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
```
-вы бы получили несколько значений (`foo` и `bar`), которые относятся к параметру `q`, в виде Python `list` внутри вашей *функции обработки пути*, в *параметре функции* `q`.
+вы получите множественные значения *query-параметров* `q` (`foo` и `bar`) в виде Python-`list` внутри вашей *функции-обработчика пути*, в *параметре функции* `q`.
Таким образом, ответ на этот URL будет:
@@ -332,29 +278,29 @@ http://localhost:8000/items/?q=foo&q=bar
}
```
-/// tip | Подсказка
+/// tip | Совет
-Чтобы объявить query-параметр типом `list`, как в примере выше, вам нужно явно использовать `Query`, иначе он будет интерпретирован как тело запроса.
+Чтобы объявить query-параметр типа `list`, как в примере выше, нужно явно использовать `Query`, иначе он будет интерпретирован как тело запроса.
///
-Интерактивная документация API будет обновлена соответствующим образом, где будет разрешено множество значений:
+Интерактивная документация API обновится соответствующим образом и позволит передавать несколько значений:
-### Query-параметр со множеством значений по умолчанию
+### 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
{
@@ -365,45 +311,45 @@ http://localhost:8000/items/
}
```
-#### Использование `list`
+#### Просто `list` { #using-just-list }
-Вы также можете использовать `list` напрямую вместо `List[str]` (или `list[str]` в Python 3.9+):
+Можно использовать `list` напрямую вместо `list[str]`:
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
-/// note | Технические детали
+/// note | Примечание
-Запомните, что в таком случае, FastAPI не будет проверять содержимое списка.
+Имейте в виду, что в этом случае FastAPI не будет проверять содержимое списка.
-Например, для List[int] список будет провалидирован (и задокументирован) на содержание только целочисленных элементов. Но для простого `list` такой проверки не будет.
+Например, `list[int]` проверит (и задокументирует), что элементы списка — целые числа. А просто `list` — нет.
///
-## Больше метаданных
+## Больше метаданных { #declare-more-metadata }
-Вы можете добавить больше информации об query-параметре.
+Можно добавить больше информации о параметре.
-Указанная информация будет включена в генерируемую OpenAPI документацию и использована в пользовательском интерфейсе и внешних инструментах.
+Эта информация будет включена в сгенерированную OpenAPI-схему и использована интерфейсами документации и внешними инструментами.
-/// note | Технические детали
+/// note | Примечание
-Имейте в виду, что разные инструменты могут иметь разные уровни поддержки OpenAPI.
+Помните, что разные инструменты могут иметь разный уровень поддержки OpenAPI.
-Некоторые из них могут не отображать (на данный момент) всю заявленную дополнительную информацию, хотя в большинстве случаев отсутствующая функция уже запланирована к разработке.
+Некоторые из них пока могут не показывать всю дополнительную информацию, хотя в большинстве случаев недостающая возможность уже запланирована к разработке.
///
-Вы можете указать название query-параметра, используя параметр `title`:
+Можно задать `title`:
{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *}
-Добавить описание, используя параметр `description`:
+И `description`:
{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
-## Псевдонимы параметров
+## Псевдонимы параметров { #alias-parameters }
-Представьте, что вы хотите использовать query-параметр с названием `item-query`.
+Представьте, что вы хотите, чтобы параметр назывался `item-query`.
Например:
@@ -411,54 +357,117 @@ http://localhost:8000/items/
http://127.0.0.1:8000/items/?item-query=foobaritems
```
-Но `item-query` является невалидным именем переменной в Python.
+Но `item-query` — недопустимое имя переменной в Python.
-Наиболее похожее валидное имя `item_query`.
+Ближайший вариант — `item_query`.
-Но вам всё равно необходим `item-query`...
+Но вам всё равно нужно именно `item-query`...
-Тогда вы можете объявить `псевдоним`, и этот псевдоним будет использоваться для поиска значения параметра запроса:
+Тогда можно объявить `alias`, и этот псевдоним будет использован для поиска значения параметра:
{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
-## Устаревшие параметры
+## Маркировка параметров как устаревших { #deprecating-parameters }
-Предположим, вы больше не хотите использовать какой-либо параметр.
+Предположим, этот параметр вам больше не нравится.
-Вы решили оставить его, потому что клиенты всё ещё им пользуются. Но вы хотите отобразить это в документации как устаревший функционал.
+Его нужно оставить на какое‑то время, так как клиенты его используют, но вы хотите, чтобы в документации он явно отображался как устаревший.
-Тогда для `Query` укажите параметр `deprecated=True`:
+Тогда передайте параметр `deprecated=True` в `Query`:
{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *}
-В документации это будет отображено следующим образом:
+В документации это будет показано так:
-## Исключить из OpenAPI
+## Исключить параметры из OpenAPI { #exclude-parameters-from-openapi }
-Чтобы исключить query-параметр из генерируемой OpenAPI схемы (а также из системы автоматической генерации документации), укажите в `Query` параметр `include_in_schema=False`:
+Чтобы исключить query-параметр из генерируемой OpenAPI-схемы (и, следовательно, из систем автоматической документации), укажите у `Query` параметр `include_in_schema=False`:
{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *}
-## Резюме
+## Кастомная валидация { #custom-validation }
+
+Бывают случаи, когда нужна **кастомная валидация**, которую нельзя выразить параметрами выше.
+
+В таких случаях можно использовать **кастомную функцию-валидатор**, которая применяется после обычной валидации (например, после проверки, что значение — это `str`).
+
+Этого можно добиться, используя `AfterValidator` Pydantic внутри `Annotated`.
+
+/// tip | Совет
+
+В Pydantic также есть `BeforeValidator` и другие. 🤓
+
+///
+
+Например, эта кастомная проверка убеждается, что ID элемента начинается с `isbn-` для номера книги ISBN или с `imdb-` для ID URL фильма на IMDB:
+
+{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
+
+/// info | Дополнительная информация
+
+Это доступно в Pydantic версии 2 и выше. 😎
+
+///
-Вы можете объявлять дополнительные правила валидации и метаданные для ваших параметров запроса.
+/// tip | Совет
-Общие метаданные:
+Если вам нужна валидация, требующая общения с каким‑либо **внешним компонентом** — базой данных или другим API — вместо этого используйте **Зависимости FastAPI** (FastAPI Dependencies), вы познакомитесь с ними позже.
+
+Эти кастомные валидаторы предназначены для проверок, которые можно выполнить, имея **только** те же **данные**, что пришли в запросе.
+
+///
+
+### Понимание этого кода { #understand-that-code }
+
+Важный момент — это использовать **`AfterValidator` с функцией внутри `Annotated`**. Смело пропускайте эту часть. 🤸
+
+---
+
+Но если вам любопытен именно этот пример и всё ещё интересно, вот немного подробностей.
+
+#### Строка и `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()` мы получаем итерируемый объект с кортежами, содержащими ключ и значение для каждого элемента словаря.
+
+Мы превращаем этот итерируемый объект в обычный `list` через `list(data.items())`.
+
+Затем с `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 }
+
+Вы можете объявлять дополнительные проверки и метаданные для параметров.
+
+Общие метаданные и настройки:
* `alias`
* `title`
* `description`
* `deprecated`
-* `include_in_schema`
-Специфичные правила валидации для строк:
+Проверки, специфичные для строк:
* `min_length`
* `max_length`
-* `regex`
+* `pattern`
+
+Кастомные проверки с использованием `AfterValidator`.
-В рассмотренных примерах показано объявление правил валидации для строковых значений `str`.
+В этих примерах вы видели, как объявлять проверки для значений типа `str`.
-В следующих главах вы увидете, как объявлять правила валидации для других типов (например, чисел).
+Смотрите следующие главы, чтобы узнать, как объявлять проверки для других типов, например чисел.
diff --git a/docs/ru/docs/tutorial/query-params.md b/docs/ru/docs/tutorial/query-params.md
index 547d9831d..be1c0e46e 100644
--- a/docs/ru/docs/tutorial/query-params.md
+++ b/docs/ru/docs/tutorial/query-params.md
@@ -1,8 +1,8 @@
-# Query-параметры
+# Query-параметры { #query-parameters }
-Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры.
+Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры.
-{* ../../docs_src/query_params/tutorial001.py hl[9] *}
+{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *}
Query-параметры представляют из себя набор пар ключ-значение, которые идут после знака `?` в URL-адресе, разделенные символами `&`.
@@ -19,7 +19,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10
Будучи частью URL-адреса, они "по умолчанию" являются строками.
-Но когда вы объявляете их с использованием аннотаций (в примере выше, как `int`), они конвертируются в указанный тип данных и проходят проверку на соответствие ему.
+Но когда вы объявляете их с использованием типов Python (в примере выше, как `int`), они конвертируются в указанный тип данных и проходят проверку на соответствие ему.
Все те же правила, которые применяются к path-параметрам, также применяются и query-параметрам:
@@ -28,7 +28,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10
* Проверка на соответствие данных (Валидация)
* Автоматическая документация
-## Значения по умолчанию
+## Значения по умолчанию { #defaults }
Поскольку query-параметры не являются фиксированной частью пути, они могут быть не обязательными и иметь значения по умолчанию.
@@ -57,7 +57,7 @@ http://127.0.0.1:8000/items/?skip=20
* `skip=20`: потому что вы установили это в URL-адресе
* `limit=10`: т.к это было значение по умолчанию
-## Необязательные параметры
+## Необязательные параметры { #optional-parameters }
Аналогично, вы можете объявлять необязательные query-параметры, установив их значение по умолчанию, равное `None`:
@@ -71,7 +71,7 @@ http://127.0.0.1:8000/items/?skip=20
///
-## Преобразование типа параметра запроса
+## Преобразование типа параметра запроса { #query-parameter-type-conversion }
Вы также можете объявлять параметры с типом `bool`, которые будут преобразованы соответственно:
@@ -109,10 +109,9 @@ http://127.0.0.1:8000/items/foo?short=yes
или в любом другом варианте написания (в верхнем регистре, с заглавной буквой, и т.п), внутри вашей функции параметр `short` будет иметь значение `True` типа данных `bool` . В противном случае - `False`.
+## Смешивание query-параметров и path-параметров { #multiple-path-and-query-parameters }
-## Смешивание query-параметров и path-параметров
-
-Вы можете объявлять несколько query-параметров и path-параметров одновременно,**FastAPI** сам разберётся, что чем является.
+Вы можете объявлять несколько query-параметров и path-параметров одновременно, **FastAPI** сам разберётся, что чем является.
И вы не обязаны объявлять их в каком-либо определенном порядке.
@@ -120,15 +119,15 @@ http://127.0.0.1:8000/items/foo?short=yes
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
-## Обязательные query-параметры
+## Обязательные query-параметры { #required-query-parameters }
-Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то это значение не является обязательным.
+Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то он не является обязательным.
Если вы не хотите задавать конкретное значение, но хотите сделать параметр необязательным, вы можете установить значение по умолчанию равным `None`.
Но если вы хотите сделать query-параметр обязательным, вы можете просто не указывать значение по умолчанию:
-{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
+{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
Здесь параметр запроса `needy` является обязательным параметром с типом данных `str`.
@@ -142,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
+ }
+ ]
}
```
@@ -170,7 +170,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
}
```
-Конечно, вы можете определить некоторые параметры как обязательные, некоторые - со значением по умполчанию, а некоторые - полностью необязательные:
+Конечно, вы можете определить некоторые параметры как обязательные, некоторые — со значением по умолчанию, а некоторые — полностью необязательные:
{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *}
@@ -182,6 +182,6 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
/// tip | Подсказка
-Вы можете использовать класс `Enum` также, как ранее применяли его с [Path-параметрами](path-params.md#_7){.internal-link target=_blank}.
+Вы можете использовать класс `Enum` также, как ранее применяли его с [Path-параметрами](path-params.md#predefined-values){.internal-link target=_blank}.
///
diff --git a/docs/ru/docs/tutorial/request-files.md b/docs/ru/docs/tutorial/request-files.md
index 2cfa4e1dc..9cfbd53df 100644
--- a/docs/ru/docs/tutorial/request-files.md
+++ b/docs/ru/docs/tutorial/request-files.md
@@ -1,4 +1,4 @@
-# Загрузка файлов
+# Загрузка файлов { #request-files }
Используя класс `File`, мы можем позволить клиентам загружать файлы.
@@ -6,19 +6,23 @@
Чтобы получать загруженные файлы, сначала установите `python-multipart`.
-Например: `pip install python-multipart`.
+Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
-Это связано с тем, что загружаемые файлы передаются как данные формы.
+```console
+$ pip install python-multipart
+```
+
+Это связано с тем, что загружаемые файлы передаются как "данные формы".
///
-## Импорт `File`
+## Импорт `File` { #import-file }
Импортируйте `File` и `UploadFile` из модуля `fastapi`:
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
-## Определите параметры `File`
+## Определите параметры `File` { #define-file-parameters }
Создайте параметры `File` так же, как вы это делаете для `Body` или `Form`:
@@ -46,7 +50,7 @@
Однако возможны случаи, когда использование `UploadFile` может оказаться полезным.
-## Загрузка файла с помощью `UploadFile`
+## Параметры файла с `UploadFile` { #file-parameters-with-uploadfile }
Определите параметр файла с типом `UploadFile`:
@@ -62,7 +66,7 @@
* Он реализует file-like `async` интерфейс.
* Он предоставляет реальный объект Python `SpooledTemporaryFile` который вы можете передать непосредственно другим библиотекам, которые ожидают файл в качестве объекта.
-### `UploadFile`
+### `UploadFile` { #uploadfile }
`UploadFile` имеет следующие атрибуты:
@@ -70,12 +74,12 @@
* `content_type`: Строка `str` с типом содержимого (MIME type / media type) (например, `image/jpeg`).
* `file`: `SpooledTemporaryFile` (a file-like объект). Это фактический файл Python, который можно передавать непосредственно другим функциям или библиотекам, ожидающим файл в качестве объекта.
-`UploadFile` имеет следующие методы `async`. Все они вызывают соответствующие файловые методы (используя внутренний SpooledTemporaryFile).
+`UploadFile` имеет следующие методы `async`. Все они вызывают соответствующие файловые методы (используя внутренний `SpooledTemporaryFile`).
* `write(data)`: Записать данные `data` (`str` или `bytes`) в файл.
* `read(size)`: Прочитать количество `size` (`int`) байт/символов из файла.
* `seek(offset)`: Перейти к байту на позиции `offset` (`int`) в файле.
- * Наример, `await myfile.seek(0)` перейдет к началу файла.
+ * Например, `await myfile.seek(0)` перейдет к началу файла.
* Это особенно удобно, если вы один раз выполнили команду `await myfile.read()`, а затем вам нужно прочитать содержимое файла еще раз.
* `close()`: Закрыть файл.
@@ -93,6 +97,7 @@ contents = await myfile.read()
contents = myfile.file.read()
```
+
/// note | Технические детали `async`
При использовании методов `async` **FastAPI** запускает файловые методы в пуле потоков и ожидает их.
@@ -105,7 +110,7 @@ contents = myfile.file.read()
///
-## Про данные формы ("Form Data")
+## Что такое «данные формы» { #what-is-form-data }
Способ, которым HTML-формы (``) отправляют данные на сервер, обычно использует "специальную" кодировку для этих данных, отличную от JSON.
@@ -117,7 +122,7 @@ contents = myfile.file.read()
Но когда форма включает файлы, она кодируется как multipart/form-data. Если вы используете `File`, **FastAPI** будет знать, что ему нужно получить файлы из нужной части тела.
-Если вы хотите узнать больше об этих кодировках и полях форм, перейдите по ссылке MDN web docs for POST.
+Если вы хотите узнать больше об этих кодировках и полях форм, перейдите по ссылке MDN web docs for POST.
///
@@ -129,19 +134,19 @@ contents = myfile.file.read()
///
-## Необязательная загрузка файлов
+## Необязательная загрузка файлов { #optional-file-upload }
Вы можете сделать загрузку файла необязательной, используя стандартные аннотации типов и установив значение по умолчанию `None`:
{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
-## `UploadFile` с дополнительными метаданными
+## `UploadFile` с дополнительными метаданными { #uploadfile-with-additional-metadata }
Вы также можете использовать `File()` вместе с `UploadFile`, например, для установки дополнительных метаданных:
{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
-## Загрузка нескольких файлов
+## Загрузка нескольких файлов { #multiple-file-uploads }
Можно одновременно загружать несколько файлов.
@@ -153,7 +158,7 @@ contents = myfile.file.read()
Вы получите, как и было объявлено, список `list` из `bytes` или `UploadFile`.
-/// note | Technical Details
+/// note | Технические детали
Можно также использовать `from starlette.responses import HTMLResponse`.
@@ -161,12 +166,12 @@ contents = myfile.file.read()
///
-### Загрузка нескольких файлов с дополнительными метаданными
+### Загрузка нескольких файлов с дополнительными метаданными { #multiple-file-uploads-with-additional-metadata }
Так же, как и раньше, вы можете использовать `File()` для задания дополнительных параметров, даже для `UploadFile`:
{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
-## Резюме
+## Резюме { #recap }
Используйте `File`, `bytes` и `UploadFile` для работы с файлами, которые будут загружаться и передаваться в виде данных формы.
diff --git a/docs/ru/docs/tutorial/request-form-models.md b/docs/ru/docs/tutorial/request-form-models.md
index 1034ed27f..f8c58356c 100644
--- a/docs/ru/docs/tutorial/request-form-models.md
+++ b/docs/ru/docs/tutorial/request-form-models.md
@@ -1,6 +1,6 @@
-# Модели форм
+# Модели форм { #form-models }
-Вы можете использовать **Pydantic-модели** для объявления **полей форм** в FastAPI.
+Вы можете использовать **Pydantic-модели** для объявления **полей формы** в FastAPI.
/// info | Дополнительная информация
@@ -16,11 +16,11 @@ $ pip install python-multipart
/// note | Заметка
-Этот функционал доступен с версии `0.113.0`. 🤓
+Этот функционал доступен начиная с версии FastAPI `0.113.0`. 🤓
///
-## Pydantic-модель для формы
+## Pydantic-модели для форм { #pydantic-models-for-forms }
Вам просто нужно объявить **Pydantic-модель** с полями, которые вы хотите получить как **поля формы**, а затем объявить параметр как `Form`:
@@ -28,21 +28,21 @@ $ pip install python-multipart
**FastAPI** **извлечёт** данные для **каждого поля** из **данных формы** в запросе и выдаст вам объявленную Pydantic-модель.
-## Проверка сгенерированной документации
+## Проверьте документацию { #check-the-docs }
-Вы можете посмотреть поля формы в графическом интерфейсе Документации по пути `/docs`:
+Вы можете проверить это в интерфейсе документации по адресу `/docs`:
-И также обе модели будут использованы в интерактивной документации 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}.
+Самый распространённый случай — [возвращать 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`.
+Этот простой случай обрабатывается FastAPI автоматически, потому что аннотация возвращаемого типа — это класс (или подкласс) `Response`.
-И ваши помощники разработки также будут счастливы, т.к. оба класса `RedirectResponse` и `JSONResponse` являются подклассами `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 автоматически обработает этот простейший случай.
+Это тоже сработает, так как `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`.
+...это не сработает, потому что аннотация типа не является типом Pydantic и это не единственный класс `Response` или его подкласс, а объединение (`union`) из `Response` и `dict`.
-### Возможно ли отключить генерацию модели ответа?
+### Отключить модель ответа { #disable-response-model }
-Продолжим рассматривать предыдущий пример. Допустим, что вы хотите отказаться от автоматической валидации ответа, документации, фильтрации и т.д.
+Продолжая пример выше, вы можете не хотеть использовать стандартные валидацию данных, документирование, фильтрацию и т.п., выполняемые FastAPI.
-Но в то же время, хотите сохранить аннотацию возвращаемого типа для функции, чтобы обеспечить работу помощников и анализаторов типов (например, mypy).
+Но при этом вы можете хотеть сохранить аннотацию возвращаемого типа в функции, чтобы пользоваться поддержкой инструментов вроде редакторов кода и инструментов проверки типов (например, mypy).
-В таком случае, вы можете отключить генерацию модели ответа, указав `response_model=None`:
+В этом случае вы можете отключить генерацию модели ответа, установив `response_model=None`:
{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *}
-Тогда FastAPI не станет генерировать модель ответа и вы сможете сохранить такую аннотацию типа, которая вам требуется, никак не влияя на работу FastAPI. 🤓
+Так FastAPI пропустит генерацию модели ответа, и вы сможете использовать любые аннотации возвращаемых типов, которые вам нужны, без влияния на ваше приложение FastAPI. 🤓
-## Параметры модели ответа
+## Параметры кодирования модели ответа { #response-model-encoding-parameters }
-Модель ответа может иметь значения по умолчанию, например:
+У вашей модели ответа могут быть значения по умолчанию, например:
{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *}
-* `description: Union[str, None] = None` (или `str | None = None` в Python 3.10), где `None` является значением по умолчанию.
-* `tax: float = 10.5`, где `10.5` является значением по умолчанию.
-* `tags: List[str] = []`, где пустой список `[]` является значением по умолчанию.
+* `description: Union[str, None] = None` (или `str | None = None` в Python 3.10) имеет значение по умолчанию `None`.
+* `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] *}
-и тогда значения по умолчанию не будут включены в ответ. В нем будут только те поля, значения которых фактически были установлены.
+и эти значения по умолчанию не будут включены в ответ — только те значения, которые действительно были установлены.
-Итак, если вы отправите запрос на данную *операцию пути* для элемента, с ID = `Foo` - ответ (с исключенными значениями по-умолчанию) будет таким:
+Итак, если вы отправите запрос к этой *операции пути* для элемента с ID `foo`, ответ (без значений по умолчанию) будет таким:
```JSON
{
@@ -242,24 +252,18 @@ FastAPI совместно с Pydantic выполнит некоторую ма
/// info | Информация
-"Под капотом" FastAPI использует метод `.dict()` у объектов моделей Pydantic с параметром `exclude_unset`, чтобы достичь такого эффекта.
-
-///
-
-/// info | Информация
-
Вы также можете использовать:
* `response_model_exclude_defaults=True`
* `response_model_exclude_none=True`
-как описано в документации Pydantic для параметров `exclude_defaults` и `exclude_none`.
+как описано в документации Pydantic для `exclude_defaults` и `exclude_none`.
///
-#### Если значение поля отличается от значения по-умолчанию
+#### Данные со значениями для полей, имеющих значения по умолчанию { #data-with-values-for-fields-with-defaults }
-Если для некоторых полей модели, имеющих значения по-умолчанию, значения были явно установлены - как для элемента с ID = `Bar`, ответ будет таким:
+Но если в ваших данных есть значения для полей модели, для которых указаны значения по умолчанию, как у элемента с ID `bar`:
```Python hl_lines="3 5"
{
@@ -270,11 +274,11 @@ FastAPI совместно с Pydantic выполнит некоторую ма
}
```
-они не будут исключены из ответа.
+они будут включены в ответ.
-#### Если значение поля совпадает с его значением по умолчанию
+#### Данные с такими же значениями, как значения по умолчанию { #data-with-the-same-values-as-the-defaults }
-Если данные содержат те же значения, которые являются для этих полей по умолчанию, но были установлены явно - как для элемента с ID = `baz`, ответ будет таким:
+Если данные имеют те же значения, что и значения по умолчанию, как у элемента с ID `baz`:
```Python hl_lines="3 5-6"
{
@@ -286,54 +290,54 @@ FastAPI совместно с Pydantic выполнит некоторую ма
}
```
-FastAPI достаточно умен (на самом деле, это заслуга Pydantic), чтобы понять, что, хотя `description`, `tax` и `tags` хранят такие же данные, какие должны быть по умолчанию - для них эти значения были установлены явно (а не получены из значений по умолчанию).
+FastAPI достаточно умен (на самом деле, это Pydantic), чтобы понять, что хотя `description`, `tax` и `tags` совпадают со значениями по умолчанию, они были установлены явно (а не взяты из значений по умолчанию).
-И поэтому, они также будут включены в JSON ответа.
+Поэтому они тоже будут включены в JSON-ответ.
-/// tip | Подсказка
+/// tip | Совет
-Значением по умолчанию может быть что угодно, не только `None`.
+Обратите внимание, что значения по умолчанию могут быть любыми, не только `None`.
-Им может быть и список (`[]`), значение 10.5 типа `float`, и т.п.
+Это может быть список (`[]`), число с плавающей точкой `10.5` и т.д.
///
-### `response_model_include` и `response_model_exclude`
+### `response_model_include` и `response_model_exclude` { #response-model-include-and-response-model-exclude }
-Вы также можете использовать параметры *декоратора операции пути*, такие, как `response_model_include` и `response_model_exclude`.
+Вы также можете использовать параметры *декоратора операции пути* `response_model_include` и `response_model_exclude`.
-Они принимают аргументы типа `set`, состоящий из строк (`str`) с названиями атрибутов, которые либо требуется включить в ответ (при этом исключив все остальные), либо наоборот исключить (оставив в ответе все остальные поля).
+Они принимают `set` из `str` с именами атрибутов, которые нужно включить (исключив остальные) или исключить (оставив остальные).
-Это можно использовать как быстрый способ исключить данные из ответа, не создавая отдельную модель Pydantic.
+Это можно использовать как быстрый способ, если у вас только одна Pydantic-модель и вы хотите убрать часть данных из ответа.
-/// tip | Подсказка
+/// tip | Совет
-Но по-прежнему рекомендуется следовать изложенным выше советам и использовать несколько моделей вместо данных параметров.
+Но всё же рекомендуется использовать подходы выше — несколько классов — вместо этих параметров.
-Потому как JSON схема OpenAPI, генерируемая вашим приложением (а также документация) все еще будет содержать все поля, даже если вы использовали `response_model_include` или `response_model_exclude` и исключили некоторые атрибуты.
+Потому что JSON Schema, генерируемая в OpenAPI вашего приложения (и документации), всё равно будет соответствовать полной модели, даже если вы используете `response_model_include` или `response_model_exclude`, чтобы опустить некоторые атрибуты.
-То же самое применимо к параметру `response_model_by_alias`.
+То же относится к `response_model_by_alias`, который работает аналогично.
///
{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *}
-/// tip | Подсказка
+/// tip | Совет
-При помощи кода `{"name","description"}` создается объект множества (`set`) с двумя строковыми значениями.
+Синтаксис `{"name", "description"}` создаёт `set` с этими двумя значениями.
-Того же самого можно достичь используя `set(["name", "description"])`.
+Это эквивалентно `set(["name", "description"])`.
///
-#### Что если использовать `list` вместо `set`?
+#### Использование `list` вместо `set` { #using-lists-instead-of-sets }
-Если вы забыли про `set` и использовали структуру `list` или `tuple`, FastAPI автоматически преобразует этот объект в `set`, чтобы обеспечить корректную работу:
+Если вы забыли использовать `set` и применили `list` или `tuple` вместо него, FastAPI всё равно преобразует это в `set`, и всё будет работать корректно:
{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *}
-## Резюме
+## Резюме { #recap }
-Используйте параметр `response_model` у *декоратора операции пути* для того, чтобы задать модель ответа и в большей степени для того, чтобы быть уверенным, что приватная информация будет отфильтрована.
+Используйте параметр `response_model` у *декоратора операции пути*, чтобы задавать модели ответа, и особенно — чтобы приватные данные отфильтровывались.
-А также используйте `response_model_exclude_unset`, чтобы возвращать только те значения, которые были заданы явно.
+Используйте `response_model_exclude_unset`, чтобы возвращать только те значения, которые были установлены явно.
diff --git a/docs/ru/docs/tutorial/response-status-code.md b/docs/ru/docs/tutorial/response-status-code.md
index b46f656f3..30f642b64 100644
--- a/docs/ru/docs/tutorial/response-status-code.md
+++ b/docs/ru/docs/tutorial/response-status-code.md
@@ -1,6 +1,6 @@
-# HTTP коды статуса ответа
+# Статус-код ответа { #response-status-code }
-Вы можете задать HTTP код статуса ответа с помощью параметра `status_code` подобно тому, как вы определяете схему ответа в любой из *операций пути*:
+Подобно тому, как вы можете задать модель/схему ответа, вы можете объявить HTTP статус-код, используемый для ответа, с помощью параметра `status_code` в любой из *операций пути*:
* `@app.get()`
* `@app.post()`
@@ -8,15 +8,15 @@
* `@app.delete()`
* и других.
-{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
+{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
/// note | Примечание
-Обратите внимание, что `status_code` является атрибутом метода-декоратора (`get`, `post` и т.д.), а не *функции-обработчика пути* в отличие от всех остальных параметров и тела запроса.
+Обратите внимание, что `status_code` — это параметр метода-декоратора (`get`, `post` и т.д.), а не вашей *функции-обработчика пути*, в отличие от всех остальных параметров и тела запроса.
///
-Параметр `status_code` принимает число, обозначающее HTTP код статуса ответа.
+Параметр `status_code` принимает число, обозначающее HTTP статус-код.
/// info | Информация
@@ -27,7 +27,7 @@
Это позволит:
* Возвращать указанный код статуса в ответе.
-* Документировать его как код статуса ответа в OpenAPI схеме (а значит, и в пользовательском интерфейсе):
+* Документировать его как код статуса ответа в OpenAPI схеме (а значит, и в пользовательских интерфейсах):
@@ -39,11 +39,11 @@ FastAPI знает об этом и создаст документацию Open
///
-## Об HTTP кодах статуса ответа
+## Об HTTP статус-кодах { #about-http-status-codes }
/// note | Примечание
-Если вы уже знаете, что представляют собой HTTP коды статуса ответа, можете перейти к следующему разделу.
+Если вы уже знаете, что представляют собой HTTP статус-коды, можете перейти к следующему разделу.
///
@@ -51,30 +51,30 @@ FastAPI знает об этом и создаст документацию Open
У кодов статуса есть названия, чтобы упростить их распознавание, но важны именно числовые значения.
-Кратко о значениях кодов:
+Кратко:
-* `1XX` – статус-коды информационного типа. Они редко используются разработчиками напрямую. Ответы с этими кодами не могут иметь тела.
-* **`2XX`** – статус-коды, сообщающие об успешной обработке запроса. Они используются чаще всего.
+* `100 - 199` – статус-коды информационного типа. Они редко используются разработчиками напрямую. Ответы с этими кодами не могут иметь тела.
+* **`200 - 299`** – статус-коды, сообщающие об успешной обработке запроса. Они используются чаще всего.
* `200` – это код статуса ответа по умолчанию, который означает, что все прошло "OK".
* Другим примером может быть статус `201`, "Created". Он обычно используется после создания новой записи в базе данных.
- * Особый случай – `204`, "No Content". Этот статус ответа используется, когда нет содержимого для возврата клиенту, и поэтому ответ не должен иметь тела.
-* **`3XX`** – статус-коды, сообщающие о перенаправлениях. Ответы с этими кодами статуса могут иметь или не иметь тело, за исключением ответов со статусом `304`, "Not Modified", у которых не должно быть тела.
-* **`4XX`** – статус-коды, сообщающие о клиентской ошибке. Это ещё одна наиболее часто используемая категория.
+ * Особый случай – `204`, "No Content". Этот статус ответа используется, когда нет содержимого для возврата клиенту, и поэтому ответ не должен иметь тела.
+* **`300 - 399`** – статус-коды, сообщающие о перенаправлениях. Ответы с этими кодами статуса могут иметь или не иметь тело, за исключением ответов со статусом `304`, "Not Modified", у которых не должно быть тела.
+* **`400 - 499`** – статус-коды, сообщающие о клиентской ошибке. Это ещё одна наиболее часто используемая категория.
* Пример – код `404` для статуса "Not Found".
* Для общих ошибок со стороны клиента можно просто использовать код `400`.
-* `5XX` – статус-коды, сообщающие о серверной ошибке. Они почти никогда не используются разработчиками напрямую. Когда что-то идет не так в какой-то части кода вашего приложения или на сервере, он автоматически вернёт один из 5XX кодов.
+* `500 - 599` – статус-коды, сообщающие о серверной ошибке. Они почти никогда не используются разработчиками напрямую. Когда что-то идет не так в какой-то части кода вашего приложения или на сервере, он автоматически вернёт один из этих кодов статуса.
/// tip | Подсказка
-Чтобы узнать больше о HTTP кодах статуса и о том, для чего каждый из них предназначен, ознакомьтесь с документацией MDN об HTTP кодах статуса ответа.
+Чтобы узнать больше о 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` – это код статуса "Создано".
@@ -82,9 +82,9 @@ 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] *}
-Они содержат те же числовые значения, но позволяют использовать подсказки редактора для выбора кода статуса:
+Они содержат те же числовые значения, но позволяют использовать автозавершение редактора кода для выбора кода статуса:
@@ -96,6 +96,6 @@ FastAPI знает об этом и создаст документацию Open
///
-## Изменение кода статуса по умолчанию
+## Изменение кода статуса по умолчанию { #changing-the-default }
-Позже, в [Руководстве для продвинутых пользователей](../advanced/response-change-status-code.md){.internal-link target=_blank}, вы узнаете, как возвращать HTTP коды статуса, отличные от используемого здесь кода статуса по умолчанию.
+Позже, в [Руководстве для продвинутых пользователей](../advanced/response-change-status-code.md){.internal-link target=_blank}, вы узнаете, как возвращать HTTP статус-код, отличный от значения по умолчанию, которое вы объявляете здесь.
diff --git a/docs/ru/docs/tutorial/schema-extra-example.md b/docs/ru/docs/tutorial/schema-extra-example.md
index f17b24349..e4a97c880 100644
--- a/docs/ru/docs/tutorial/schema-extra-example.md
+++ b/docs/ru/docs/tutorial/schema-extra-example.md
@@ -1,42 +1,48 @@
-# Объявление примера запроса данных
+# Объявление примеров данных запроса { #declare-request-example-data }
Вы можете объявлять примеры данных, которые ваше приложение может получать.
-Вот несколько способов, как это можно сделать.
+Вот несколько способов, как это сделать.
-## Pydantic `schema_extra`
+## Дополнительные данные JSON Schema в моделях Pydantic { #extra-json-schema-data-in-pydantic-models }
-Вы можете объявить ключ `example` для модели Pydantic, используя класс `Config` и переменную `schema_extra`, как описано в Pydantic документации: Настройка схемы:
+Вы можете объявить `examples` для модели Pydantic, которые будут добавлены в сгенерированную JSON Schema.
-{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:21] *}
+{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
-Эта дополнительная информация будет включена в **JSON Schema** выходных данных для этой модели, и она будет использоваться в документации к API.
+Эта дополнительная информация будет добавлена как есть в выходную **JSON Schema** этой модели и будет использоваться в документации API.
+
+Вы можете использовать атрибут `model_config`, который принимает `dict`, как описано в Документации Pydantic: Конфигурация.
+
+Вы можете задать `"json_schema_extra"` с `dict`, содержащим любые дополнительные данные, которые вы хотите видеть в сгенерированной JSON Schema, включая `examples`.
/// tip | Подсказка
-Вы можете использовать тот же метод для расширения JSON-схемы и добавления своей собственной дополнительной информации.
+Вы можете использовать тот же приём, чтобы расширить JSON Schema и добавить свою собственную дополнительную информацию.
-Например, вы можете использовать это для добавления дополнительной информации для пользовательского интерфейса в вашем веб-приложении и т.д.
+Например, вы можете использовать это, чтобы добавить метаданные для фронтенд‑пользовательского интерфейса и т.д.
///
-## Дополнительные аргументы поля `Field`
+/// info | Информация
-При использовании `Field()` с моделями Pydantic, вы также можете объявлять дополнительную информацию для **JSON Schema**, передавая любые другие произвольные аргументы в функцию.
+OpenAPI 3.1.0 (используется начиная с FastAPI 0.99.0) добавил поддержку `examples`, который является частью стандарта **JSON Schema**.
-Вы можете использовать это, чтобы добавить аргумент `example` для каждого поля:
+До этого поддерживалось только ключевое слово `example` с одним примером. Оно всё ещё поддерживается в OpenAPI 3.1.0, но помечено как устаревшее и не является частью стандарта JSON Schema. Поэтому рекомендуется мигрировать `example` на `examples`. 🤓
-{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *}
+Подробнее — в конце этой страницы.
-/// warning | Внимание
+///
-Имейте в виду, что эти дополнительные переданные аргументы не добавляют никакой валидации, только дополнительную информацию для документации.
+## Дополнительные аргументы `Field` { #field-additional-arguments }
-///
+При использовании `Field()` с моделями Pydantic вы также можете объявлять дополнительные `examples`:
+
+{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *}
-## Использование `example` и `examples` в OpenAPI
+## `examples` в JSON Schema — OpenAPI { #examples-in-json-schema-openapi }
-При использовании любой из этих функций:
+При использовании любой из функций:
* `Path()`
* `Query()`
@@ -46,65 +52,151 @@
* `Form()`
* `File()`
-вы также можете добавить аргумент, содержащий `example` или группу `examples` с дополнительной информацией, которая будет добавлена в **OpenAPI**.
+вы также можете объявить набор `examples` с дополнительной информацией, которая будет добавлена в их **JSON Schema** внутри **OpenAPI**.
-### Параметр `Body` с аргументом `example`
+### `Body` с `examples` { #body-with-examples }
-Здесь мы передаём аргумент `example`, как пример данных ожидаемых в параметре `Body()`:
+Здесь мы передаём `examples`, содержащий один пример данных, ожидаемых в `Body()`:
-{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:27] *}
+{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *}
-### Аргумент "example" в 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 Schema** для данных тела запроса.
+
+Тем не менее, на момент написания этого Swagger UI, инструмент, отвечающий за отображение UI документации, не поддерживает показ нескольких примеров для данных в **JSON Schema**. Но ниже есть обходной путь.
+
+### Специфические для OpenAPI `examples` { #openapi-specific-examples }
+
+Ещё до того как **JSON Schema** поддержала `examples`, в OpenAPI была поддержка другого поля, также называемого `examples`.
+
+Эти **специфические для OpenAPI** `examples` находятся в другой секции спецификации OpenAPI. Они находятся в **подробностях для каждой операции пути (обработчика пути)**, а не внутри каждого объекта Schema.
+
+И Swagger UI уже какое‑то время поддерживает именно это поле `examples`. Поэтому вы можете использовать его, чтобы **отобразить** разные **примеры в UI документации**.
+
+Структура этого специфичного для OpenAPI поля `examples` — это `dict` с **несколькими примерами** (вместо `list`), каждый с дополнительной информацией, которая также будет добавлена в **OpenAPI**.
+
+Это не помещается внутрь каждого объекта Schema в OpenAPI, это находится снаружи, непосредственно на уровне самой *операции пути*.
+
+### Использование параметра `openapi_examples` { #using-the-openapi-examples-parameter }
+
+Вы можете объявлять специфические для OpenAPI `examples` в FastAPI с помощью параметра `openapi_examples` для:
+
+* `Path()`
+* `Query()`
+* `Header()`
+* `Cookie()`
+* `Body()`
+* `Form()`
+* `File()`
-В качестве альтернативы одному аргументу `example`, вы можете передавать `examples` используя тип данных `dict` с **несколькими примерами**, каждый из которых содержит дополнительную информацию, которая также будет добавлена в **OpenAPI**.
+Ключи `dict` идентифицируют каждый пример, а каждое значение — это ещё один `dict`.
-Ключи `dict` указывают на каждый пример, а значения для каждого из них - на еще один тип `dict` с дополнительной информацией.
+Каждый конкретный пример `dict` в `examples` может содержать:
-Каждый конкретный пример типа `dict` в аргументе `examples` может содержать:
+* `summary`: Краткое описание примера.
+* `description`: Подробное описание, которое может содержать текст в Markdown.
+* `value`: Это фактический пример, который отображается, например, `dict`.
+* `externalValue`: альтернатива `value`, URL, указывающий на пример. Хотя это может поддерживаться не так многими инструментами, как `value`.
-* `summary`: Краткое описание для примера.
-* `description`: Полное описание, которое может содержать текст в формате Markdown.
-* `value`: Это конкретный пример, который отображается, например, в виде типа `dict`.
-* `externalValue`: альтернатива параметру `value`, URL-адрес, указывающий на пример. Хотя это может не поддерживаться таким же количеством инструментов разработки и тестирования API, как параметр `value`.
+Использовать это можно так:
-{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:49] *}
+{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *}
-### Аргумент "examples" в UI документации
+### OpenAPI-примеры в UI документации { #openapi-examples-in-the-docs-ui }
-С аргументом `examples`, добавленным в `Body()`, страница документации `/docs` будет выглядеть так:
+С `openapi_examples`, добавленным в `Body()`, страница `/docs` будет выглядеть так:
-## Технические Детали
+## Технические детали { #technical-details }
+
+/// tip | Подсказка
+
+Если вы уже используете **FastAPI** версии **0.99.0 или выше**, вы, вероятно, можете **пропустить** эти подробности.
+
+Они более актуальны для старых версий, до того как стала доступна OpenAPI 3.1.0.
+
+Считайте это кратким **уроком истории** про OpenAPI и JSON Schema. 🤓
+
+///
/// warning | Внимание
-Эти технические детали относятся к стандартам **JSON Schema** и **OpenAPI**.
+Далее идут очень технические подробности о стандартах **JSON Schema** и **OpenAPI**.
-Если предложенные выше идеи уже работают для вас, возможно этого будет достаточно и эти детали вам не потребуются, можете спокойно их пропустить.
+Если идеи выше уже работают для вас, этого может быть достаточно, и, вероятно, вам не нужны эти детали — смело пропускайте их.
///
-Когда вы добавляете пример внутрь модели Pydantic, используя `schema_extra` или `Field(example="something")`, этот пример добавляется в **JSON Schema** для данной модели Pydantic.
+До OpenAPI 3.1.0 OpenAPI использовала более старую и модифицированную версию **JSON Schema**.
+
+В JSON Schema не было `examples`, поэтому OpenAPI добавила собственное поле `example` в свою модифицированную версию.
+
+OpenAPI также добавила поля `example` и `examples` в другие части спецификации:
+
+* `Parameter Object` (в спецификации), которое использовалось в FastAPI:
+ * `Path()`
+ * `Query()`
+ * `Header()`
+ * `Cookie()`
+* `Request Body Object`, в поле `content`, в `Media Type Object` (в спецификации), которое использовалось в FastAPI:
+ * `Body()`
+ * `File()`
+ * `Form()`
+
+/// info | Информация
+
+Этот старый специфичный для OpenAPI параметр `examples` теперь называется `openapi_examples`, начиная с FastAPI `0.103.0`.
+
+///
+
+### Поле `examples` в JSON Schema { #json-schemas-examples-field }
+
+Позже в новой версии спецификации JSON Schema было добавлено поле `examples`.
+
+А затем новый OpenAPI 3.1.0 был основан на последней версии (JSON Schema 2020-12), которая включала это новое поле `examples`.
+
+И теперь это новое поле `examples` имеет приоритет над старым одиночным (и кастомным) полем `example`, которое теперь устарело.
+
+Это новое поле `examples` в JSON Schema — это **просто `list`** примеров, а не dict с дополнительными метаданными, как в других местах OpenAPI (описанных выше).
+
+/// info | Информация
+
+Даже после того как 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 { #pydantic-and-fastapi-examples }
+
+Когда вы добавляете `examples` внутри модели Pydantic, используя `schema_extra` или `Field(examples=["something"])`, этот пример добавляется в **JSON Schema** для этой модели Pydantic.
И эта **JSON Schema** модели Pydantic включается в **OpenAPI** вашего API, а затем используется в UI документации.
-Поля `example` как такового не существует в стандартах **JSON Schema**. В последних версиях JSON-схемы определено поле `examples`, но OpenAPI 3.0.3 основан на более старой версии JSON-схемы, которая не имела поля `examples`.
+В версиях 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 и выше, всё стало более последовательным, и примеры включаются в JSON Schema.
-Таким образом, OpenAPI 3.0.3 определяет своё собственное поле `example` для модифицированной версии **JSON Schema**, которую он использует чтобы достичь той же цели (однако это именно поле `example`, а не `examples`), и именно это используется API в UI документации (с интеграцией Swagger UI).
+### Swagger UI и специфичные для OpenAPI `examples` { #swagger-ui-and-openapi-specific-examples }
-Итак, хотя поле `example` не является частью JSON-схемы, оно является частью настраиваемой версии JSON-схемы в OpenAPI, и именно это поле будет использоваться в UI документации.
+Теперь, поскольку Swagger UI не поддерживал несколько примеров JSON Schema (по состоянию на 2023-08-26), у пользователей не было способа показать несколько примеров в документации.
-Однако, когда вы используете поле `example` или `examples` с любой другой функцией (`Query()`, `Body()`, и т.д.), эти примеры не добавляются в JSON-схему, которая описывает эти данные (даже в собственную версию JSON-схемы OpenAPI), они добавляются непосредственно в объявление *операции пути* в OpenAPI (вне частей OpenAPI, которые используют JSON-схему).
+Чтобы решить это, FastAPI `0.103.0` **добавил поддержку** объявления того же старого, **специфичного для OpenAPI**, поля `examples` с новым параметром `openapi_examples`. 🤓
-Для функций `Path()`, `Query()`, `Header()`, и `Cookie()`, аргументы `example` или `examples` добавляются в определение OpenAPI, к объекту `Parameter Object` (в спецификации).
+### Итог { #summary }
-И для функций `Body()`, `File()` и `Form()` аргументы `example` или `examples` аналогично добавляются в определение OpenAPI, к объекту `Request Body Object`, в поле `content` в объекте `Media Type Object` (в спецификации).
+Раньше я говорил, что не очень люблю историю... а теперь вот рассказываю «уроки технической истории». 😅
-С другой стороны, существует более новая версия OpenAPI: **3.1.0**, недавно выпущенная. Она основана на последней версии JSON-схемы и большинство модификаций из OpenAPI JSON-схемы удалены в обмен на новые возможности из последней версии JSON-схемы, так что все эти мелкие отличия устранены. Тем не менее, Swagger UI в настоящее время не поддерживает OpenAPI 3.1.0, поэтому пока лучше продолжать использовать вышеупомянутые методы.
+Коротко: **обновитесь до FastAPI 0.99.0 или выше** — так всё будет значительно **проще, последовательнее и интуитивнее**, и вам не придётся знать все эти исторические подробности. 😎
diff --git a/docs/ru/docs/tutorial/security/first-steps.md b/docs/ru/docs/tutorial/security/first-steps.md
index 375c2d7f6..983e85e66 100644
--- a/docs/ru/docs/tutorial/security/first-steps.md
+++ b/docs/ru/docs/tutorial/security/first-steps.md
@@ -1,52 +1,58 @@
-# Безопасность - первые шаги
+# Безопасность — первые шаги { #security-first-steps }
-Представим, что у вас есть свой **бэкенд** API на некотором домене.
+Представим, что у вас есть **бэкенд** API на некотором домене.
И у вас есть **фронтенд** на другом домене или на другом пути того же домена (или в мобильном приложении).
-И вы хотите иметь возможность аутентификации фронтенда с бэкендом, используя **имя пользователя** и **пароль**.
+И вы хотите, чтобы фронтенд мог аутентифицироваться на бэкенде, используя **имя пользователя** и **пароль**.
-Мы можем использовать **OAuth2** для создания такой системы с помощью **FastAPI**.
+Мы можем использовать **OAuth2**, чтобы построить это с **FastAPI**.
-Но давайте избавим вас от необходимости читать всю длинную спецификацию, чтобы найти те небольшие кусочки информации, которые вам нужны.
+Но давайте сэкономим вам время на чтение всей длинной спецификации в поисках тех небольших фрагментов информации, которые вам нужны.
-Для работы с безопасностью воспользуемся средствами, предоставленными **FastAPI**.
+Воспользуемся инструментами, предоставленными **FastAPI**, чтобы работать с безопасностью.
-## Как это выглядит
+## Как это выглядит { #how-it-looks }
-Давайте сначала просто воспользуемся кодом и посмотрим, как он работает, а затем детально разберём, что происходит.
+Сначала просто воспользуемся кодом и посмотрим, как он работает, а затем вернемся и разберемся, что происходит.
-## Создание `main.py`
+## Создание `main.py` { #create-main-py }
Скопируйте пример в файл `main.py`:
{* ../../docs_src/security/tutorial001_an_py39.py *}
-## Запуск
+## Запуск { #run-it }
/// info | Дополнительная информация
-Вначале, установите библиотеку `python-multipart`.
+Пакет `python-multipart` автоматически устанавливается вместе с **FastAPI**, если вы запускаете команду `pip install "fastapi[standard]"`.
-А именно: `pip install python-multipart`.
+Однако, если вы используете команду `pip install fastapi`, пакет `python-multipart` по умолчанию не включается.
-Это связано с тем, что **OAuth2** использует "данные формы" для передачи `имени пользователя` и `пароля`.
+Чтобы установить его вручную, убедитесь, что вы создали [виртуальное окружение](../../virtual-environments.md){.internal-link target=_blank}, активировали его и затем установили пакет:
+
+```console
+$ pip install python-multipart
+```
+
+Это связано с тем, что **OAuth2** использует «данные формы» для отправки `username` и `password`.
///
-Запустите ваш сервер:
+Запустите пример командой:
-/// note | Технические детали
+/// note | Примечание
-Неважно, что вы введете в форму, она пока не будет работать. Но мы к этому еще придем.
+Неважно, что вы введете в форму — пока это не будет работать. Но мы скоро до этого дойдем.
///
-Конечно, это не фронтенд для конечных пользователей, но это отличный автоматический инструмент для интерактивного документирования всех ваших API.
+Конечно, это не фронтенд для конечных пользователей, но это отличный автоматический инструмент для интерактивного документирования всего вашего API.
-Он может использоваться командой фронтенда (которой можете быть и вы сами).
+Им может пользоваться команда фронтенда (которой можете быть и вы сами).
-Он может быть использован сторонними приложениями и системами.
+Им могут пользоваться сторонние приложения и системы.
-Кроме того, его можно использовать самостоятельно для отладки, проверки и тестирования одного и того же приложения.
+И им также можете пользоваться вы сами, чтобы отлаживать, проверять и тестировать то же самое приложение.
-## Аутентификация по паролю
+## «`password` flow» (аутентификация по паролю) { #the-password-flow }
-Теперь давайте вернемся немного назад и разберемся, что же это такое.
+Теперь давайте немного вернемся и разберемся, что это все такое.
-Аутентификация по паролю является одним из способов, определенных в OAuth2, для обеспечения безопасности и аутентификации.
+«`password` flow» — это один из способов («flows»), определенных в OAuth2, для обеспечения безопасности и аутентификации.
-OAuth2 был разработан для того, чтобы бэкэнд или API были независимы от сервера, который аутентифицирует пользователя.
+OAuth2 был спроектирован так, чтобы бэкенд или API были независимы от сервера, который аутентифицирует пользователя.
-Но в нашем случае одно и то же приложение **FastAPI** будет работать с API и аутентификацией.
+Но в нашем случае одно и то же приложение **FastAPI** будет работать и с API, и с аутентификацией.
-Итак, рассмотрим его с этой упрощенной точки зрения:
+Итак, рассмотрим это с упрощенной точки зрения:
-* Пользователь вводит на фронтенде `имя пользователя` и `пароль` и нажимает `Enter`.
-* Фронтенд (работающий в браузере пользователя) отправляет эти `имя пользователя` и `пароль` на определенный URL в нашем API (объявленный с помощью параметра `tokenUrl="token"`).
-* API проверяет эти `имя пользователя` и `пароль` и выдает в ответ "токен" (мы еще не реализовали ничего из этого).
- * "Токен" - это просто строка с некоторым содержимым, которое мы можем использовать позже для верификации пользователя.
- * Обычно срок действия токена истекает через некоторое время.
- * Таким образом, пользователю придется снова войти в систему в какой-то момент времени.
- * И если токен будет украден, то риск будет меньше, так как он не похож на постоянный ключ, который будет работать вечно (в большинстве случаев).
-* Фронтенд временно хранит этот токен в каком-то месте.
-* Пользователь щелкает мышью на фронтенде, чтобы перейти в другой раздел на фронтенде.
-* Фронтенду необходимо получить дополнительные данные из API.
- * Но для этого необходима аутентификация для конкретной конечной точки.
- * Поэтому для аутентификации в нашем API он посылает заголовок `Authorization` со значением `Bearer` плюс сам токен.
- * Если токен содержит `foobar`, то содержание заголовка `Authorization` будет таким: `Bearer foobar`.
+* Пользователь вводит на фронтенде `username` и `password` и нажимает `Enter`.
+* Фронтенд (работающий в браузере пользователя) отправляет эти `username` и `password` на конкретный URL в нашем API (объявленный с `tokenUrl="token"`).
+* API проверяет этот `username` и `password` и отвечает «токеном» (мы еще ничего из этого не реализовали).
+ * «Токен» — это просто строка с некоторым содержимым, которое мы сможем позже использовать для проверки этого пользователя.
+ * Обычно у токена установлен срок действия: он истекает через некоторое время.
+ * Поэтому пользователю придется снова войти в систему в какой‑то момент.
+ * И если токен украдут, риск меньше: это не постоянный ключ, который будет работать вечно (в большинстве случаев).
+* Фронтенд временно где‑то хранит этот токен.
+* Пользователь кликает во фронтенде, чтобы перейти в другой раздел веб‑приложения.
+* Фронтенду нужно получить дополнительные данные из API.
+ * Но для этого для конкретной конечной точки нужна аутентификация.
+ * Поэтому, чтобы аутентифицироваться в нашем API, он отправляет HTTP-заголовок `Authorization` со значением `Bearer ` плюс сам токен.
+ * Если токен содержит `foobar`, то содержимое заголовка `Authorization` будет: `Bearer foobar`.
-## Класс `OAuth2PasswordBearer` в **FastAPI**
+## Класс `OAuth2PasswordBearer` в **FastAPI** { #fastapis-oauth2passwordbearer }
**FastAPI** предоставляет несколько средств на разных уровнях абстракции для реализации этих функций безопасности.
-В данном примере мы будем использовать **OAuth2**, с аутентификацией по паролю, используя токен **Bearer**. Для этого мы используем класс `OAuth2PasswordBearer`.
+В этом примере мы будем использовать **OAuth2**, с потоком **Password**, используя токен **Bearer**. Для этого мы используем класс `OAuth2PasswordBearer`.
/// info | Дополнительная информация
-Токен "bearer" - не единственный вариант, но для нашего случая он является наилучшим.
+Токен «bearer» — не единственный вариант.
+
+Но для нашего случая он — лучший.
-И это может быть лучшим вариантом для большинства случаев использования, если только вы не являетесь экспертом в области OAuth2 и точно знаете, почему вам лучше подходит какой-то другой вариант.
+И он может быть лучшим для большинства случаев использования, если только вы не являетесь экспертом по OAuth2 и точно знаете, почему другой вариант лучше подходит под ваши нужды.
-В этом случае **FastAPI** также предоставляет инструменты для его реализации.
+В этом случае **FastAPI** также предоставляет инструменты, чтобы его реализовать.
///
-При создании экземпляра класса `OAuth2PasswordBearer` мы передаем в него параметр `tokenUrl`. Этот параметр содержит URL, который клиент (фронтенд, работающий в браузере пользователя) будет использовать для отправки `имени пользователя` и `пароля` с целью получения токена.
+При создании экземпляра класса `OAuth2PasswordBearer` мы передаем параметр `tokenUrl`. Этот параметр содержит URL, который клиент (фронтенд, работающий в браузере пользователя) будет использовать для отправки `username` и `password`, чтобы получить токен.
{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *}
@@ -130,27 +138,27 @@ OAuth2 был разработан для того, чтобы бэкэнд ил
Здесь `tokenUrl="token"` ссылается на относительный URL `token`, который мы еще не создали. Поскольку это относительный URL, он эквивалентен `./token`.
-Поскольку мы используем относительный URL, если ваш API расположен по адресу `https://example.com/`, то он будет ссылаться на `https://example.com/token`. Если же ваш API расположен по адресу `https://example.com/api/v1/`, то он будет ссылаться на `https://example.com/api/v1/token`.
+Поскольку мы используем относительный URL, если ваш API расположен по адресу `https://example.com/`, то он будет ссылаться на `https://example.com/token`. А если ваш API расположен по адресу `https://example.com/api/v1/`, то он будет ссылаться на `https://example.com/api/v1/token`.
-Использование относительного URL важно для того, чтобы ваше приложение продолжало работать даже в таких сложных случаях, как оно находится [за прокси-сервером](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
+Использование относительного URL важно для того, чтобы ваше приложение продолжало работать даже в таком продвинутом случае, как [За прокси-сервером](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
///
-Этот параметр не создает конечную точку / *операцию пути*, а объявляет, что URL `/token` будет таким, который клиент должен использовать для получения токена. Эта информация используется в OpenAPI, а затем в интерактивных системах документации API.
+Этот параметр не создает конечную точку / *операцию пути*, а объявляет, что URL `/token` — это тот, который клиент должен использовать для получения токена. Эта информация используется в OpenAPI, а затем в интерактивных системах документации по API.
-Вскоре мы создадим и саму операцию пути.
+Скоро мы также создадим и саму операцию пути.
/// info | Дополнительная информация
-Если вы очень строгий "питонист", то вам может не понравиться стиль названия параметра `tokenUrl` вместо `token_url`.
+Если вы очень строгий «питонист», вам может не понравиться стиль имени параметра `tokenUrl` вместо `token_url`.
-Это связано с тем, что тут используется то же имя, что и в спецификации OpenAPI. Таким образом, если вам необходимо более подробно изучить какую-либо из этих схем безопасности, вы можете просто использовать копирование/вставку, чтобы найти дополнительную информацию о ней.
+Это потому, что используется то же имя, что и в спецификации OpenAPI. Так, если вам нужно разобраться подробнее в какой‑то из этих схем безопасности, вы можете просто скопировать и вставить это имя, чтобы найти больше информации.
///
-Переменная `oauth2_scheme` является экземпляром `OAuth2PasswordBearer`, но она также является "вызываемой".
+Переменная `oauth2_scheme` — это экземпляр `OAuth2PasswordBearer`, но она также «вызываемая».
-Ее можно вызвать следующим образом:
+Ее можно вызвать так:
```Python
oauth2_scheme(some, parameters)
@@ -158,38 +166,38 @@ oauth2_scheme(some, parameters)
Поэтому ее можно использовать вместе с `Depends`.
-### Использование
+### Использование { #use-it }
-Теперь вы можете передать ваш `oauth2_scheme` в зависимость с помощью `Depends`.
+Теперь вы можете передать `oauth2_scheme` как зависимость с `Depends`.
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
-Эта зависимость будет предоставлять `строку`, которая присваивается параметру `token` в *функции операции пути*.
+Эта зависимость предоставит `str`, который будет присвоен параметру `token` *функции-обработчика пути*.
-**FastAPI** будет знать, что он может использовать эту зависимость для определения "схемы безопасности" в схеме OpenAPI (и автоматической документации по API).
+**FastAPI** будет знать, что может использовать эту зависимость для определения «схемы безопасности» в схеме OpenAPI (и в автоматической документации по API).
/// info | Технические детали
-**FastAPI** будет знать, что он может использовать класс `OAuth2PasswordBearer` (объявленный в зависимости) для определения схемы безопасности в OpenAPI, поскольку он наследуется от `fastapi.security.oauth2.OAuth2`, который, в свою очередь, наследуется от `fastapi.security.base.SecurityBase`.
+**FastAPI** будет знать, что может использовать класс `OAuth2PasswordBearer` (объявленный в зависимости) для определения схемы безопасности в OpenAPI, потому что он наследуется от `fastapi.security.oauth2.OAuth2`, который, в свою очередь, наследуется от `fastapi.security.base.SecurityBase`.
-Все утилиты безопасности, интегрируемые в OpenAPI (и автоматическая документация по API), наследуются от `SecurityBase`, поэтому **FastAPI** может знать, как интегрировать их в OpenAPI.
+Все утилиты безопасности, интегрируемые с OpenAPI (и автоматической документацией по API), наследуются от `SecurityBase`, — так **FastAPI** понимает, как интегрировать их в OpenAPI.
///
-## Что он делает
+## Что он делает { #what-it-does }
-Он будет искать в запросе заголовок `Authorization` и проверять, содержит ли он значение `Bearer` с некоторым токеном, и возвращать токен в виде `строки`.
+Он будет искать в запросе заголовок `Authorization`, проверять, что его значение — это `Bearer ` плюс некоторый токен, и вернет токен как `str`.
-Если он не видит заголовка `Authorization` или значение не имеет токена `Bearer`, то в ответ будет выдана ошибка с кодом состояния 401 (`UNAUTHORIZED`).
+Если заголовок `Authorization` отсутствует или его значение не содержит токен `Bearer `, он сразу ответит ошибкой со статус-кодом 401 (`UNAUTHORIZED`).
-Для возврата ошибки даже не нужно проверять, существует ли токен. Вы можете быть уверены, что если ваша функция была выполнена, то в этом токене есть `строка`.
+Вам даже не нужно проверять наличие токена, чтобы вернуть ошибку. Вы можете быть уверены: если ваша функция была выполнена, в этом токене будет `str`.
-Проверить это можно уже сейчас в интерактивной документации:
+Это уже можно попробовать в интерактивной документации:
-Мы пока не проверяем валидность токена, но для начала неплохо.
+Мы пока не проверяем валидность токена, но для начала это уже неплохо.
-## Резюме
+## Резюме { #recap }
-Таким образом, всего за 3-4 дополнительные строки вы получаете некую примитивную форму защиты.
+Таким образом, всего за 3–4 дополнительные строки у вас уже есть некая примитивная форма защиты.
diff --git a/docs/ru/docs/tutorial/security/get-current-user.md b/docs/ru/docs/tutorial/security/get-current-user.md
index 05eb290d7..c6bc07cc1 100644
--- a/docs/ru/docs/tutorial/security/get-current-user.md
+++ b/docs/ru/docs/tutorial/security/get-current-user.md
@@ -1,99 +1,105 @@
-# Данные текущего пользователя
+# Получить текущего пользователя { #get-current-user }
-В предыдущей главе система безопасности (основанная на системе внедрения зависимостей) передавала *функции, обрабатывающей эндпоинт,* `токен` в виде `строки`:
+В предыдущей главе система безопасности (основанная на системе внедрения зависимостей) передавала *функции-обработчику пути* `token` типа `str`:
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
-Это пока что не слишком нам полезно. Давайте изменим код так, чтобы он возвращал нам данные пользователя, отправившего запрос.
+Но это всё ещё не слишком полезно.
-## Создание модели пользователя
+Сделаем так, чтобы она возвращала текущего пользователя.
+
+## Создать модель пользователя { #create-a-user-model }
Сначала создадим Pydantic-модель пользователя.
-Точно так же, как мы использовали Pydantic для объявления тел запросов, мы можем использовать его где угодно:
+Точно так же, как мы используем Pydantic для объявления тел запросов, мы можем использовать его где угодно:
{* ../../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` будет получать `token` в виде `строки` от подзависимости `oauth2_scheme`:
+Аналогично тому, как мы делали ранее прямо в *операции пути*, новая зависимость `get_current_user` получит `token` типа `str` от подзависимости `oauth2_scheme`:
{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *}
-## Получение данных пользователя
+## Получить пользователя { #get-the-user }
-`get_current_user` будет использовать созданную нами (ненастоящую) служебную функцию, которая принимает токен в виде `строки` и возвращает нашу Pydantic-модель `User`:
+`get_current_user` будет использовать созданную нами (ненастоящую) служебную функцию, которая принимает токен типа `str` и возвращает нашу Pydantic-модель `User`:
{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *}
-## Внедрение зависимости текущего пользователя
+## Внедрить текущего пользователя { #inject-the-current-user }
-Теперь мы можем использовать тот же `Depends` с нашей зависимостью `get_current_user` в *функции обрабатывающей эндпоинт*:
+Теперь мы можем использовать тот же `Depends` с нашей `get_current_user` в *операции пути*:
{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *}
-Обратите внимание, что мы объявляем тип переменной `current_user` как Pydantic-модель `User`.
+Обратите внимание, что мы объявляем тип `current_user` как Pydantic-модель `User`.
-Это поможет выполнить внутри функции все проверки автозаполнения и типа.
+Это поможет внутри функции с автозавершением и проверками типов.
/// tip | Подсказка
-Возможно, вы помните, что тело запроса также объявляется с помощью Pydantic-моделей.
-В этом месте у **FastAPI** не возникнет проблем, потому что вы используете `Depends`.
+Возможно, вы помните, что тела запросов также объявляются с помощью Pydantic-моделей.
+
+Здесь **FastAPI** не запутается, потому что вы используете `Depends`.
+
///
/// check | Заметка
-То, как устроена эта система зависимостей, позволяет нам иметь различные зависимости, которые возвращают модель `User`.
-Мы не ограничены наличием только одной зависимости, которая может возвращать данные такого типа.
+То, как устроена эта система зависимостей, позволяет иметь разные зависимости, которые возвращают модель `User`.
+
+Мы не ограничены наличием только одной зависимости, которая может возвращать такой тип данных.
+
///
-## Другие модели
+## Другие модели { #other-models }
-Теперь вы можете получать информацию о текущем пользователе непосредственно в *функции обрабатывающей эндпоинт* и работать с механизмами безопасности на уровне **Внедрения Зависимостей**, используя `Depends`.
+Теперь вы можете получать текущего пользователя напрямую в *функциях-обработчиках пути* и работать с механизмами безопасности на уровне **внедрения зависимостей**, используя `Depends`.
-Причем для обеспечения требований безопасности можно использовать любую модель или данные (в данном случае - Pydantic-модель `User`).
+И вы можете использовать любую модель или данные для требований безопасности (в данном случае Pydantic-модель `User`).
-Но вы не ограничены использованием какой-то конкретной моделью данных, классом или типом.
+Но вы не ограничены использованием какой-то конкретной модели данных, класса или типа.
-Вы хотите использовать в модели `id` и `email`, а `username` вам не нужен? Ну разумеется. Воспользуйтесь тем же инструментарием.
+Хотите, чтобы в модели были `id` и `email`, но не было `username`? Пожалуйста. Можно использовать те же инструменты.
-Вам нужны только `строки`? Или только `словари`? Или непосредственно экземпляр модели класса базы данных? Все это работает точно также.
+Хотите просто `str`? Или просто `dict`? Или напрямую экземпляр класса модели базы данных? Всё работает одинаково.
-У вас нет пользователей, которые входят в ваше приложение, а только роботы, боты или другие системы, у которых есть только токен доступа? Опять же, все работает одинаково.
+У вас вообще нет пользователей, которые входят в приложение, а есть роботы, боты или другие системы, у которых есть только токен доступа? Снова — всё работает так же.
-Просто используйте любую модель, любой класс, любую базу данных, которые нужны для вашего приложения. Система внедрения зависимостей **FastAPI** поможет вам в этом.
+Просто используйте любую модель, любой класс, любую базу данных, которые нужны вашему приложению. Система внедрения зависимостей **FastAPI** поможет вам в этом.
-## Размер кода
+## Размер кода { #code-size }
-Этот пример может показаться многословным. Следует иметь в виду, что в одном файле мы смешиваем безопасность, модели данных, служебные функции и *эндпоинты*.
+Этот пример может показаться многословным. Имейте в виду, что в одном файле мы смешиваем безопасность, модели данных, служебные функции и *операции пути*.
-Но вот ключевой момент:
+Но вот ключевой момент.
-Все, что касается безопасности и внедрения зависимостей, пишется один раз.
+Всё, что касается безопасности и внедрения зависимостей, пишется один раз.
-И вы можете сделать его настолько сложным, насколько захотите. И все это будет написано только один раз, в одном месте, со всей своей гибкостью.
+И вы можете сделать это настолько сложным, насколько захотите. И всё равно это будет написано только один раз, в одном месте. Со всей гибкостью.
-И у вас могут быть тысячи конечных точек (*эндпоинтов*), использующих одну и ту же систему безопасности.
+При этом у вас могут быть тысячи эндпоинтов (*операций пути*), использующих одну и ту же систему безопасности.
И все они (или любая их часть по вашему желанию) могут воспользоваться преимуществами повторного использования этих зависимостей или любых других зависимостей, которые вы создадите.
-И все эти тысячи *эндпоинтов* могут составлять всего 3 строки:
+И все эти тысячи *операций пути* могут состоять всего из 3 строк:
{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
-## Резюме
+## Резюме { #recap }
-Теперь вы можете получать данные о текущем пользователе непосредственно в своей *функции обработчике эндпоинта*.
+Теперь вы можете получать текущего пользователя прямо в своей *функции-обработчике пути*.
-Мы уже на полпути к этому.
+Мы уже на полпути.
-Осталось лишь добавить *эндпоинт* для отправки пользователем/клиентом своих `имени пользователя` и `пароля`.
+Нужно лишь добавить *операцию пути*, чтобы пользователь/клиент мог отправить `username` и `password`.
-Это будет рассмотрено в следующем разделе.
+Это будет дальше.
diff --git a/docs/ru/docs/tutorial/security/index.md b/docs/ru/docs/tutorial/security/index.md
index 8fb4bf24f..ebac013b6 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/security/oauth2-jwt.md b/docs/ru/docs/tutorial/security/oauth2-jwt.md
index 57f973d2c..803491f53 100644
--- a/docs/ru/docs/tutorial/security/oauth2-jwt.md
+++ b/docs/ru/docs/tutorial/security/oauth2-jwt.md
@@ -1,12 +1,12 @@
-# OAuth2 с паролем (и хешированием), Bearer с JWT-токенами
+# OAuth2 с паролем (и хешированием), Bearer с JWT-токенами { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens }
-Теперь, когда у нас определен процесс обеспечения безопасности, давайте сделаем приложение действительно безопасным, используя токены JWT и безопасное хеширование паролей.
+Теперь, когда у нас определен процесс обеспечения безопасности, давайте сделаем приложение действительно безопасным, используя токены JWT и безопасное хеширование паролей.
Этот код можно реально использовать в своем приложении, сохранять хэши паролей в базе данных и т.д.
Мы продолжим разбираться, начиная с того места, на котором остановились в предыдущей главе.
-## Про JWT
+## Про JWT { #about-jwt }
JWT означает "JSON Web Tokens".
@@ -26,7 +26,7 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4
Если вы хотите поиграть с JWT-токенами и посмотреть, как они работают, посмотрите https://jwt.io.
-## Установка `PyJWT`
+## Установка `PyJWT` { #install-pyjwt }
Нам необходимо установить `pyjwt` для генерации и проверки JWT-токенов на языке Python.
@@ -45,10 +45,10 @@ $ pip install pyjwt
/// info | Дополнительная информация
Если вы планируете использовать алгоритмы цифровой подписи, такие как RSA или ECDSA, вам следует установить зависимость библиотеки криптографии `pyjwt[crypto]`.
-Подробнее об этом можно прочитать в документации по установке PyJWT.
+Подробнее об этом можно прочитать в документации по установке PyJWT.
///
-## Хеширование паролей
+## Хеширование паролей { #password-hashing }
"Хеширование" означает преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.
@@ -56,26 +56,26 @@ $ pip install pyjwt
Но преобразовать тарабарщину обратно в пароль невозможно.
-### Для чего нужно хеширование паролей
+### Для чего нужно хеширование паролей { #why-use-password-hashing }
Если ваша база данных будет украдена, то вор не получит пароли пользователей в открытом виде, а только их хэши.
Таким образом, вор не сможет использовать этот пароль в другой системе (поскольку многие пользователи везде используют один и тот же пароль, это было бы опасно).
-## Установка `passlib`
+## Установка `pwdlib` { #install-pwdlib }
-PassLib - это отличный пакет Python для работы с хэшами паролей.
+pwdlib — это отличный пакет Python для работы с хэшами паролей.
Он поддерживает множество безопасных алгоритмов хеширования и утилит для работы с ними.
-Рекомендуемый алгоритм - "Bcrypt".
+Рекомендуемый алгоритм — "Argon2".
-Убедитесь, что вы создали и активировали виртуальное окружение, и затем установите PassLib вместе с Bcrypt:
+Убедитесь, что вы создали [виртуальное окружение](../../virtual-environments.md){.internal-link target=_blank}, активируйте его, и затем установите pwdlib вместе с Argon2:
/// note | Техническая информация
-Обратите внимание на заголовок `Authorization`, значение которого начинается с `Bearer`.
+Обратите внимание на HTTP-заголовок `Authorization`, значение которого начинается с `Bearer `.
///
-## Продвинутое использование `scopes`
+## Продвинутое использование `scopes` { #advanced-usage-with-scopes }
В OAuth2 существует понятие "диапазоны" ("`scopes`").
@@ -236,9 +236,9 @@ Password: `secret`
Затем вы можете передать этот токен непосредственно пользователю или третьей стороне для взаимодействия с вашим API с определенным набором ограничений.
-О том, как их использовать и как они интегрированы в **FastAPI**, читайте далее в **Руководстве пользователя**.
+О том, как их использовать и как они интегрированы в **FastAPI**, читайте далее в **Расширенном руководстве пользователя**.
-## Резюме
+## Резюме { #recap }
С учетом того, что вы видели до сих пор, вы можете создать безопасное приложение **FastAPI**, используя такие стандарты, как OAuth2 и JWT.
@@ -252,10 +252,10 @@ Password: `secret`
Он предоставляет вам полную свободу действий, позволяя выбирать то, что лучше всего подходит для вашего проекта.
-Вы можете напрямую использовать многие хорошо поддерживаемые и широко распространенные пакеты, такие как `passlib` и `PyJWT`, поскольку **FastAPI** не требует сложных механизмов для интеграции внешних пакетов.
+Вы можете напрямую использовать многие хорошо поддерживаемые и широко распространенные пакеты, такие как `pwdlib` и `PyJWT`, поскольку **FastAPI** не требует сложных механизмов для интеграции внешних пакетов.
Напротив, он предоставляет инструменты, позволяющие максимально упростить этот процесс без ущерба для гибкости, надежности и безопасности.
При этом вы можете использовать и реализовывать безопасные стандартные протоколы, такие как OAuth2, относительно простым способом.
-В **Руководстве пользователя** вы можете узнать больше о том, как использовать "диапазоны" ("`scopes`") OAuth2 для создания более точно настроенной системы разрешений в соответствии с теми же стандартами. OAuth2 с диапазонами - это механизм, используемый многими крупными провайдерами сервиса аутентификации, такими как Facebook, Google, GitHub, Microsoft, X (Twitter) и др., для авторизации сторонних приложений на взаимодействие с их API от имени их пользователей.
+В **Расширенном руководстве пользователя** вы можете узнать больше о том, как использовать "диапазоны" ("`scopes`") OAuth2 для создания более точно настроенной системы разрешений в соответствии с теми же стандартами. OAuth2 с диапазонами — это механизм, используемый многими крупными провайдерами сервиса аутентификации, такими как Facebook, Google, GitHub, Microsoft, X (Twitter) и др., для авторизации сторонних приложений на взаимодействие с их API от имени их пользователей.
diff --git a/docs/ru/docs/tutorial/security/simple-oauth2.md b/docs/ru/docs/tutorial/security/simple-oauth2.md
index 9732265cc..36ff32c8e 100644
--- a/docs/ru/docs/tutorial/security/simple-oauth2.md
+++ b/docs/ru/docs/tutorial/security/simple-oauth2.md
@@ -1,59 +1,58 @@
-# Простая авторизация по протоколу OAuth2 с токеном типа Bearer
+# Простая авторизация OAuth2 с паролем и «Bearer» { #simple-oauth2-with-password-and-bearer }
-Теперь, отталкиваясь от предыдущей главы, добавим недостающие части, чтобы получить безопасную систему.
+Теперь, отталкиваясь от предыдущей главы, добавим недостающие части, чтобы получить полный поток безопасности.
-## Получение `имени пользователя` и `пароля`
+## Получение `username` и `password` { #get-the-username-and-password }
-Для получения `имени пользователя` и `пароля` мы будем использовать утилиты безопасности **FastAPI**.
+Для получения `username` и `password` мы будем использовать утилиты безопасности **FastAPI**.
-Протокол OAuth2 определяет, что при использовании "аутентификации по паролю" (которую мы и используем) клиент/пользователь должен передавать поля `username` и `password` в полях формы.
+OAuth2 определяет, что при использовании "password flow" (аутентификация по паролю - именно его мы используем) клиент/пользователь должен передавать поля `username` и `password` в полях формы.
В спецификации сказано, что поля должны быть названы именно так. Поэтому `user-name` или `email` работать не будут.
-Но не волнуйтесь, вы можете показать его конечным пользователям во фронтенде в том виде, в котором хотите.
+Но не волнуйтесь, вы можете показать это конечным пользователям во фронтенде в том виде, в котором хотите.
А ваши модели баз данных могут использовать любые другие имена.
-Но при авторизации согласно спецификации, требуется использовать именно эти имена, что даст нам возможность воспользоваться встроенной системой документации API.
+Но для логин-операции пути нам нужно использовать именно эти имена, чтобы быть совместимыми со спецификацией (и иметь возможность, например, использовать встроенную систему документации API).
В спецификации также указано, что `username` и `password` должны передаваться в виде данных формы (так что никакого JSON здесь нет).
-### Oбласть видимости (scope)
+### `scope` { #scope }
-В спецификации также говорится, что клиент может передать еще одно поле формы "`scope`".
+В спецификации также говорится, что клиент может передать еще одно поле формы — `scope`.
-Имя поля формы - `scope` (в единственном числе), но на самом деле это длинная строка, состоящая из отдельных областей видимости (scopes), разделенных пробелами.
+Имя поля формы — `scope` (в единственном числе), но на самом деле это длинная строка, состоящая из отдельных "scopes", разделенных пробелами.
-Каждая "область видимости" (scope) - это просто строка (без пробелов).
+Каждый "scope" — это просто строка (без пробелов).
Обычно они используются для указания уровней доступа, например:
-* `users:read` или `users:write` являются распространенными примерами.
+* `users:read` или `users:write` — распространенные примеры.
* `instagram_basic` используется Facebook / Instagram.
-* `https://www.googleapis.com/auth/drive` используется компанией Google.
+* `https://www.googleapis.com/auth/drive` используется Google.
-/// info | Дополнительнаяя информация
-В OAuth2 "scope" - это просто строка, которая уточняет уровень доступа.
+/// info | Дополнительная информация
+В OAuth2 "scope" — это просто строка, которая указывает требуемое конкретное разрешение.
-Не имеет значения, содержит ли он другие символы, например `:`, или является ли он URL.
+Не имеет значения, содержит ли она другие символы, например `:`, или является ли это URL.
-Эти детали зависят от конкретной реализации.
+Эти детали зависят от реализации.
Для OAuth2 это просто строки.
///
-## Код получения `имени пользователя` и `пароля`
-
-Для решения задачи давайте воспользуемся утилитами, предоставляемыми **FastAPI**.
+## Код для получения `username` и `password` { #code-to-get-the-username-and-password }
-### `OAuth2PasswordRequestForm`
+Теперь воспользуемся утилитами, предоставляемыми **FastAPI**, чтобы обработать это.
-Сначала импортируйте `OAuth2PasswordRequestForm` и затем используйте ее как зависимость с `Depends` в *эндпоинте* `/token`:
+### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform }
+Сначала импортируйте `OAuth2PasswordRequestForm` и затем используйте её как зависимость с `Depends` в операции пути для `/token`:
{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *}
-`OAuth2PasswordRequestForm` - это класс для использования в качестве зависимости для *функции обрабатывающей эндпоинт*, который определяет тело формы со следующими полями:
+`OAuth2PasswordRequestForm` — это зависимость-класс, которая объявляет тело формы со следующими полями:
* `username`.
* `password`.
@@ -61,51 +60,51 @@
* Необязательное поле `grant_type`.
/// tip | Подсказка
-По спецификации OAuth2 поле `grant_type` является обязательным и содержит фиксированное значение `password`, но `OAuth2PasswordRequestForm` не обеспечивает этого.
+По спецификации OAuth2 поле `grant_type` обязательно и содержит фиксированное значение `password`, но `OAuth2PasswordRequestForm` это не проверяет строго.
-Если вам необходимо использовать `grant_type`, воспользуйтесь `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`.
+Если вам нужно это строгое требование, используйте `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`.
///
-* Необязательное поле `client_id` (в нашем примере он не нужен).
-* Необязательное поле `client_secret` (в нашем примере он не нужен).
+* Необязательное поле `client_id` (в нашем примере оно не нужно).
+* Необязательное поле `client_secret` (в нашем примере оно не нужно).
/// info | Дополнительная информация
-Форма `OAuth2PasswordRequestForm` не является специальным классом для **FastAPI**, как `OAuth2PasswordBearer`.
+`OAuth2PasswordRequestForm` — это не специальный класс для **FastAPI**, как `OAuth2PasswordBearer`.
-`OAuth2PasswordBearer` указывает **FastAPI**, что это схема безопасности. Следовательно, она будет добавлена в OpenAPI.
+`OAuth2PasswordBearer` сообщает **FastAPI**, что это схема безопасности. Поэтому она добавляется в OpenAPI соответствующим образом.
-Но `OAuth2PasswordRequestForm` - это всего лишь класс зависимости, который вы могли бы написать самостоятельно или вы могли бы объявить параметры `Form` напрямую.
+А `OAuth2PasswordRequestForm` — это просто зависимость-класс, которую вы могли бы написать сами, или вы могли бы объявить параметры `Form` напрямую.
-Но, поскольку это распространённый вариант использования, он предоставляется **FastAPI** напрямую, просто чтобы облегчить задачу.
+Но так как это распространённый вариант использования, он предоставлен **FastAPI** напрямую, чтобы упростить задачу.
///
-### Использование данных формы
+### Использование данных формы { #use-the-form-data }
/// tip | Подсказка
-В экземпляре зависимого класса `OAuth2PasswordRequestForm` атрибут `scope`, состоящий из одной длинной строки, разделенной пробелами, заменен на атрибут `scopes`, состоящий из списка отдельных строк, каждая из которых соответствует определенному уровню доступа.
+У экземпляра зависимости `OAuth2PasswordRequestForm` не будет атрибута `scope` с длинной строкой, разделенной пробелами. Вместо этого будет атрибут `scopes` со списком отдельных строк — по одной для каждого переданного scope.
-В данном примере мы не используем `scopes`, но если вам это необходимо, то такая функциональность имеется.
+В данном примере мы не используем `scopes`, но если вам это необходимо, функциональность есть.
///
Теперь получим данные о пользователе из (ненастоящей) базы данных, используя `username` из поля формы.
-Если такого пользователя нет, то мы возвращаем ошибку "неверное имя пользователя или пароль".
+Если такого пользователя нет, то мы возвращаем ошибку "Incorrect username or password" (неверное имя пользователя или пароль).
-Для ошибки мы используем исключение `HTTPException`:
+Для ошибки используем исключение `HTTPException`:
{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *}
-### Проверка пароля
+### Проверка пароля { #check-the-password }
На данный момент у нас есть данные о пользователе из нашей базы данных, но мы еще не проверили пароль.
-Давайте сначала поместим эти данные в модель Pydantic `UserInDB`.
+Давайте сначала поместим эти данные в Pydantic-модель `UserInDB`.
-Ни в коем случае нельзя сохранять пароли в открытом виде, поэтому мы будем использовать (пока что ненастоящую) систему хеширования паролей.
+Никогда нельзя сохранять пароли в открытом виде, поэтому мы будем использовать (пока что ненастоящую) систему хеширования паролей.
Если пароли не совпадают, мы возвращаем ту же ошибку.
-#### Хеширование паролей
+#### Хеширование паролей { #password-hashing }
"Хеширование" означает: преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.
@@ -113,19 +112,19 @@
Но преобразовать тарабарщину обратно в пароль невозможно.
-##### Зачем использовать хеширование паролей
+##### Зачем использовать хеширование паролей { #why-use-password-hashing }
-Если ваша база данных будет украдена, то у вора не будет паролей пользователей в открытом виде, только хэши.
+Если вашу базу данных украдут, у злоумышленника не будет паролей пользователей в открытом виде, только хэши.
-Таким образом, вор не сможет использовать эти же пароли в другой системе (поскольку многие пользователи используют одни и те же пароли повсеместно, это было бы опасно).
+Таким образом, он не сможет попробовать использовать эти же пароли в другой системе (поскольку многие пользователи используют один и тот же пароль повсеместно, это было бы опасно).
{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *}
-#### Про `**user_dict`
+#### Про `**user_dict` { #about-user-dict }
`UserInDB(**user_dict)` означает:
-*Передавать ключи и значения `user_dict` непосредственно в качестве аргументов ключ-значение, что эквивалентно:*
+*Передать ключи и значения `user_dict` непосредственно как аргументы ключ-значение, эквивалентно:*
```Python
UserInDB(
@@ -138,23 +137,23 @@ 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.
+Ответ операции пути `/token` должен быть объектом JSON.
-Он должен иметь `token_type`. В нашем случае, поскольку мы используем токены типа "Bearer", тип токена должен быть "`bearer`".
+В нём должен быть `token_type`. В нашем случае, поскольку мы используем токены типа "Bearer", тип токена должен быть `bearer`.
-И в нем должна быть строка `access_token`, содержащая наш токен доступа.
+И в нём должен быть `access_token` — строка, содержащая наш токен доступа.
-В этом простом примере мы нарушим все правила безопасности, и будем считать, что имя пользователя (username) полностью соответствует токену (token)
+В этом простом примере мы намеренно поступим небезопасно и вернём тот же `username` в качестве токена.
/// tip | Подсказка
-В следующей главе мы рассмотрим реальную защищенную реализацию с хешированием паролей и токенами JWT.
+В следующей главе вы увидите реальную защищённую реализацию с хешированием паролей и токенами JWT.
-Но пока давайте остановимся на необходимых нам деталях.
+Но пока давайте сосредоточимся на необходимых нам деталях.
///
{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *}
@@ -162,50 +161,50 @@ UserInDB(
/// tip | Подсказка
Согласно спецификации, вы должны возвращать JSON с `access_token` и `token_type`, как в данном примере.
-Это то, что вы должны сделать сами в своем коде и убедиться, что вы используете эти JSON-ключи.
+Это то, что вы должны сделать сами в своём коде и убедиться, что вы используете именно эти JSON-ключи.
-Это практически единственное, что нужно не забывать делать самостоятельно, чтобы следовать требованиям спецификации.
+Это практически единственное, о чём нужно не забыть, чтобы соответствовать спецификациям.
-Все остальное за вас сделает **FastAPI**.
+Остальное за вас сделает **FastAPI**.
///
-## Обновление зависимостей
+## Обновление зависимостей { #update-the-dependencies }
Теперь мы обновим наши зависимости.
-Мы хотим получить значение `current_user` *только* если этот пользователь активен.
+Мы хотим получить `current_user` только если этот пользователь активен.
-Поэтому мы создаем дополнительную зависимость `get_current_active_user`, которая, в свою очередь, использует в качестве зависимости `get_current_user`.
+Поэтому мы создаём дополнительную зависимость `get_current_active_user`, которая, в свою очередь, использует в качестве зависимости `get_current_user`.
Обе эти зависимости просто вернут HTTP-ошибку, если пользователь не существует или неактивен.
-Таким образом, в нашем эндпоинте мы получим пользователя только в том случае, если он существует, правильно аутентифицирован и активен:
+Таким образом, в нашей операции пути мы получим пользователя только в том случае, если он существует, корректно аутентифицирован и активен:
{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *}
/// info | Дополнительная информация
-Дополнительный заголовок `WWW-Authenticate` со значением `Bearer`, который мы здесь возвращаем, также является частью спецификации.
+Дополнительный HTTP-заголовок `WWW-Authenticate` со значением `Bearer`, который мы здесь возвращаем, также является частью спецификации.
-Ответ сервера с HTTP-кодом 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`.
+Любой HTTP статус-код 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`.
В случае с bearer-токенами (наш случай) значение этого заголовка должно быть `Bearer`.
-На самом деле этот дополнительный заголовок можно пропустить и все будет работать.
+Фактически, этот дополнительный заголовок можно опустить, и всё будет работать.
-Но он приведён здесь для соответствия спецификации.
+Но он приведён здесь для соответствия спецификациям.
-Кроме того, могут существовать инструменты, которые ожидают его и могут использовать, и это может быть полезно для вас или ваших пользователей сейчас или в будущем.
+Кроме того, могут существовать инструменты, которые ожидают его и могут использовать, и это может быть полезно для вас или ваших пользователей — сейчас или в будущем.
В этом и заключается преимущество стандартов...
///
-## Посмотим как это работает
+## Посмотрим, как это работает { #see-it-in-action }
-Откроем интерактивную документацию: http://127.0.0.1:8000/docs.
+Откройте интерактивную документацию: http://127.0.0.1:8000/docs.
-### Аутентификация
+### Аутентификация { #authenticate }
-Нажмите кнопку "Авторизация".
+Нажмите кнопку "Authorize".
Используйте учётные данные:
@@ -215,13 +214,15 @@ UserInDB(
-После авторизации в системе вы увидите следующее:
+После аутентификации вы увидите следующее:
-### Получение собственных пользовательских данных
+### Получение собственных пользовательских данных { #get-your-own-user-data }
+
+Теперь используйте операцию `GET` с путём `/users/me`.
-Теперь, используя операцию `GET` с путем `/users/me`, вы получите данные пользователя, например:
+Вы получите свои пользовательские данные, например:
```JSON
{
@@ -235,7 +236,7 @@ UserInDB(
-Если щелкнуть на значке замка и выйти из системы, а затем попытаться выполнить ту же операцию ещё раз, то будет выдана ошибка HTTP 401:
+Если щёлкнуть на значке замка и выйти из системы, а затем попытаться выполнить ту же операцию ещё раз, будет выдана ошибка HTTP 401:
```JSON
{
@@ -243,17 +244,17 @@ UserInDB(
}
```
-### Неактивный пользователь
+### Неактивный пользователь { #inactive-user }
-Теперь попробуйте пройти аутентификацию с неактивным пользователем:
+Теперь попробуйте с неактивным пользователем, аутентифицируйтесь с:
Пользователь: `alice`
Пароль: `secret2`
-И попробуйте использовать операцию `GET` с путем `/users/me`.
+И попробуйте использовать операцию `GET` с путём `/users/me`.
-Вы получите ошибку "Inactive user", как тут:
+Вы получите ошибку "Inactive user", как здесь:
```JSON
{
@@ -261,12 +262,12 @@ UserInDB(
}
```
-## Резюме
+## Резюме { #recap }
-Теперь у вас есть инструменты для реализации полноценной системы безопасности на основе `имени пользователя` и `пароля` для вашего API.
+Теперь у вас есть инструменты для реализации полноценной системы безопасности на основе `username` и `password` для вашего API.
-Используя эти средства, можно сделать систему безопасности совместимой с любой базой данных, с любым пользователем или моделью данных.
+Используя эти средства, можно сделать систему безопасности совместимой с любой базой данных и с любой пользовательской или моделью данных.
- Единственным недостатком нашей системы является то, что она всё ещё не защищена.
+Единственная деталь, которой не хватает, — система пока ещё не "защищена" по-настоящему.
-В следующей главе вы увидите, как использовать библиотеку безопасного хеширования паролей и токены JWT.
+В следующей главе вы увидите, как использовать библиотеку безопасного хеширования паролей и токены JWT.
diff --git a/docs/ru/docs/tutorial/sql-databases.md b/docs/ru/docs/tutorial/sql-databases.md
index b127e44d5..1d0346533 100644
--- a/docs/ru/docs/tutorial/sql-databases.md
+++ b/docs/ru/docs/tutorial/sql-databases.md
@@ -1,18 +1,18 @@
-# SQL (реляционные) базы данных
+# SQL (реляционные) базы данных { #sql-relational-databases }
-**FastAPI** не требует использования реляционной базы данных. Вы можете воспользоваться любой базой данных, которой хотите.
+**FastAPI** не требует использовать SQL (реляционную) базу данных. Но вы можете использовать любую базу данных, которую хотите.
-В этом разделе мы продемонстрируем, как работать с SQLModel.
+Здесь мы рассмотрим пример с использованием SQLModel.
-Библиотека **SQLModel** построена на основе SQLAlchemy и Pydantic. Она была разработана автором **FastAPI** специально для приложений на основе FastAPI, которые используют **реляционные базы данных**.
+**SQLModel** построен поверх SQLAlchemy и Pydantic. Его создал тот же автор, что и **FastAPI**, чтобы он идеально подходил для приложений FastAPI, которым нужны **SQL базы данных**.
/// tip | Подсказка
-Вы можете воспользоваться любой библиотекой для работы с реляционными (SQL) или нереляционными (NoSQL) базами данных. (Их ещё называют **ORM** библиотеками). FastAPI не принуждает вас к использованию чего-либо конкретного. 😎
+Вы можете использовать любую другую библиотеку для работы с SQL или NoSQL базами данных (иногда их называют "ORMs"), FastAPI ничего не навязывает. 😎
///
-В основе SQLModel лежит SQLAlchemy, поэтому вы спокойно можете использовать любую базу данных, поддерживаемую SQLAlchemy (и, соответственно, поддерживаемую SQLModel), например:
+Так как SQLModel основан на SQLAlchemy, вы можете легко использовать **любую поддерживаемую** SQLAlchemy базу данных (а значит, и поддерживаемую SQLModel), например:
* PostgreSQL
* MySQL
@@ -20,21 +20,21 @@
* Oracle
* Microsoft SQL Server, и т.д.
-В данном примере мы будем использовать базу данных **SQLite**, т.к. она состоит из единственного файла и поддерживается встроенными библиотеками Python. Таким образом, вы сможете скопировать данный пример и запустить его как он есть.
+В этом примере мы будем использовать **SQLite**, потому что она использует один файл и имеет встроенную поддержку в Python. Так что вы можете скопировать этот пример и запустить его как есть.
-В дальнейшем, для продакшн-версии вашего приложения, возможно, вам стоит использовать серверную базу данных, например, **PostgreSQL**.
+Позже, для продакшн-приложения, возможно, вы захотите использовать серверную базу данных, например **PostgreSQL**.
/// tip | Подсказка
-Существует официальный генератор проектов на **FastAPI** и **PostgreSQL**, который также включает frontend и дополнительные инструменты https://github.com/fastapi/full-stack-fastapi-template
+Существует официальный генератор проектов на **FastAPI** и **PostgreSQL**, включающий frontend и другие инструменты: https://github.com/fastapi/full-stack-fastapi-template
///
-Это очень простое и короткое руководство, поэтому, если вы хотите узнать о базах данных в целом, об SQL, разобраться с более продвинутым функционалом, то воспользуйтесь документацией SQLModel.
+Это очень простое и короткое руководство. Если вы хотите узнать больше о базах данных в целом, об SQL или о более продвинутых возможностях, обратитесь к документации SQLModel.
-## Установка `SQLModel`
+## Установка `SQLModel` { #install-sqlmodel }
-Создайте виртуальное окружение [virtual environment](../virtual-environments.md){.internal-link target=_blank}, активируйте его и установите `sqlmodel`:
+Сначала убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и затем установили `sqlmodel`:
uvicorn - oluşturduğumuz uygulamayı servis edecek web sunucusu görevini üstlenir.
+* uvicorn - oluşturduğumuz uygulamayı servis edecek web sunucusu görevini üstlenir.
* orjson - `ORJSONResponse` kullanacaksanız gereklidir.
* ujson - `UJSONResponse` kullanacaksanız gerekli.
diff --git a/docs/tr/docs/tutorial/first-steps.md b/docs/tr/docs/tutorial/first-steps.md
index 2d2949b50..9a8ef762d 100644
--- a/docs/tr/docs/tutorial/first-steps.md
+++ b/docs/tr/docs/tutorial/first-steps.md
@@ -139,7 +139,7 @@ Ayrıca, API'ınızla iletişim kuracak önyüz, mobil veya IoT uygulamaları gi
`FastAPI` doğrudan `Starlette`'i miras alan bir sınıftır.
-Starlette'in tüm işlevselliğini `FastAPI` ile de kullanabilirsiniz.
+Starlette'in tüm işlevselliğini `FastAPI` ile de kullanabilirsiniz.
///
diff --git a/docs/tr/docs/tutorial/static-files.md b/docs/tr/docs/tutorial/static-files.md
index db30f13bc..4542aca77 100644
--- a/docs/tr/docs/tutorial/static-files.md
+++ b/docs/tr/docs/tutorial/static-files.md
@@ -37,4 +37,4 @@ Bu parametrelerin hepsi "`static`"den farklı olabilir, bunları kendi uygulaman
## Daha Fazla Bilgi
-Daha fazla detay ve seçenek için Starlette'in Statik Dosyalar hakkındaki dokümantasyonunu incelleyin.
+Daha fazla detay ve seçenek için Starlette'in Statik Dosyalar hakkındaki dokümantasyonunu incelleyin.
diff --git a/docs/tr/llm-prompt.md b/docs/tr/llm-prompt.md
new file mode 100644
index 000000000..2ba922ec5
--- /dev/null
+++ b/docs/tr/llm-prompt.md
@@ -0,0 +1,68 @@
+### Target language
+
+Translate to Turkish (Türkçe).
+
+Language code: tr.
+
+### Core principle
+
+Don't translate word-by-word. Rewrite naturally in Turkish as if writing the doc from scratch. Preserve meaning, but prioritize fluency over literal accuracy.
+
+### Grammar and tone
+
+- Use instructional Turkish, consistent with existing Turkish docs.
+- Use imperative/guide language (e.g. "açalım", "gidin", "kopyalayalım", "bir bakalım").
+- Avoid filler words and overly long sentences.
+- Ensure sentences make sense in Turkish context — adjust structure, conjunctions, and verb forms as needed for natural flow (e.g. use "Ancak" instead of "Ve" when connecting contrasting sentences, use "-maktadır/-mektedir" for formal statements).
+
+### Headings
+
+- Follow existing Turkish heading style (Title Case where used; no trailing period).
+
+### Quotes
+
+- Keep quote style consistent with existing Turkish docs (typically ASCII quotes in text).
+- Never modify quotes inside inline code, code blocks, URLs, or file paths.
+
+### Ellipsis
+
+- Keep ellipsis style (`...`) consistent with existing Turkish docs.
+- Never modify `...` in code, URLs, or CLI examples.
+
+### Consistency
+
+- Use the same translation for the same term throughout the document.
+- If you translate a concept one way, keep it consistent across all occurrences.
+
+### Links and references
+
+- Never modify link syntax like `{.internal-link target=_blank}`.
+- Keep markdown link structure intact: `[text](url){.internal-link}`.
+
+### Preferred translations / glossary
+
+Do not translate technical terms like path, route, request, response, query, body, cookie, and header, keep them as is.
+
+- Suffixing is very important, when adding Turkish suffixes to the English words, do that based on the pronunciation of the word and with an apostrophe.
+
+- Suffixes also changes based on what word comes next in Turkish too, here is an example:
+
+"Server'a gelen request'leri intercept... " or this could have been "request'e", "request'i" etc.
+
+- Some words are tricky like "path'e" can't be used like "path'a" but it could have been "path'i" "path'leri" etc.
+
+- You can use a more instructional style, that is consistent with the document, you can add the Turkish version of the term in parenthesis if it is not something very obvious, or an advanced concept, but do not over do it, do it only the first time it is mentioned, but keep the English term as the primary word.
+
+### `///` admonitions
+
+- Keep the admonition keyword in English (do not translate `note`, `tip`, etc.).
+- If a title is present, prefer these canonical titles:
+
+- `/// note | Not`
+- `/// note | Teknik Detaylar`
+- `/// tip | İpucu`
+- `/// warning | Uyarı`
+- `/// info | Bilgi`
+- `/// check | Ek bilgi`
+
+Prefer `İpucu` over `Ipucu`.
diff --git a/docs/uk/docs/alternatives.md b/docs/uk/docs/alternatives.md
index 1acbe237a..786df45c5 100644
--- a/docs/uk/docs/alternatives.md
+++ b/docs/uk/docs/alternatives.md
@@ -415,7 +415,7 @@ Pydantic — це бібліотека для визначення переві
///
-### Starlette
+### Starlette
Starlette — це легкий фреймворк/набір інструментів ASGI, який ідеально підходить для створення високопродуктивних asyncio сервісів.
@@ -460,7 +460,7 @@ ASGI — це новий «стандарт», який розробляєтьс
///
-### Uvicorn
+### Uvicorn
Uvicorn — це блискавичний сервер ASGI, побудований на uvloop і httptools.
diff --git a/docs/uk/docs/fastapi-cli.md b/docs/uk/docs/fastapi-cli.md
index 6bbbbc326..f18b10471 100644
--- a/docs/uk/docs/fastapi-cli.md
+++ b/docs/uk/docs/fastapi-cli.md
@@ -60,7 +60,7 @@ FastAPI CLI приймає шлях до Вашої Python програми (н
Натомість, для запуску у продакшн використовуйте `fastapi run`. 🚀
-Всередині **FastAPI CLI** використовує Uvicorn, високопродуктивний, production-ready, ASGI cервер. 😎
+Всередині **FastAPI CLI** використовує Uvicorn, високопродуктивний, production-ready, ASGI cервер. 😎
## `fastapi dev`
diff --git a/docs/uk/docs/features.md b/docs/uk/docs/features.md
index 7d679d8ee..aa0ef7c79 100644
--- a/docs/uk/docs/features.md
+++ b/docs/uk/docs/features.md
@@ -147,7 +147,7 @@ FastAPI має розумні налаштування **за замовчува
## Можливості Starlette
-**FastAPI** повністю сумісний із (та побудований на основі) Starlette. Тому будь-який додатковий код Starlette, який ви маєте, також працюватиме.
+**FastAPI** повністю сумісний із (та побудований на основі) Starlette. Тому будь-який додатковий код Starlette, який ви маєте, також працюватиме.
**FastAPI** фактично є підкласом **Starlette**. Тому, якщо ви вже знайомі зі Starlette або використовуєте його, більшість функціональності працюватиме так само.
diff --git a/docs/uk/docs/index.md b/docs/uk/docs/index.md
index 7e919e257..526409c5c 100644
--- a/docs/uk/docs/index.md
+++ b/docs/uk/docs/index.md
@@ -1,8 +1,14 @@
+# FastAPI { #fastapi }
+
+
+
- Готовий до продакшину, високопродуктивний, простий у вивченні та швидкий для написання коду фреймворк + Фреймворк 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).
-
-* **Швидке написання коду**: Пришвидшує розробку функціоналу приблизно на 200%-300%. *
-* **Менше помилок**: Зменшить кількість помилок спричинених людиною (розробником) на 40%. *
-* **Інтуїтивний**: Чудова підтримка редакторами коду. Доповнення всюди. Зменште час на налагодження.
-* **Простий**: Спроектований, для легкого використання та навчання. Знадобиться менше часу на читання документації.
-* **Короткий**: Зведе до мінімуму дублювання коду. Кожен оголошений параметр може виконувати кілька функцій.
-* **Надійний**: Ви матимете стабільний код готовий до продакшину з автоматичною інтерактивною документацією.
-* **Стандартизований**: Оснований та повністю сумісний з відкритими стандартами для API: OpenAPI (попередньо відомий як Swagger) та JSON Schema.
+* **Швидкий**: дуже висока продуктивність, на рівні з **NodeJS** та **Go** (завдяки Starlette та Pydantic). [Один із найшвидших Python-фреймворків](#performance).
+* **Швидке написання коду**: пришвидшує розробку функціоналу приблизно на 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._"
-
-* Starlette для web частини.
-* Pydantic для частини даних.
+## **Typer**, FastAPI для CLI { #typer-the-fastapi-of-clis }
-## Вставновлення
+uvicorn main:app --reload...fastapi dev main.py...email-validator - для валідації електронної пошти.
-* pydantic-settings - для управління налаштуваннями.
-* pydantic-extra-types - для додаткових типів, що можуть бути використані з Pydantic.
+Використовується Starlette:
+
+* httpx - потрібно, якщо ви хочете використовувати `TestClient`.
+* jinja2 - потрібно, якщо ви хочете використовувати конфігурацію шаблонів за замовчуванням.
+* python-multipart - потрібно, якщо ви хочете підтримувати «parsing» форм за допомогою `request.form()`.
-Starlette використовує:
+Використовується FastAPI:
-* httpx - Необхідно, якщо Ви хочете використовувати `TestClient`.
-* jinja2 - Необхідно, якщо Ви хочете використовувати шаблони як конфігурацію за замовчуванням.
-* python-multipart - Необхідно, якщо Ви хочете підтримувати "розбір" форми за допомогою `request.form()`.
-* itsdangerous - Необхідно для підтримки `SessionMiddleware`.
-* pyyaml - Необхідно для підтримки Starlette `SchemaGenerator` (ймовірно, вам це не потрібно з FastAPI).
+* uvicorn - для сервера, який завантажує та обслуговує ваш застосунок. Це включає `uvicorn[standard]`, до якого входять деякі залежності (наприклад, `uvloop`), потрібні для високопродуктивної роботи сервера.
+* `fastapi-cli[standard]` - щоб надати команду `fastapi`.
+ * Це включає `fastapi-cloud-cli`, який дозволяє розгортати ваш застосунок FastAPI у FastAPI Cloud.
-FastAPI / Starlette використовують:
+### Без залежностей `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.
-* uvicorn - для сервера, який завантажує та обслуговує вашу програму.
-* orjson - Необхідно, якщо Ви хочете використовувати `ORJSONResponse`.
-* ujson - Необхідно, якщо Ви хочете використовувати `UJSONResponse`.
+Додаткові необовʼязкові залежності FastAPI:
-Ви можете встановити все це за допомогою `pip install fastapi[all]`.
+* orjson - потрібно, якщо ви хочете використовувати `ORJSONResponse`.
+* ujson - потрібно, якщо ви хочете використовувати `UJSONResponse`.
-## Ліцензія
+## Ліцензія { #license }
Цей проєкт ліцензовано згідно з умовами ліцензії MIT.
diff --git a/docs/uk/docs/learn/index.md b/docs/uk/docs/learn/index.md
index 7f9f21e57..6e28d414a 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 676bafb15..a82d13a28 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()`.
-* Конкатенує їх разом із пробілом по середині.
-
-{* ../../docs_src/python_types/tutorial001.py hl[2] *}
+* Перетворює першу літеру кожного з них у верхній регістр за допомогою `title()`.
+* Конкатенує їх разом із пробілом по середині.
+{* ../../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 для свого проекту, ви зможете скористатися цією додатковою простотою. Дивіться кілька прикладів нижче.
-
-#### List (список)
-
-Наприклад, давайте визначимо змінну, яка буде `list` із `str`.
-
-//// tab | Python 3.8 і вище
-
-З модуля `typing`, імпортуємо `List` (з великої літери `L`):
-
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
+У міру розвитку Python **новіші версії** мають покращену підтримку цих анотацій типів і в багатьох випадках вам навіть не потрібно буде імпортувати та використовувати модуль `typing` для оголошення анотацій типів.
-Оголосимо змінну з тим самим синтаксисом двокрапки (`:`).
+Якщо ви можете вибрати новішу версію Python для свого проекту, ви зможете скористатися цією додатковою простотою.
-Як тип вкажемо `List`, який ви імпортували з `typing`.
+У всій документації є приклади, сумісні з кожною версією Python (коли є різниця).
-Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки:
+Наприклад, «**Python 3.6+**» означає, що це сумісно з Python 3.6 або вище (включно з 3.7, 3.8, 3.9, 3.10 тощо). А «**Python 3.9+**» означає, що це сумісно з Python 3.9 або вище (включаючи 3.10 тощо).
-```Python hl_lines="4"
-{!> ../../docs_src/python_types/tutorial006.py!}
-```
+Якщо ви можете використовувати **останні версії Python**, використовуйте приклади для останньої версії — вони матимуть **найкращий і найпростіший синтаксис**, наприклад, «**Python 3.10+**».
-////
+#### List { #list }
-//// tab | Python 3.9 і вище
+Наприклад, давайте визначимо змінну, яка буде `list` із `str`.
-Оголосимо змінну з тим самим синтаксисом двокрапки (`:`).
+Оголосіть змінну з тим самим синтаксисом двокрапки (`:`).
-Як тип вкажемо `list`.
+Як тип вкажіть `list`.
Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки:
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial006_py39.py!}
-```
-
-////
+{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
-/// info
+/// info | Інформація
-Ці внутрішні типи в квадратних дужках називаються "параметрами типу".
+Ці внутрішні типи в квадратних дужках називаються «параметрами типу».
-У цьому випадку, `str` це параметр типу переданий у `List` (або `list` у Python 3.9 і вище).
+У цьому випадку `str` — це параметр типу, переданий у `list`.
///
-Це означає: "змінна `items` це `list`, і кожен з елементів у цьому списку - `str`".
-
-/// tip
-
-Якщо ви використовуєте Python 3.9 і вище, вам не потрібно імпортувати `List` з `typing`, ви можете використовувати натомість тип `list`.
-
-///
+Це означає: «змінна `items` — це `list`, і кожен з елементів у цьому списку — `str`».
Зробивши це, ваш редактор може надати підтримку навіть під час обробки елементів зі списку:
@@ -221,95 +189,67 @@ 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 і вище
+//// tab | Python 3.10+
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial008b.py!}
+```Python hl_lines="1"
+{!> ../../docs_src/python_types/tutorial008b_py310.py!}
```
////
-//// tab | Python 3.10 і вище
+//// tab | Python 3.9+
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial008b_py310.py!}
+```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,103 +258,128 @@ John Doe
Це також означає, що в Python 3.10 ви можете використовувати `Something | None`:
-//// tab | Python 3.8 і вище
+//// tab | Python 3.10+
-```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009.py!}
+```Python hl_lines="1"
+{!> ../../docs_src/python_types/tutorial009_py310.py!}
```
////
-//// tab | Python 3.8 і вище - альтернатива
+//// tab | Python 3.9+
```Python hl_lines="1 4"
-{!> ../../docs_src/python_types/tutorial009b.py!}
+{!> ../../docs_src/python_types/tutorial009_py39.py!}
```
////
-//// tab | Python 3.10 і вище
+//// tab | Python 3.9+ alternative
-```Python hl_lines="1"
-{!> ../../docs_src/python_types/tutorial009_py310.py!}
+```Python hl_lines="1 4"
+{!> ../../docs_src/python_types/tutorial009b_py39.py!}
```
////
-#### Generic типи
+#### Використання `Union` або `Optional` { #using-union-or-optional }
-Ці типи, які приймають параметри типу у квадратних дужках, називаються **Generic types** or **Generics**, наприклад:
+Якщо ви використовуєте версію Python нижче 3.10, ось порада з моєї дуже **суб’єктивної** точки зору:
-//// tab | Python 3.8 і вище
+* 🚨 Уникайте використання `Optional[SomeType]`
+* Натомість ✨ **використовуйте `Union[SomeType, None]`** ✨.
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Union`
-* `Optional`
-* ...та інші.
+Обидва варіанти еквівалентні й «під капотом» це одне й те саме, але я рекомендую `Union` замість `Optional`, тому що слово «**optional**» може створювати враження, ніби значення є необов’язковим, хоча насправді це означає «воно може бути `None`», навіть якщо воно не є необов’язковим і все одно є обов’язковим.
-////
+Я вважаю, що `Union[SomeType, None]` більш явно показує, що саме мається на увазі.
+
+Це лише про слова й назви. Але ці слова можуть впливати на те, як ви та ваша команда думаєте про код.
-//// tab | Python 3.9 і вище
+Як приклад, розгляньмо цю функцію:
-Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині):
+{* ../../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.10+
+
+Ви можете використовувати ті самі вбудовані типи як generic (з квадратними дужками та типами всередині):
* `list`
* `tuple`
* `set`
* `dict`
-І те саме, що й у Python 3.8, із модуля `typing`:
+І так само, як і в попередніх версіях Python, з модуля `typing`:
* `Union`
* `Optional`
* ...та інші.
+У Python 3.10 як альтернативу використанню generic `Union` та `Optional` ви можете використовувати вертикальну смугу (`|`) для оголошення об’єднань типів — це значно краще й простіше.
+
////
-//// tab | Python 3.10 і вище
+//// tab | Python 3.9+
-Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині):
+Ви можете використовувати ті самі вбудовані типи як generic (з квадратними дужками та типами всередині):
* `list`
* `tuple`
* `set`
* `dict`
-І те саме, що й у Python 3.8, із модуля `typing`:
+І generic з модуля `typing`:
* `Union`
-* `Optional` (так само як у Python 3.8)
+* `Optional`
* ...та інші.
-У 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,47 +389,57 @@ John Doe
І ви отримуєте всю підтримку редактора з цим отриманим об’єктом.
-Приклад з документації Pydantic:
+Приклад з офіційної документації Pydantic:
-//// tab | Python 3.8 і вище
+{* ../../docs_src/python_types/tutorial011_py310.py *}
-```Python
-{!> ../../docs_src/python_types/tutorial011.py!}
-```
+/// info | Інформація
-////
+Щоб дізнатись більше про Pydantic, перегляньте його документацію.
-//// tab | Python 3.9 і вище
+///
-```Python
-{!> ../../docs_src/python_types/tutorial011_py39.py!}
-```
+**FastAPI** повністю базується на Pydantic.
-////
+Ви побачите набагато більше цього всього на практиці в [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
-//// tab | Python 3.10 і вище
+/// tip | Порада
-```Python
-{!> ../../docs_src/python_types/tutorial011_py310.py!}
-```
+Pydantic має спеціальну поведінку, коли ви використовуєте `Optional` або `Union[Something, None]` без значення за замовчуванням; детальніше про це можна прочитати в документації Pydantic про Required Optional fields.
-////
+///
-/// info
+## Підказки типів з анотаціями метаданих { #type-hints-with-metadata-annotations }
-Щоб дізнатись більше про Pydantic, перегляньте його документацію.
+У Python також є можливість додавати **додаткові метадані** до цих підказок типів за допомогою `Annotated`.
-///
+Починаючи з Python 3.9, `Annotated` є частиною стандартної бібліотеки, тож ви можете імпортувати його з `typing`.
-**FastAPI** повністю базується на Pydantic.
+{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
-Ви побачите набагато більше цього всього на практиці в [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
+Сам Python нічого не робить із цим `Annotated`. А для редакторів та інших інструментів тип усе ще є `str`.
+
+Але ви можете використати це місце в `Annotated`, щоб надати **FastAPI** додаткові метадані про те, як ви хочете, щоб ваш застосунок поводився.
+
+Важливо пам’ятати, що **перший *параметр типу***, який ви передаєте в `Annotated`, — це **фактичний тип**. Решта — це лише метадані для інших інструментів.
+
+Наразі вам просто потрібно знати, що `Annotated` існує і що це стандартний Python. 😎
+
+Пізніше ви побачите, наскільки **потужним** це може бути.
+
+/// tip | Порада
+
+Той факт, що це **стандартний Python**, означає, що ви й надалі отримуватимете **найкращий можливий досвід розробки** у вашому редакторі, з інструментами, які ви використовуєте для аналізу та рефакторингу коду тощо. ✨
+
+А також те, що ваш код буде дуже сумісним із багатьма іншими інструментами та бібліотеками Python. 🚀
+
+///
-## Анотації типів у **FastAPI**
+## Анотації типів у **FastAPI** { #type-hints-in-fastapi }
-**FastAPI** використовує ці підказки для виконання кількох речей.
+**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 912ba8c2a..6d7804195 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`.
+Клас `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 7ddd9d104..70d94f3d6 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 e2acf8a70..dc9a768c3 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 abc33f2eb..6d0669358 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 e78b5a5bf..2ae68291c 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 38fed7bb8..ca1f308ab 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".
+`str | None` (Python 3.10+) або `Union` у `Union[str, None]` (Python 3.9+) FastAPI не використовує, щоб визначити, що значення не є обов’язковим — він знатиме, що воно не є обов’язковим, тому що має значення за замовчуванням `= None`.
-`Optional` у `Optional[str]` не використовується FastAPI, але дозволить вашому редактору надати вам кращу підтримку та виявляти помилки.
+Але додавання анотацій типів дозволить вашому редактору надати вам кращу підтримку та виявляти помилки.
///
-## Без 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 f070b6ac8..3c6407716 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 b320645cb..8a5b44e8a 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 95b204d0f..f3ed8a7d9 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 b0e5344f8..0db418dcc 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 f202c7989..1b403d5bb 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 5da942b6e..a3545e074 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 e910c4ccc..5f3750010 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 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`.
+* Створіть екземпляр `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 12a356cd0..53b8b12f6 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 у *відповіді* або деінде у вашому коді (не у *запиті* клієнта), це насправді є помилкою у Вашому коді.
-
-І поки Ви її виправляєте, клієнти/користувачі не повинні мати доступу до внутрішньої інформації про помилку, оскільки це може призвести до вразливості безпеки.
+{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *}
-### Перевизначення обробника помилок `HTTPException`
+/// note | Технічні деталі
-Аналогічно, Ви можете перевизначити обробник `HTTPException`.
+Ви також можете використовувати `from starlette.responses import PlainTextResponse`.
-Наприклад, Ви можете захотіти повернути текстову відповідь замість JSON для цих помилок:
+**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.
-{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}
+///
-/// note | Технічні деталі
+/// warning | Попередження
-Ви також можете використовувати `from starlette.responses import PlainTextResponse`.
+Пам’ятайте, що `RequestValidationError` містить інформацію про назву файлу та рядок, де сталася помилка валідації, щоб за потреби ви могли показати це у своїх логах із відповідною інформацією.
-**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.
+Але це означає, що якщо ви просто перетворите це на рядок і повернете цю інформацію напряму, ви можете розкрити трохи інформації про вашу систему, тому тут код витягає та показує кожну помилку незалежно.
///
-### Використання тіла `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 6f7b0bdae..c080c19f0 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 807be484a..2d1580e49 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 8ee4f480f..f6aa92019 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 da4ff2f9e..059890549 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-параметри, що містять шляхи
-
-Припустимо, у вас є *операція шляху* з маршрутом `/files/{file_path}`.
+## Параметри шляху, що містять шляхи { #path-parameters-containing-paths }
-Але вам потрібно, щоб `file_path` містив *шлях*, наприклад `home/johndoe/myfile.txt`.
+Припустімо, у вас є *операція шляху* зі шляхом `/files/{file_path}`.
-Отже, URL для цього файлу виглядатиме так: `/files/home/johndoe/myfile.txt`.
+Але вам потрібно, щоб `file_path` сам містив *шлях*, наприклад `home/johndoe/myfile.txt`.
+Отже, URL для цього файлу виглядатиме приблизно так: `/files/home/johndoe/myfile.txt`.
+### Підтримка OpenAPI { #openapi-support }
-### Підтримка OpenAPI
+OpenAPI не підтримує спосіб оголошення *параметра шляху*, який має містити всередині *шлях*, оскільки це може призвести до сценаріїв, які складно тестувати та визначати.
-OpenAPI не підтримує спосіб оголошення *параметра шляху*, що містить *шлях* всередині, оскільки це може призвести до сценаріїв, які складно тестувати та визначати.
+Проте ви все одно можете зробити це в **FastAPI**, використовуючи один із внутрішніх інструментів Starlette.
-Однак (одначе), Ви все одно можете зробити це в **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 97eb82fa1..a28bf6c27 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-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`.
+або будь-який інший варіант написання (великі літери, перша літера велика тощо), ваша функція побачить параметр `short` зі значенням `True` типу `bool`. В іншому випадку — `False`.
-## Кілька path і query параметрів
-Ви можете одночасно оголошувати кілька path і query параметрів, і **FastAPI** автоматично визначить, який з них до чого належить.
+## Кілька 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 18b7cc01c..a6ff70dc0 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 7f5759e79..1bfd368d6 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
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 def1f8a2d..2fcad9438 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 853fd5e65..54608c2ab 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] *}
-////
-
-//// tab | Pydantic v1
-
-{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
-
-////
-
-Ця додаткова інформація буде додана як є до **JSON-схеми**, і вона буде використовуватися в документації до API.
-
-//// tab | Pydantic v2
+Ця додаткова інформація буде додана як є до **JSON-схеми** для цієї моделі, і вона буде використана в документації до API.
-У версії 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 c3d94be8d..f1fb25178 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 a84782d8f..32ca1311d 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 про статичні файли.
+Детальніше про налаштування та можливості можна дізнатися в документації Starlette про статичні файли.
diff --git a/docs/uk/docs/tutorial/testing.md b/docs/uk/docs/tutorial/testing.md
index 25fc370d6..462592829 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`.
-
-//// 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
+Обидві *операції шляху* вимагають заголовок `X-Token`.
-/// tip | Порада
-
-Бажано використовувати версію з `Annotated`, якщо це можливо
-
-///
-
-```Python
-{!> ../../docs_src/app_testing/app_b_py310/main.py!}
-```
-
-////
-
-//// tab | Python 3.8+ non-Annotated
+{* ../../docs_src/app_testing/app_b_an_py310/main.py *}
-/// tip | Порада
-
-Бажано використовувати версію з `Annotated`, якщо це можливо
-
-///
+### Розширений тестовий файл { #extended-testing-file }
-```Python
-{!> ../../docs_src/app_testing/app_b/main.py!}
-```
-
-////
+Потім ви можете оновити `test_main.py`, додавши розширені тести:
-### Розширений тестовий файл
+{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *}
-Потім Ви можете оновити `test_main.py`, додавши розширені тести:
-{* ../../docs_src/app_testing/app_b/test_main.py *}
+Коли вам потрібно передати клієнту інформацію в запиті, але ви не знаєте, як це зробити, ви можете пошукати (Google), як це зробити в `httpx`, або навіть як це зробити з `requests`, оскільки дизайн HTTPX базується на дизайні Requests.
-Коли Вам потрібно передати клієнту інформацію в запиті, але Ви не знаєте, як це зробити, Ви можете пошукати (наприклад, у 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 403e89930..000000000
--- 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 901c8fd59..000000000
--- 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 dfeeed8c5..000000000
--- 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:
-
-- Ìlànà wẹ́ẹ́bù FastAPI, iṣẹ́ gíga, ó rọrùn láti kọ̀, o yára láti kóòdù, ó sì ṣetán fún iṣelọpọ ní lílo -
- - ---- - -**Àkọsílẹ̀**: https://fastapi.tiangolo.com - -**Orisun Kóòdù**: https://github.com/fastapi/fastapi - ---- - -FastAPI jẹ́ ìgbàlódé, tí ó yára (iṣẹ-giga), ìlànà wẹ́ẹ́bù fún kikọ àwọn API pẹ̀lú Python èyí tí ó da lori àwọn ìtọ́kasí àmì irúfẹ́ Python. - -Àwọn ẹya pàtàkì ni: - -* **Ó yára**: Iṣẹ tí ó ga púpọ̀, tí ó wa ni ibamu pẹ̀lú **NodeJS** àti **Go** (ọpẹ si Starlette àti Pydantic). [Ọkan nínú àwọn ìlànà Python ti o yára jùlọ ti o wa](#isesi). -* **Ó yára láti kóòdù**: O mu iyara pọ si láti kọ àwọn ẹya tuntun kóòdù nipasẹ "Igba ìdá ọgọ́rùn-ún" (i.e. 200%) si "ọ̀ọ́dúrún ìdá ọgọ́rùn-ún" (i.e. 300%). -* **Àìtọ́ kékeré**: O n din aṣiṣe ku bi ọgbon ìdá ọgọ́rùn-ún (i.e. 40%) ti eda eniyan (oṣiṣẹ kóòdù) fa. * -* **Ọgbọ́n àti ìmọ̀**: Atilẹyin olootu nla. Ìparí nibi gbogbo. Àkókò díẹ̀ nipa wíwá ibi tí ìṣòro kóòdù wà. -* **Irọrun**: A kọ kí ó le rọrun láti lo àti láti kọ ẹkọ nínú rè. Ó máa fún ọ ní àkókò díẹ̀ látı ka àkọsílẹ. -* **Ó kúkurú ní kikọ**: Ó dín àtúnkọ àti àtúntò kóòdù kù. Ìkéde àṣàyàn kọ̀ọ̀kan nínú rẹ̀ ní ọ̀pọ̀lọpọ̀ àwọn ìlò. O ṣe iranlọwọ láti má ṣe ní ọ̀pọ̀lọpọ̀ àṣìṣe. -* **Ó lágbára**: Ó ń ṣe àgbéjáde kóòdù tí ó ṣetán fún ìṣelọ́pọ̀. Pẹ̀lú àkọsílẹ̀ tí ó máa ṣàlàyé ara rẹ̀ fún ẹ ní ìbáṣepọ̀ aládàáṣiṣẹ́ pẹ̀lú rè. -* **Ajohunše/Ìtọ́kasí**: Ó da lori (àti ibamu ni kikun pẹ̀lú) àwọn ìmọ ajohunše/ìtọ́kasí fún àwọn API: OpenAPI (èyí tí a mọ tẹlẹ si Swagger) àti JSON Schema. - -* iṣiro yi da lori àwọn idanwo tí ẹgbẹ ìdàgbàsókè FastAPI ṣe, nígbàtí wọn kọ àwọn ohun elo iṣelọpọ kóòdù pẹ̀lú rẹ. - -## Àwọn onígbọ̀wọ́ - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} -async def...uvicorn main:app --reload...email-validator - fún ifọwọsi ímeèlì.
-* pydantic-settings - fún ètò ìsàkóso.
-* pydantic-extra-types - fún àfikún oríṣi láti lọ pẹ̀lú Pydantic.
-
-Èyí tí Starlette ń lò:
-
-* httpx - Nílò tí ó bá fẹ́ láti lọ `TestClient`.
-* jinja2 - Nílò tí ó bá fẹ́ láti lọ iṣeto awoṣe aiyipada.
-* python-multipart - Nílò tí ó bá fẹ́ láti ṣe àtìlẹ́yìn fún "àyẹ̀wò" fọọmu, pẹ̀lú `request.form()`.
-* itsdangerous - Nílò fún àtìlẹ́yìn `SessionMiddleware`.
-* pyyaml - Nílò fún àtìlẹ́yìn Starlette's `SchemaGenerator` (ó ṣe ṣe kí ó má nílò rẹ̀ fún FastAPI).
-
-Èyí tí FastAPI / Starlette ń lò:
-
-* uvicorn - Fún olupin tí yóò sẹ́ àmúyẹ àti tí yóò ṣe ìpèsè fún iṣẹ́ rẹ tàbí ohun èlò rẹ.
-* orjson - Nílò tí ó bá fẹ́ láti lọ `ORJSONResponse`.
-* ujson - Nílò tí ó bá fẹ́ láti lọ `UJSONResponse`.
-
-Ó lè fi gbogbo àwọn wọ̀nyí sórí ẹrọ pẹ̀lú `pip install "fastapi[all]"`.
-
-## Iwe-aṣẹ
-
-Iṣẹ́ yìí ni iwe-aṣẹ lábẹ́ àwọn òfin tí iwe-aṣẹ MIT.
diff --git a/docs/yo/mkdocs.yml b/docs/yo/mkdocs.yml
deleted file mode 100644
index de18856f4..000000000
--- a/docs/yo/mkdocs.yml
+++ /dev/null
@@ -1 +0,0 @@
-INHERIT: ../en/mkdocs.yml
diff --git a/docs/zh-hant/docs/fastapi-cli.md b/docs/zh-hant/docs/fastapi-cli.md
index 3c644ce46..b107e7e73 100644
--- a/docs/zh-hant/docs/fastapi-cli.md
+++ b/docs/zh-hant/docs/fastapi-cli.md
@@ -60,7 +60,7 @@ FastAPI CLI 接收你的 Python 程式路徑(例如 `main.py`),並自動
在生產環境,你應該使用 `fastapi run` 命令。 🚀
-**FastAPI CLI** 內部使用了 Uvicorn,這是一個高效能、適合生產環境的 ASGI 伺服器。 😎
+**FastAPI CLI** 內部使用了 Uvicorn,這是一個高效能、適合生產環境的 ASGI 伺服器。 😎
## `fastapi dev`
diff --git a/docs/zh-hant/docs/features.md b/docs/zh-hant/docs/features.md
index 3a1392b51..f44d28a7f 100644
--- a/docs/zh-hant/docs/features.md
+++ b/docs/zh-hant/docs/features.md
@@ -167,7 +167,7 @@ FastAPI 有一個使用簡單,但是非常強大的`dataclasses`):
-{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *}
+{* ../../docs_src/dataclasses_/tutorial001.py hl[1,7:12,19:20] *}
这还是借助于 **Pydantic** 及其内置的 `dataclasses`。
@@ -32,7 +32,7 @@ FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic
在 `response_model` 参数中使用 `dataclasses`:
-{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
+{* ../../docs_src/dataclasses_/tutorial002.py hl[1,7:13,19] *}
本例把数据类自动转换为 Pydantic 数据类。
@@ -49,7 +49,7 @@ API 文档中也会显示相关概图:
本例把标准的 `dataclasses` 直接替换为 `pydantic.dataclasses`:
```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" }
-{!../../docs_src/dataclasses/tutorial003.py!}
+{!../../docs_src/dataclasses_/tutorial003.py!}
```
1. 本例依然要从标准的 `dataclasses` 中导入 `field`;
diff --git a/docs/zh/docs/advanced/events.md b/docs/zh/docs/advanced/events.md
index 5ade0f0ff..1ef6cdd3c 100644
--- a/docs/zh/docs/advanced/events.md
+++ b/docs/zh/docs/advanced/events.md
@@ -162,7 +162,7 @@ async with lifespan(app):
/// info | 说明
-有关事件处理器的详情,请参阅 Starlette 官档 - 事件。
+有关事件处理器的详情,请参阅 Starlette 官档 - 事件。
包括如何处理生命周期状态,这可以用于程序的其他部分。
diff --git a/docs/zh/docs/advanced/middleware.md b/docs/zh/docs/advanced/middleware.md
index c7b15b929..65e8c183f 100644
--- a/docs/zh/docs/advanced/middleware.md
+++ b/docs/zh/docs/advanced/middleware.md
@@ -92,4 +92,4 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow")
* Uvicorn 的 `ProxyHeadersMiddleware`
* MessagePack
-其它可用中间件详见 Starlette 官档 - 中间件 及 ASGI Awesome 列表。
+其它可用中间件详见 Starlette 官档 - 中间件 及 ASGI Awesome 列表。
diff --git a/docs/zh/docs/advanced/response-cookies.md b/docs/zh/docs/advanced/response-cookies.md
index d4b93d003..d5f2fe6fc 100644
--- a/docs/zh/docs/advanced/response-cookies.md
+++ b/docs/zh/docs/advanced/response-cookies.md
@@ -46,4 +46,4 @@
///
-如果你想查看所有可用的参数和选项,可以参考 Starlette帮助文档
+如果你想查看所有可用的参数和选项,可以参考 Starlette帮助文档
diff --git a/docs/zh/docs/advanced/response-headers.md b/docs/zh/docs/advanced/response-headers.md
index fe2cb0da8..5c6a62e93 100644
--- a/docs/zh/docs/advanced/response-headers.md
+++ b/docs/zh/docs/advanced/response-headers.md
@@ -36,4 +36,4 @@
请注意,可以使用'X-'前缀添加自定义专有头部。
-但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在Starlette的CORS文档中记录的`expose_headers`参数。
+但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在Starlette的CORS文档中记录的`expose_headers`参数。
diff --git a/docs/zh/docs/advanced/templates.md b/docs/zh/docs/advanced/templates.md
index 8b7019ede..e627eed98 100644
--- a/docs/zh/docs/advanced/templates.md
+++ b/docs/zh/docs/advanced/templates.md
@@ -122,4 +122,4 @@ Item ID: 42
## 更多说明
-包括测试模板等更多详情,请参阅 Starlette 官方文档 - 模板。
+包括测试模板等更多详情,请参阅 Starlette 官方文档 - 模板。
diff --git a/docs/zh/docs/advanced/testing-websockets.md b/docs/zh/docs/advanced/testing-websockets.md
index 5d713d5f7..b84647a3e 100644
--- a/docs/zh/docs/advanced/testing-websockets.md
+++ b/docs/zh/docs/advanced/testing-websockets.md
@@ -8,6 +8,6 @@
/// note | 笔记
-更多细节详见 Starlette 官档 - 测试 WebSockets。
+更多细节详见 Starlette 官档 - 测试 WebSockets。
///
diff --git a/docs/zh/docs/advanced/using-request-directly.md b/docs/zh/docs/advanced/using-request-directly.md
index db0fcafdf..a9658c034 100644
--- a/docs/zh/docs/advanced/using-request-directly.md
+++ b/docs/zh/docs/advanced/using-request-directly.md
@@ -15,7 +15,7 @@
## `Request` 对象的细节
-实际上,**FastAPI** 的底层是 **Starlette**,**FastAPI** 只不过是在 **Starlette** 顶层提供了一些工具,所以能直接使用 Starlette 的 `Request` 对象。
+实际上,**FastAPI** 的底层是 **Starlette**,**FastAPI** 只不过是在 **Starlette** 顶层提供了一些工具,所以能直接使用 Starlette 的 `Request` 对象。
但直接从 `Request` 对象提取数据时(例如,读取请求体),**FastAPI** 不会验证、转换和存档数据(为 API 文档使用 OpenAPI)。
@@ -45,7 +45,7 @@
## `Request` 文档
-更多细节详见 Starlette 官档 - `Request` 对象。
+更多细节详见 Starlette 官档 - `Request` 对象。
/// note | 技术细节
diff --git a/docs/zh/docs/advanced/websockets.md b/docs/zh/docs/advanced/websockets.md
index d91aacc03..005ed9242 100644
--- a/docs/zh/docs/advanced/websockets.md
+++ b/docs/zh/docs/advanced/websockets.md
@@ -172,5 +172,5 @@ Client #1596980209979 left the chat
要了解更多选项,请查看 Starlette 的文档:
-* [WebSocket 类](https://www.starlette.io/websockets/)
-* [基于类的 WebSocket 处理](https://www.starlette.io/endpoints/#websocketendpoint)。
+* [WebSocket 类](https://www.starlette.dev/websockets/)
+* [基于类的 WebSocket 处理](https://www.starlette.dev/endpoints/#websocketendpoint)。
diff --git a/docs/zh/docs/deployment/manually.md b/docs/zh/docs/deployment/manually.md
index 3dc5942e3..2c2784a64 100644
--- a/docs/zh/docs/deployment/manually.md
+++ b/docs/zh/docs/deployment/manually.md
@@ -52,7 +52,7 @@ FastAPI 使用了一种用于构建 Python Web 框架和服务器的标准,称
除此之外,还有其他一些可选的 ASGI 服务器,例如:
-* Uvicorn:高性能 ASGI 服务器。
+* Uvicorn:高性能 ASGI 服务器。
* Hypercorn:与 HTTP/2 和 Trio 等兼容的 ASGI 服务器。
* Daphne:为 Django Channels 构建的 ASGI 服务器。
* Granian:基于 Rust 的 HTTP 服务器,专为 Python 应用设计。
diff --git a/docs/zh/docs/fastapi-cli.md b/docs/zh/docs/fastapi-cli.md
index 8a70e1d80..3b67eb664 100644
--- a/docs/zh/docs/fastapi-cli.md
+++ b/docs/zh/docs/fastapi-cli.md
@@ -52,7 +52,7 @@ FastAPI CLI 接收你的 Python 程序路径,自动检测包含 FastAPI 的变
在生产环境中,你应该使用 `fastapi run` 命令。🚀
-在内部,**FastAPI CLI** 使用了 Uvicorn,这是一个高性能、适用于生产环境的 ASGI 服务器。😎
+在内部,**FastAPI CLI** 使用了 Uvicorn,这是一个高性能、适用于生产环境的 ASGI 服务器。😎
## `fastapi dev`
diff --git a/docs/zh/docs/features.md b/docs/zh/docs/features.md
index 24dc3e8ce..eaf8daff7 100644
--- a/docs/zh/docs/features.md
+++ b/docs/zh/docs/features.md
@@ -165,7 +165,7 @@ FastAPI 有一个使用非常简单,但是非常强大的.
+更多细节查看 Starlette's official docs for Background Tasks.
## 告诫
diff --git a/docs/zh/docs/tutorial/first-steps.md b/docs/zh/docs/tutorial/first-steps.md
index 80a34116a..2d7c35c8c 100644
--- a/docs/zh/docs/tutorial/first-steps.md
+++ b/docs/zh/docs/tutorial/first-steps.md
@@ -155,7 +155,7 @@ OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送
`FastAPI` 是直接从 `Starlette` 继承的类。
-你可以通过 `FastAPI` 使用所有的 Starlette 的功能。
+你可以通过 `FastAPI` 使用所有的 Starlette 的功能。
///
diff --git a/docs/zh/docs/tutorial/handling-errors.md b/docs/zh/docs/tutorial/handling-errors.md
index 0b887c292..ae667b74a 100644
--- a/docs/zh/docs/tutorial/handling-errors.md
+++ b/docs/zh/docs/tutorial/handling-errors.md
@@ -83,7 +83,7 @@
## 安装自定义异常处理器
-添加自定义处理器,要使用 [Starlette 的异常工具](https://www.starlette.io/exceptions/)。
+添加自定义处理器,要使用 [Starlette 的异常工具](https://www.starlette.dev/exceptions/)。
假设要触发的自定义异常叫作 `UnicornException`。
diff --git a/docs/zh/docs/tutorial/middleware.md b/docs/zh/docs/tutorial/middleware.md
index 258ca7482..5608c4ee1 100644
--- a/docs/zh/docs/tutorial/middleware.md
+++ b/docs/zh/docs/tutorial/middleware.md
@@ -37,7 +37,7 @@
请记住可以 用'X-' 前缀添加专有自定义请求头.
-但是如果你想让浏览器中的客户端看到你的自定义请求头, 你需要把它们加到 CORS 配置 ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) 的 `expose_headers` 参数中,在 Starlette's CORS docs文档中.
+但是如果你想让浏览器中的客户端看到你的自定义请求头, 你需要把它们加到 CORS 配置 ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) 的 `expose_headers` 参数中,在 Starlette's CORS docs文档中.
///
diff --git a/docs/zh/docs/tutorial/static-files.md b/docs/zh/docs/tutorial/static-files.md
index c19079565..1a0d4504c 100644
--- a/docs/zh/docs/tutorial/static-files.md
+++ b/docs/zh/docs/tutorial/static-files.md
@@ -37,4 +37,4 @@
## 更多信息
-更多细节和选择查阅 Starlette's docs about Static Files.
+更多细节和选择查阅 Starlette's docs about Static Files.
diff --git a/docs/zh/docs/tutorial/testing.md b/docs/zh/docs/tutorial/testing.md
index 3e0c48caf..3877adbac 100644
--- a/docs/zh/docs/tutorial/testing.md
+++ b/docs/zh/docs/tutorial/testing.md
@@ -1,6 +1,6 @@
# 测试
-感谢 Starlette,测试**FastAPI** 应用轻松又愉快。
+感谢 Starlette,测试**FastAPI** 应用轻松又愉快。
它基于 HTTPX, 而HTTPX又是基于Requests设计的,所以很相似且易懂。
diff --git a/docs/zh/llm-prompt.md b/docs/zh/llm-prompt.md
new file mode 100644
index 000000000..7ce6f96a4
--- /dev/null
+++ b/docs/zh/llm-prompt.md
@@ -0,0 +1,46 @@
+### Target language
+
+Translate to Simplified Chinese (简体中文).
+
+Language code: zh.
+
+### Grammar and tone
+
+- Use clear, concise technical Chinese consistent with existing docs.
+- Address the reader naturally (commonly using “你/你的”).
+
+### Headings
+
+- Follow existing Simplified Chinese heading style (short and descriptive).
+- Do not add trailing punctuation to headings.
+- If a heading contains only the name of a FastAPI feature, do not translate it.
+
+### Quotes and punctuation
+
+- Keep punctuation style consistent with existing Simplified Chinese docs (they often mix English terms like “FastAPI” with Chinese text).
+- Never change punctuation inside inline code, code blocks, URLs, or file paths.
+
+### Ellipsis
+
+- Keep ellipsis style consistent within each document, prefer `...` over `……`.
+- Never change ellipsis 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
+
+- Keep the admonition keyword in English (do not translate `note`, `tip`, etc.).
+- If a title is present, prefer these canonical titles:
+
+- `/// tip | 提示`
+- `/// note | 注意`
+- `/// warning | 警告`
+- `/// info | 信息`
+- `/// danger | 危险`
diff --git a/docs_src/app_testing/app_b/__init__.py b/docs_src/additional_responses/__init__.py
similarity index 100%
rename from docs_src/app_testing/app_b/__init__.py
rename to docs_src/additional_responses/__init__.py
diff --git a/docs_src/additional_responses/tutorial001.py b/docs_src/additional_responses/tutorial001_py39.py
similarity index 100%
rename from docs_src/additional_responses/tutorial001.py
rename to docs_src/additional_responses/tutorial001_py39.py
diff --git a/docs_src/additional_responses/tutorial002_py310.py b/docs_src/additional_responses/tutorial002_py310.py
new file mode 100644
index 000000000..a94b740c9
--- /dev/null
+++ b/docs_src/additional_responses/tutorial002_py310.py
@@ -0,0 +1,28 @@
+from fastapi import FastAPI
+from fastapi.responses import FileResponse
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ id: str
+ value: str
+
+
+app = FastAPI()
+
+
+@app.get(
+ "/items/{item_id}",
+ response_model=Item,
+ responses={
+ 200: {
+ "content": {"image/png": {}},
+ "description": "Return the JSON item or an image.",
+ }
+ },
+)
+async def read_item(item_id: str, img: bool | None = None):
+ if img:
+ return FileResponse("image.png", media_type="image/png")
+ else:
+ return {"id": "foo", "value": "there goes my hero"}
diff --git a/docs_src/additional_responses/tutorial002.py b/docs_src/additional_responses/tutorial002_py39.py
similarity index 100%
rename from docs_src/additional_responses/tutorial002.py
rename to docs_src/additional_responses/tutorial002_py39.py
diff --git a/docs_src/additional_responses/tutorial003.py b/docs_src/additional_responses/tutorial003_py39.py
similarity index 100%
rename from docs_src/additional_responses/tutorial003.py
rename to docs_src/additional_responses/tutorial003_py39.py
diff --git a/docs_src/additional_responses/tutorial004_py310.py b/docs_src/additional_responses/tutorial004_py310.py
new file mode 100644
index 000000000..65cbef634
--- /dev/null
+++ b/docs_src/additional_responses/tutorial004_py310.py
@@ -0,0 +1,30 @@
+from fastapi import FastAPI
+from fastapi.responses import FileResponse
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ id: str
+ value: str
+
+
+responses = {
+ 404: {"description": "Item not found"},
+ 302: {"description": "The item was moved"},
+ 403: {"description": "Not enough privileges"},
+}
+
+
+app = FastAPI()
+
+
+@app.get(
+ "/items/{item_id}",
+ response_model=Item,
+ responses={**responses, 200: {"content": {"image/png": {}}}},
+)
+async def read_item(item_id: str, img: bool | None = None):
+ if img:
+ return FileResponse("image.png", media_type="image/png")
+ else:
+ return {"id": "foo", "value": "there goes my hero"}
diff --git a/docs_src/additional_responses/tutorial004.py b/docs_src/additional_responses/tutorial004_py39.py
similarity index 100%
rename from docs_src/additional_responses/tutorial004.py
rename to docs_src/additional_responses/tutorial004_py39.py
diff --git a/docs_src/app_testing/app_b_an/__init__.py b/docs_src/additional_status_codes/__init__.py
similarity index 100%
rename from docs_src/app_testing/app_b_an/__init__.py
rename to docs_src/additional_status_codes/__init__.py
diff --git a/docs_src/additional_status_codes/tutorial001_an.py b/docs_src/additional_status_codes/tutorial001_an.py
deleted file mode 100644
index b5ad6a16b..000000000
--- a/docs_src/additional_status_codes/tutorial001_an.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI, status
-from fastapi.responses import JSONResponse
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-items = {"foo": {"name": "Fighters", "size": 6}, "bar": {"name": "Tenders", "size": 3}}
-
-
-@app.put("/items/{item_id}")
-async def upsert_item(
- item_id: str,
- name: Annotated[Union[str, None], Body()] = None,
- size: Annotated[Union[int, None], Body()] = None,
-):
- if item_id in items:
- item = items[item_id]
- item["name"] = name
- item["size"] = size
- return item
- else:
- item = {"name": name, "size": size}
- items[item_id] = item
- return JSONResponse(status_code=status.HTTP_201_CREATED, content=item)
diff --git a/docs_src/additional_status_codes/tutorial001.py b/docs_src/additional_status_codes/tutorial001_py39.py
similarity index 100%
rename from docs_src/additional_status_codes/tutorial001.py
rename to docs_src/additional_status_codes/tutorial001_py39.py
diff --git a/docs_src/bigger_applications/app/__init__.py b/docs_src/advanced_middleware/__init__.py
similarity index 100%
rename from docs_src/bigger_applications/app/__init__.py
rename to docs_src/advanced_middleware/__init__.py
diff --git a/docs_src/advanced_middleware/tutorial001.py b/docs_src/advanced_middleware/tutorial001_py39.py
similarity index 100%
rename from docs_src/advanced_middleware/tutorial001.py
rename to docs_src/advanced_middleware/tutorial001_py39.py
diff --git a/docs_src/advanced_middleware/tutorial002.py b/docs_src/advanced_middleware/tutorial002_py39.py
similarity index 100%
rename from docs_src/advanced_middleware/tutorial002.py
rename to docs_src/advanced_middleware/tutorial002_py39.py
diff --git a/docs_src/advanced_middleware/tutorial003.py b/docs_src/advanced_middleware/tutorial003_py39.py
similarity index 100%
rename from docs_src/advanced_middleware/tutorial003.py
rename to docs_src/advanced_middleware/tutorial003_py39.py
diff --git a/docs_src/bigger_applications/app/internal/__init__.py b/docs_src/app_testing/app_a_py39/__init__.py
similarity index 100%
rename from docs_src/bigger_applications/app/internal/__init__.py
rename to docs_src/app_testing/app_a_py39/__init__.py
diff --git a/docs_src/app_testing/main.py b/docs_src/app_testing/app_a_py39/main.py
similarity index 100%
rename from docs_src/app_testing/main.py
rename to docs_src/app_testing/app_a_py39/main.py
diff --git a/docs_src/app_testing/test_main.py b/docs_src/app_testing/app_a_py39/test_main.py
similarity index 100%
rename from docs_src/app_testing/test_main.py
rename to docs_src/app_testing/app_a_py39/test_main.py
diff --git a/docs_src/app_testing/app_b_an/main.py b/docs_src/app_testing/app_b_an/main.py
deleted file mode 100644
index c66278fdd..000000000
--- a/docs_src/app_testing/app_b_an/main.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Header, HTTPException
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-fake_secret_token = "coneofsilence"
-
-fake_db = {
- "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
- "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
-}
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- id: str
- title: str
- description: Union[str, None] = None
-
-
-@app.get("/items/{item_id}", response_model=Item)
-async def read_main(item_id: str, x_token: Annotated[str, Header()]):
- if x_token != fake_secret_token:
- raise HTTPException(status_code=400, detail="Invalid X-Token header")
- if item_id not in fake_db:
- raise HTTPException(status_code=404, detail="Item not found")
- return fake_db[item_id]
-
-
-@app.post("/items/", response_model=Item)
-async def create_item(item: Item, x_token: Annotated[str, Header()]):
- if x_token != fake_secret_token:
- raise HTTPException(status_code=400, detail="Invalid X-Token header")
- if item.id in fake_db:
- raise HTTPException(status_code=409, detail="Item already exists")
- fake_db[item.id] = item
- return item
diff --git a/docs_src/app_testing/app_b_an/test_main.py b/docs_src/app_testing/app_b_an/test_main.py
deleted file mode 100644
index 4e1c51ecc..000000000
--- a/docs_src/app_testing/app_b_an/test_main.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from fastapi.testclient import TestClient
-
-from .main import app
-
-client = TestClient(app)
-
-
-def test_read_item():
- response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
- assert response.status_code == 200
- assert response.json() == {
- "id": "foo",
- "title": "Foo",
- "description": "There goes my hero",
- }
-
-
-def test_read_item_bad_token():
- response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
- assert response.status_code == 400
- assert response.json() == {"detail": "Invalid X-Token header"}
-
-
-def test_read_nonexistent_item():
- response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
- assert response.status_code == 404
- assert response.json() == {"detail": "Item not found"}
-
-
-def test_create_item():
- response = client.post(
- "/items/",
- headers={"X-Token": "coneofsilence"},
- json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
- )
- assert response.status_code == 200
- assert response.json() == {
- "id": "foobar",
- "title": "Foo Bar",
- "description": "The Foo Barters",
- }
-
-
-def test_create_item_bad_token():
- response = client.post(
- "/items/",
- headers={"X-Token": "hailhydra"},
- json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
- )
- assert response.status_code == 400
- assert response.json() == {"detail": "Invalid X-Token header"}
-
-
-def test_create_existing_item():
- response = client.post(
- "/items/",
- headers={"X-Token": "coneofsilence"},
- json={
- "id": "foo",
- "title": "The Foo ID Stealers",
- "description": "There goes my stealer",
- },
- )
- assert response.status_code == 409
- assert response.json() == {"detail": "Item already exists"}
diff --git a/docs_src/bigger_applications/app/routers/__init__.py b/docs_src/app_testing/app_b_py39/__init__.py
similarity index 100%
rename from docs_src/bigger_applications/app/routers/__init__.py
rename to docs_src/app_testing/app_b_py39/__init__.py
diff --git a/docs_src/app_testing/app_b/main.py b/docs_src/app_testing/app_b_py39/main.py
similarity index 100%
rename from docs_src/app_testing/app_b/main.py
rename to docs_src/app_testing/app_b_py39/main.py
diff --git a/docs_src/app_testing/app_b/test_main.py b/docs_src/app_testing/app_b_py39/test_main.py
similarity index 100%
rename from docs_src/app_testing/app_b/test_main.py
rename to docs_src/app_testing/app_b_py39/test_main.py
diff --git a/docs_src/app_testing/tutorial001.py b/docs_src/app_testing/tutorial001_py39.py
similarity index 100%
rename from docs_src/app_testing/tutorial001.py
rename to docs_src/app_testing/tutorial001_py39.py
diff --git a/docs_src/app_testing/tutorial002.py b/docs_src/app_testing/tutorial002_py39.py
similarity index 100%
rename from docs_src/app_testing/tutorial002.py
rename to docs_src/app_testing/tutorial002_py39.py
diff --git a/docs_src/app_testing/tutorial003.py b/docs_src/app_testing/tutorial003_py39.py
similarity index 100%
rename from docs_src/app_testing/tutorial003.py
rename to docs_src/app_testing/tutorial003_py39.py
diff --git a/docs_src/app_testing/tutorial004.py b/docs_src/app_testing/tutorial004_py39.py
similarity index 100%
rename from docs_src/app_testing/tutorial004.py
rename to docs_src/app_testing/tutorial004_py39.py
diff --git a/docs_src/bigger_applications/app_an/__init__.py b/docs_src/async_tests/app_a_py39/__init__.py
similarity index 100%
rename from docs_src/bigger_applications/app_an/__init__.py
rename to docs_src/async_tests/app_a_py39/__init__.py
diff --git a/docs_src/async_tests/main.py b/docs_src/async_tests/app_a_py39/main.py
similarity index 100%
rename from docs_src/async_tests/main.py
rename to docs_src/async_tests/app_a_py39/main.py
diff --git a/docs_src/async_tests/test_main.py b/docs_src/async_tests/app_a_py39/test_main.py
similarity index 100%
rename from docs_src/async_tests/test_main.py
rename to docs_src/async_tests/app_a_py39/test_main.py
diff --git a/docs_src/bigger_applications/app_an/internal/__init__.py b/docs_src/authentication_error_status_code/__init__.py
similarity index 100%
rename from docs_src/bigger_applications/app_an/internal/__init__.py
rename to docs_src/authentication_error_status_code/__init__.py
diff --git a/docs_src/authentication_error_status_code/tutorial001_an_py39.py b/docs_src/authentication_error_status_code/tutorial001_an_py39.py
new file mode 100644
index 000000000..7bbc2f717
--- /dev/null
+++ b/docs_src/authentication_error_status_code/tutorial001_an_py39.py
@@ -0,0 +1,21 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
+
+app = FastAPI()
+
+
+class HTTPBearer403(HTTPBearer):
+ def make_not_authenticated_error(self) -> HTTPException:
+ return HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN, detail="Not authenticated"
+ )
+
+
+CredentialsDep = Annotated[HTTPAuthorizationCredentials, Depends(HTTPBearer403())]
+
+
+@app.get("/me")
+def read_me(credentials: CredentialsDep):
+ return {"message": "You are authenticated", "token": credentials.credentials}
diff --git a/docs_src/bigger_applications/app_an/routers/__init__.py b/docs_src/background_tasks/__init__.py
similarity index 100%
rename from docs_src/bigger_applications/app_an/routers/__init__.py
rename to docs_src/background_tasks/__init__.py
diff --git a/docs_src/background_tasks/tutorial001.py b/docs_src/background_tasks/tutorial001_py39.py
similarity index 100%
rename from docs_src/background_tasks/tutorial001.py
rename to docs_src/background_tasks/tutorial001_py39.py
diff --git a/docs_src/background_tasks/tutorial002_an.py b/docs_src/background_tasks/tutorial002_an.py
deleted file mode 100644
index f63502b09..000000000
--- a/docs_src/background_tasks/tutorial002_an.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from typing import Union
-
-from fastapi import BackgroundTasks, Depends, FastAPI
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-def write_log(message: str):
- with open("log.txt", mode="a") as log:
- log.write(message)
-
-
-def get_query(background_tasks: BackgroundTasks, q: Union[str, None] = None):
- if q:
- message = f"found query: {q}\n"
- background_tasks.add_task(write_log, message)
- return q
-
-
-@app.post("/send-notification/{email}")
-async def send_notification(
- email: str, background_tasks: BackgroundTasks, q: Annotated[str, Depends(get_query)]
-):
- message = f"message to {email}\n"
- background_tasks.add_task(write_log, message)
- return {"message": "Message sent"}
diff --git a/docs_src/background_tasks/tutorial002.py b/docs_src/background_tasks/tutorial002_py39.py
similarity index 100%
rename from docs_src/background_tasks/tutorial002.py
rename to docs_src/background_tasks/tutorial002_py39.py
diff --git a/docs_src/settings/app01/__init__.py b/docs_src/behind_a_proxy/__init__.py
similarity index 100%
rename from docs_src/settings/app01/__init__.py
rename to docs_src/behind_a_proxy/__init__.py
diff --git a/docs_src/behind_a_proxy/tutorial001_01.py b/docs_src/behind_a_proxy/tutorial001_01_py39.py
similarity index 100%
rename from docs_src/behind_a_proxy/tutorial001_01.py
rename to docs_src/behind_a_proxy/tutorial001_01_py39.py
diff --git a/docs_src/behind_a_proxy/tutorial001.py b/docs_src/behind_a_proxy/tutorial001_py39.py
similarity index 100%
rename from docs_src/behind_a_proxy/tutorial001.py
rename to docs_src/behind_a_proxy/tutorial001_py39.py
diff --git a/docs_src/behind_a_proxy/tutorial002.py b/docs_src/behind_a_proxy/tutorial002_py39.py
similarity index 100%
rename from docs_src/behind_a_proxy/tutorial002.py
rename to docs_src/behind_a_proxy/tutorial002_py39.py
diff --git a/docs_src/behind_a_proxy/tutorial003.py b/docs_src/behind_a_proxy/tutorial003_py39.py
similarity index 100%
rename from docs_src/behind_a_proxy/tutorial003.py
rename to docs_src/behind_a_proxy/tutorial003_py39.py
diff --git a/docs_src/behind_a_proxy/tutorial004.py b/docs_src/behind_a_proxy/tutorial004_py39.py
similarity index 100%
rename from docs_src/behind_a_proxy/tutorial004.py
rename to docs_src/behind_a_proxy/tutorial004_py39.py
diff --git a/docs_src/bigger_applications/app_an/dependencies.py b/docs_src/bigger_applications/app_an/dependencies.py
deleted file mode 100644
index 1374c54b3..000000000
--- a/docs_src/bigger_applications/app_an/dependencies.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from fastapi import Header, HTTPException
-from typing_extensions import Annotated
-
-
-async def get_token_header(x_token: Annotated[str, Header()]):
- if x_token != "fake-super-secret-token":
- raise HTTPException(status_code=400, detail="X-Token header invalid")
-
-
-async def get_query_token(token: str):
- if token != "jessica":
- raise HTTPException(status_code=400, detail="No Jessica token provided")
diff --git a/docs_src/bigger_applications/app_an/internal/admin.py b/docs_src/bigger_applications/app_an/internal/admin.py
deleted file mode 100644
index 99d3da86b..000000000
--- a/docs_src/bigger_applications/app_an/internal/admin.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from fastapi import APIRouter
-
-router = APIRouter()
-
-
-@router.post("/")
-async def update_admin():
- return {"message": "Admin getting schwifty"}
diff --git a/docs_src/bigger_applications/app_an/main.py b/docs_src/bigger_applications/app_an/main.py
deleted file mode 100644
index ae544a3aa..000000000
--- a/docs_src/bigger_applications/app_an/main.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from fastapi import Depends, FastAPI
-
-from .dependencies import get_query_token, get_token_header
-from .internal import admin
-from .routers import items, users
-
-app = FastAPI(dependencies=[Depends(get_query_token)])
-
-
-app.include_router(users.router)
-app.include_router(items.router)
-app.include_router(
- admin.router,
- prefix="/admin",
- tags=["admin"],
- dependencies=[Depends(get_token_header)],
- responses={418: {"description": "I'm a teapot"}},
-)
-
-
-@app.get("/")
-async def root():
- return {"message": "Hello Bigger Applications!"}
diff --git a/docs_src/bigger_applications/app_an/routers/items.py b/docs_src/bigger_applications/app_an/routers/items.py
deleted file mode 100644
index bde9ff4d5..000000000
--- a/docs_src/bigger_applications/app_an/routers/items.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from fastapi import APIRouter, Depends, HTTPException
-
-from ..dependencies import get_token_header
-
-router = APIRouter(
- prefix="/items",
- tags=["items"],
- dependencies=[Depends(get_token_header)],
- responses={404: {"description": "Not found"}},
-)
-
-
-fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
-
-
-@router.get("/")
-async def read_items():
- return fake_items_db
-
-
-@router.get("/{item_id}")
-async def read_item(item_id: str):
- if item_id not in fake_items_db:
- raise HTTPException(status_code=404, detail="Item not found")
- return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
-
-
-@router.put(
- "/{item_id}",
- tags=["custom"],
- responses={403: {"description": "Operation forbidden"}},
-)
-async def update_item(item_id: str):
- if item_id != "plumbus":
- raise HTTPException(
- status_code=403, detail="You can only update the item: plumbus"
- )
- return {"item_id": item_id, "name": "The great Plumbus"}
diff --git a/docs_src/bigger_applications/app_an/routers/users.py b/docs_src/bigger_applications/app_an/routers/users.py
deleted file mode 100644
index 39b3d7e7c..000000000
--- a/docs_src/bigger_applications/app_an/routers/users.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from fastapi import APIRouter
-
-router = APIRouter()
-
-
-@router.get("/users/", tags=["users"])
-async def read_users():
- return [{"username": "Rick"}, {"username": "Morty"}]
-
-
-@router.get("/users/me", tags=["users"])
-async def read_user_me():
- return {"username": "fakecurrentuser"}
-
-
-@router.get("/users/{username}", tags=["users"])
-async def read_user(username: str):
- return {"username": username}
diff --git a/docs_src/settings/app02/__init__.py b/docs_src/bigger_applications/app_py39/__init__.py
similarity index 100%
rename from docs_src/settings/app02/__init__.py
rename to docs_src/bigger_applications/app_py39/__init__.py
diff --git a/docs_src/bigger_applications/app/dependencies.py b/docs_src/bigger_applications/app_py39/dependencies.py
similarity index 100%
rename from docs_src/bigger_applications/app/dependencies.py
rename to docs_src/bigger_applications/app_py39/dependencies.py
diff --git a/docs_src/settings/app02_an/__init__.py b/docs_src/bigger_applications/app_py39/internal/__init__.py
similarity index 100%
rename from docs_src/settings/app02_an/__init__.py
rename to docs_src/bigger_applications/app_py39/internal/__init__.py
diff --git a/docs_src/bigger_applications/app/internal/admin.py b/docs_src/bigger_applications/app_py39/internal/admin.py
similarity index 100%
rename from docs_src/bigger_applications/app/internal/admin.py
rename to docs_src/bigger_applications/app_py39/internal/admin.py
diff --git a/docs_src/bigger_applications/app/main.py b/docs_src/bigger_applications/app_py39/main.py
similarity index 100%
rename from docs_src/bigger_applications/app/main.py
rename to docs_src/bigger_applications/app_py39/main.py
diff --git a/docs_src/settings/app03/__init__.py b/docs_src/bigger_applications/app_py39/routers/__init__.py
similarity index 100%
rename from docs_src/settings/app03/__init__.py
rename to docs_src/bigger_applications/app_py39/routers/__init__.py
diff --git a/docs_src/bigger_applications/app/routers/items.py b/docs_src/bigger_applications/app_py39/routers/items.py
similarity index 100%
rename from docs_src/bigger_applications/app/routers/items.py
rename to docs_src/bigger_applications/app_py39/routers/items.py
diff --git a/docs_src/bigger_applications/app/routers/users.py b/docs_src/bigger_applications/app_py39/routers/users.py
similarity index 100%
rename from docs_src/bigger_applications/app/routers/users.py
rename to docs_src/bigger_applications/app_py39/routers/users.py
diff --git a/docs_src/settings/app03_an/__init__.py b/docs_src/body/__init__.py
similarity index 100%
rename from docs_src/settings/app03_an/__init__.py
rename to docs_src/body/__init__.py
diff --git a/docs_src/body/tutorial001.py b/docs_src/body/tutorial001_py39.py
similarity index 100%
rename from docs_src/body/tutorial001.py
rename to docs_src/body/tutorial001_py39.py
diff --git a/docs_src/body/tutorial002.py b/docs_src/body/tutorial002.py
deleted file mode 100644
index 5cd86216b..000000000
--- a/docs_src/body/tutorial002.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-app = FastAPI()
-
-
-@app.post("/items/")
-async def create_item(item: Item):
- item_dict = item.dict()
- if item.tax is not None:
- price_with_tax = item.price + item.tax
- item_dict.update({"price_with_tax": price_with_tax})
- return item_dict
diff --git a/docs_src/body/tutorial002_py310.py b/docs_src/body/tutorial002_py310.py
index 454c45c88..a829a4dc9 100644
--- a/docs_src/body/tutorial002_py310.py
+++ b/docs_src/body/tutorial002_py310.py
@@ -14,7 +14,7 @@ app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
- item_dict = item.dict()
+ item_dict = item.model_dump()
if item.tax is not None:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
diff --git a/docs_src/body/tutorial002_py39.py b/docs_src/body/tutorial002_py39.py
new file mode 100644
index 000000000..fb212e8e7
--- /dev/null
+++ b/docs_src/body/tutorial002_py39.py
@@ -0,0 +1,23 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+async def create_item(item: Item):
+ item_dict = item.model_dump()
+ if item.tax is not None:
+ price_with_tax = item.price + item.tax
+ item_dict.update({"price_with_tax": price_with_tax})
+ return item_dict
diff --git a/docs_src/body/tutorial003.py b/docs_src/body/tutorial003.py
deleted file mode 100644
index 2f33cc038..000000000
--- a/docs_src/body/tutorial003.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-app = FastAPI()
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- return {"item_id": item_id, **item.dict()}
diff --git a/docs_src/body/tutorial003_py310.py b/docs_src/body/tutorial003_py310.py
index 440b210e6..51ac8aafa 100644
--- a/docs_src/body/tutorial003_py310.py
+++ b/docs_src/body/tutorial003_py310.py
@@ -14,4 +14,4 @@ app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
- return {"item_id": item_id, **item.dict()}
+ return {"item_id": item_id, **item.model_dump()}
diff --git a/docs_src/body/tutorial003_py39.py b/docs_src/body/tutorial003_py39.py
new file mode 100644
index 000000000..636ba2275
--- /dev/null
+++ b/docs_src/body/tutorial003_py39.py
@@ -0,0 +1,19 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+app = FastAPI()
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ return {"item_id": item_id, **item.model_dump()}
diff --git a/docs_src/body/tutorial004.py b/docs_src/body/tutorial004.py
deleted file mode 100644
index 0671e0a27..000000000
--- a/docs_src/body/tutorial004.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-app = FastAPI()
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item, q: Union[str, None] = None):
- result = {"item_id": item_id, **item.dict()}
- if q:
- result.update({"q": q})
- return result
diff --git a/docs_src/body/tutorial004_py310.py b/docs_src/body/tutorial004_py310.py
index b352b70ab..53b10d97b 100644
--- a/docs_src/body/tutorial004_py310.py
+++ b/docs_src/body/tutorial004_py310.py
@@ -14,7 +14,7 @@ app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, q: str | None = None):
- result = {"item_id": item_id, **item.dict()}
+ result = {"item_id": item_id, **item.model_dump()}
if q:
result.update({"q": q})
return result
diff --git a/docs_src/body/tutorial004_py39.py b/docs_src/body/tutorial004_py39.py
new file mode 100644
index 000000000..2c157abfa
--- /dev/null
+++ b/docs_src/body/tutorial004_py39.py
@@ -0,0 +1,22 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+app = FastAPI()
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item, q: Union[str, None] = None):
+ result = {"item_id": item_id, **item.model_dump()}
+ if q:
+ result.update({"q": q})
+ return result
diff --git a/tests/test_filter_pydantic_sub_model/__init__.py b/docs_src/body_fields/__init__.py
similarity index 100%
rename from tests/test_filter_pydantic_sub_model/__init__.py
rename to docs_src/body_fields/__init__.py
diff --git a/docs_src/body_fields/tutorial001_an.py b/docs_src/body_fields/tutorial001_an.py
deleted file mode 100644
index 15ea1b53d..000000000
--- a/docs_src/body_fields/tutorial001_an.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel, Field
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = Field(
- default=None, title="The description of the item", max_length=300
- )
- price: float = Field(gt=0, description="The price must be greater than zero")
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_fields/tutorial001.py b/docs_src/body_fields/tutorial001_py39.py
similarity index 100%
rename from docs_src/body_fields/tutorial001.py
rename to docs_src/body_fields/tutorial001_py39.py
diff --git a/docs/en/mkdocs.no-insiders.yml b/docs_src/body_multiple_params/__init__.py
similarity index 100%
rename from docs/en/mkdocs.no-insiders.yml
rename to docs_src/body_multiple_params/__init__.py
diff --git a/docs_src/body_multiple_params/tutorial001_an.py b/docs_src/body_multiple_params/tutorial001_an.py
deleted file mode 100644
index 308eee854..000000000
--- a/docs_src/body_multiple_params/tutorial001_an.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Path
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(
- item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
- q: Union[str, None] = None,
- item: Union[Item, None] = None,
-):
- results = {"item_id": item_id}
- if q:
- results.update({"q": q})
- if item:
- results.update({"item": item})
- return results
diff --git a/docs_src/body_multiple_params/tutorial001.py b/docs_src/body_multiple_params/tutorial001_py39.py
similarity index 100%
rename from docs_src/body_multiple_params/tutorial001.py
rename to docs_src/body_multiple_params/tutorial001_py39.py
diff --git a/docs_src/body_multiple_params/tutorial002.py b/docs_src/body_multiple_params/tutorial002_py39.py
similarity index 100%
rename from docs_src/body_multiple_params/tutorial002.py
rename to docs_src/body_multiple_params/tutorial002_py39.py
diff --git a/docs_src/body_multiple_params/tutorial003_an.py b/docs_src/body_multiple_params/tutorial003_an.py
deleted file mode 100644
index 39ef7340a..000000000
--- a/docs_src/body_multiple_params/tutorial003_an.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-class User(BaseModel):
- username: str
- full_name: Union[str, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(
- item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
-):
- results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
- return results
diff --git a/docs_src/body_multiple_params/tutorial003.py b/docs_src/body_multiple_params/tutorial003_py39.py
similarity index 100%
rename from docs_src/body_multiple_params/tutorial003.py
rename to docs_src/body_multiple_params/tutorial003_py39.py
diff --git a/docs_src/body_multiple_params/tutorial004_an.py b/docs_src/body_multiple_params/tutorial004_an.py
deleted file mode 100644
index f6830f392..000000000
--- a/docs_src/body_multiple_params/tutorial004_an.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-class User(BaseModel):
- username: str
- full_name: Union[str, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(
- *,
- item_id: int,
- item: Item,
- user: User,
- importance: Annotated[int, Body(gt=0)],
- q: Union[str, None] = None,
-):
- results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/body_multiple_params/tutorial004.py b/docs_src/body_multiple_params/tutorial004_py39.py
similarity index 100%
rename from docs_src/body_multiple_params/tutorial004.py
rename to docs_src/body_multiple_params/tutorial004_py39.py
diff --git a/docs_src/body_multiple_params/tutorial005_an.py b/docs_src/body_multiple_params/tutorial005_an.py
deleted file mode 100644
index dadde80b5..000000000
--- a/docs_src/body_multiple_params/tutorial005_an.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_multiple_params/tutorial005.py b/docs_src/body_multiple_params/tutorial005_py39.py
similarity index 100%
rename from docs_src/body_multiple_params/tutorial005.py
rename to docs_src/body_multiple_params/tutorial005_py39.py
diff --git a/docs_src/body_nested_models/__init__.py b/docs_src/body_nested_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/body_nested_models/tutorial001.py b/docs_src/body_nested_models/tutorial001_py39.py
similarity index 100%
rename from docs_src/body_nested_models/tutorial001.py
rename to docs_src/body_nested_models/tutorial001_py39.py
diff --git a/docs_src/body_nested_models/tutorial002.py b/docs_src/body_nested_models/tutorial002.py
deleted file mode 100644
index 155cff788..000000000
--- a/docs_src/body_nested_models/tutorial002.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: List[str] = []
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial003.py b/docs_src/body_nested_models/tutorial003.py
deleted file mode 100644
index 84ed18bf4..000000000
--- a/docs_src/body_nested_models/tutorial003.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import Set, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: Set[str] = set()
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial004.py b/docs_src/body_nested_models/tutorial004.py
deleted file mode 100644
index a07bfacac..000000000
--- a/docs_src/body_nested_models/tutorial004.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Set, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Image(BaseModel):
- url: str
- name: str
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: Set[str] = set()
- image: Union[Image, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial005.py b/docs_src/body_nested_models/tutorial005.py
deleted file mode 100644
index 5a01264ed..000000000
--- a/docs_src/body_nested_models/tutorial005.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Set, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel, HttpUrl
-
-app = FastAPI()
-
-
-class Image(BaseModel):
- url: HttpUrl
- name: str
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: Set[str] = set()
- image: Union[Image, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial006.py b/docs_src/body_nested_models/tutorial006.py
deleted file mode 100644
index 75f1f30e3..000000000
--- a/docs_src/body_nested_models/tutorial006.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import List, Set, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel, HttpUrl
-
-app = FastAPI()
-
-
-class Image(BaseModel):
- url: HttpUrl
- name: str
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: Set[str] = set()
- images: Union[List[Image], None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/body_nested_models/tutorial007.py b/docs_src/body_nested_models/tutorial007.py
deleted file mode 100644
index 641f09dce..000000000
--- a/docs_src/body_nested_models/tutorial007.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from typing import List, Set, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel, HttpUrl
-
-app = FastAPI()
-
-
-class Image(BaseModel):
- url: HttpUrl
- name: str
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: Set[str] = set()
- images: Union[List[Image], None] = None
-
-
-class Offer(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- items: List[Item]
-
-
-@app.post("/offers/")
-async def create_offer(offer: Offer):
- return offer
diff --git a/docs_src/body_nested_models/tutorial008.py b/docs_src/body_nested_models/tutorial008.py
deleted file mode 100644
index 3431cc636..000000000
--- a/docs_src/body_nested_models/tutorial008.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI
-from pydantic import BaseModel, HttpUrl
-
-app = FastAPI()
-
-
-class Image(BaseModel):
- url: HttpUrl
- name: str
-
-
-@app.post("/images/multiple/")
-async def create_multiple_images(images: List[Image]):
- return images
diff --git a/docs_src/body_nested_models/tutorial009.py b/docs_src/body_nested_models/tutorial009.py
deleted file mode 100644
index 41dce946e..000000000
--- a/docs_src/body_nested_models/tutorial009.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from typing import Dict
-
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.post("/index-weights/")
-async def create_index_weights(weights: Dict[int, float]):
- return weights
diff --git a/docs_src/body_updates/__init__.py b/docs_src/body_updates/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/body_updates/tutorial001.py b/docs_src/body_updates/tutorial001.py
deleted file mode 100644
index 4e65d77e2..000000000
--- a/docs_src/body_updates/tutorial001.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI
-from fastapi.encoders import jsonable_encoder
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: Union[str, None] = None
- description: Union[str, None] = None
- price: Union[float, None] = None
- tax: float = 10.5
- tags: List[str] = []
-
-
-items = {
- "foo": {"name": "Foo", "price": 50.2},
- "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
- "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
-}
-
-
-@app.get("/items/{item_id}", response_model=Item)
-async def read_item(item_id: str):
- return items[item_id]
-
-
-@app.put("/items/{item_id}", response_model=Item)
-async def update_item(item_id: str, item: Item):
- update_item_encoded = jsonable_encoder(item)
- items[item_id] = update_item_encoded
- return update_item_encoded
diff --git a/docs_src/body_updates/tutorial002.py b/docs_src/body_updates/tutorial002.py
deleted file mode 100644
index c3a0fe79e..000000000
--- a/docs_src/body_updates/tutorial002.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI
-from fastapi.encoders import jsonable_encoder
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: Union[str, None] = None
- description: Union[str, None] = None
- price: Union[float, None] = None
- tax: float = 10.5
- tags: List[str] = []
-
-
-items = {
- "foo": {"name": "Foo", "price": 50.2},
- "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
- "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
-}
-
-
-@app.get("/items/{item_id}", response_model=Item)
-async def read_item(item_id: str):
- return items[item_id]
-
-
-@app.patch("/items/{item_id}", response_model=Item)
-async def update_item(item_id: str, item: Item):
- stored_item_data = items[item_id]
- stored_item_model = Item(**stored_item_data)
- update_data = item.dict(exclude_unset=True)
- updated_item = stored_item_model.copy(update=update_data)
- items[item_id] = jsonable_encoder(updated_item)
- return updated_item
diff --git a/docs_src/body_updates/tutorial002_py310.py b/docs_src/body_updates/tutorial002_py310.py
index 349841496..e5db71110 100644
--- a/docs_src/body_updates/tutorial002_py310.py
+++ b/docs_src/body_updates/tutorial002_py310.py
@@ -29,7 +29,7 @@ async def read_item(item_id: str):
async def update_item(item_id: str, item: Item):
stored_item_data = items[item_id]
stored_item_model = Item(**stored_item_data)
- update_data = item.dict(exclude_unset=True)
- updated_item = stored_item_model.copy(update=update_data)
+ update_data = item.model_dump(exclude_unset=True)
+ updated_item = stored_item_model.model_copy(update=update_data)
items[item_id] = jsonable_encoder(updated_item)
return updated_item
diff --git a/docs_src/body_updates/tutorial002_py39.py b/docs_src/body_updates/tutorial002_py39.py
index eb35b3521..eddd7af71 100644
--- a/docs_src/body_updates/tutorial002_py39.py
+++ b/docs_src/body_updates/tutorial002_py39.py
@@ -31,7 +31,7 @@ async def read_item(item_id: str):
async def update_item(item_id: str, item: Item):
stored_item_data = items[item_id]
stored_item_model = Item(**stored_item_data)
- update_data = item.dict(exclude_unset=True)
- updated_item = stored_item_model.copy(update=update_data)
+ update_data = item.model_dump(exclude_unset=True)
+ updated_item = stored_item_model.model_copy(update=update_data)
items[item_id] = jsonable_encoder(updated_item)
return updated_item
diff --git a/docs_src/conditional_openapi/__init__.py b/docs_src/conditional_openapi/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/conditional_openapi/tutorial001.py b/docs_src/conditional_openapi/tutorial001_py39.py
similarity index 100%
rename from docs_src/conditional_openapi/tutorial001.py
rename to docs_src/conditional_openapi/tutorial001_py39.py
diff --git a/docs_src/configure_swagger_ui/__init__.py b/docs_src/configure_swagger_ui/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/configure_swagger_ui/tutorial001.py b/docs_src/configure_swagger_ui/tutorial001_py39.py
similarity index 100%
rename from docs_src/configure_swagger_ui/tutorial001.py
rename to docs_src/configure_swagger_ui/tutorial001_py39.py
diff --git a/docs_src/configure_swagger_ui/tutorial002.py b/docs_src/configure_swagger_ui/tutorial002_py39.py
similarity index 100%
rename from docs_src/configure_swagger_ui/tutorial002.py
rename to docs_src/configure_swagger_ui/tutorial002_py39.py
diff --git a/docs_src/configure_swagger_ui/tutorial003.py b/docs_src/configure_swagger_ui/tutorial003_py39.py
similarity index 100%
rename from docs_src/configure_swagger_ui/tutorial003.py
rename to docs_src/configure_swagger_ui/tutorial003_py39.py
diff --git a/docs_src/cookie_param_models/__init__.py b/docs_src/cookie_param_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/cookie_param_models/tutorial001_an.py b/docs_src/cookie_param_models/tutorial001_an.py
deleted file mode 100644
index e5839ffd5..000000000
--- a/docs_src/cookie_param_models/tutorial001_an.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from typing import Union
-
-from fastapi import Cookie, FastAPI
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class Cookies(BaseModel):
- session_id: str
- fatebook_tracker: Union[str, None] = None
- googall_tracker: Union[str, None] = None
-
-
-@app.get("/items/")
-async def read_items(cookies: Annotated[Cookies, Cookie()]):
- return cookies
diff --git a/docs_src/cookie_param_models/tutorial001.py b/docs_src/cookie_param_models/tutorial001_py39.py
similarity index 100%
rename from docs_src/cookie_param_models/tutorial001.py
rename to docs_src/cookie_param_models/tutorial001_py39.py
diff --git a/docs_src/cookie_param_models/tutorial002_an.py b/docs_src/cookie_param_models/tutorial002_an.py
deleted file mode 100644
index ce5644b7b..000000000
--- a/docs_src/cookie_param_models/tutorial002_an.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import Union
-
-from fastapi import Cookie, FastAPI
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class Cookies(BaseModel):
- model_config = {"extra": "forbid"}
-
- session_id: str
- fatebook_tracker: Union[str, None] = None
- googall_tracker: Union[str, None] = None
-
-
-@app.get("/items/")
-async def read_items(cookies: Annotated[Cookies, Cookie()]):
- return cookies
diff --git a/docs_src/cookie_param_models/tutorial002_pv1.py b/docs_src/cookie_param_models/tutorial002_pv1.py
deleted file mode 100644
index 13f78b850..000000000
--- a/docs_src/cookie_param_models/tutorial002_pv1.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import Union
-
-from fastapi import Cookie, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Cookies(BaseModel):
- class Config:
- extra = "forbid"
-
- session_id: str
- fatebook_tracker: Union[str, None] = None
- googall_tracker: Union[str, None] = None
-
-
-@app.get("/items/")
-async def read_items(cookies: Cookies = Cookie()):
- return cookies
diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an.py b/docs_src/cookie_param_models/tutorial002_pv1_an.py
deleted file mode 100644
index ddfda9b6f..000000000
--- a/docs_src/cookie_param_models/tutorial002_pv1_an.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from typing import Union
-
-from fastapi import Cookie, FastAPI
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class Cookies(BaseModel):
- class Config:
- extra = "forbid"
-
- session_id: str
- fatebook_tracker: Union[str, None] = None
- googall_tracker: Union[str, None] = None
-
-
-@app.get("/items/")
-async def read_items(cookies: Annotated[Cookies, Cookie()]):
- return cookies
diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py b/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py
deleted file mode 100644
index ac00360b6..000000000
--- a/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import Annotated
-
-from fastapi import Cookie, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Cookies(BaseModel):
- class Config:
- extra = "forbid"
-
- session_id: str
- fatebook_tracker: str | None = None
- googall_tracker: str | None = None
-
-
-@app.get("/items/")
-async def read_items(cookies: Annotated[Cookies, Cookie()]):
- return cookies
diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py b/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py
deleted file mode 100644
index 573caea4b..000000000
--- a/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import Annotated, Union
-
-from fastapi import Cookie, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Cookies(BaseModel):
- class Config:
- extra = "forbid"
-
- session_id: str
- fatebook_tracker: Union[str, None] = None
- googall_tracker: Union[str, None] = None
-
-
-@app.get("/items/")
-async def read_items(cookies: Annotated[Cookies, Cookie()]):
- return cookies
diff --git a/docs_src/cookie_param_models/tutorial002_pv1_py310.py b/docs_src/cookie_param_models/tutorial002_pv1_py310.py
deleted file mode 100644
index 2c59aad12..000000000
--- a/docs_src/cookie_param_models/tutorial002_pv1_py310.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from fastapi import Cookie, FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Cookies(BaseModel):
- class Config:
- extra = "forbid"
-
- session_id: str
- fatebook_tracker: str | None = None
- googall_tracker: str | None = None
-
-
-@app.get("/items/")
-async def read_items(cookies: Cookies = Cookie()):
- return cookies
diff --git a/docs_src/cookie_param_models/tutorial002.py b/docs_src/cookie_param_models/tutorial002_py39.py
similarity index 100%
rename from docs_src/cookie_param_models/tutorial002.py
rename to docs_src/cookie_param_models/tutorial002_py39.py
diff --git a/docs_src/cookie_params/__init__.py b/docs_src/cookie_params/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/cookie_params/tutorial001_an.py b/docs_src/cookie_params/tutorial001_an.py
deleted file mode 100644
index 6d5931229..000000000
--- a/docs_src/cookie_params/tutorial001_an.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from typing import Union
-
-from fastapi import Cookie, FastAPI
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(ads_id: Annotated[Union[str, None], Cookie()] = None):
- return {"ads_id": ads_id}
diff --git a/docs_src/cookie_params/tutorial001.py b/docs_src/cookie_params/tutorial001_py39.py
similarity index 100%
rename from docs_src/cookie_params/tutorial001.py
rename to docs_src/cookie_params/tutorial001_py39.py
diff --git a/docs_src/cors/__init__.py b/docs_src/cors/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/cors/tutorial001.py b/docs_src/cors/tutorial001_py39.py
similarity index 100%
rename from docs_src/cors/tutorial001.py
rename to docs_src/cors/tutorial001_py39.py
diff --git a/docs_src/custom_docs_ui/__init__.py b/docs_src/custom_docs_ui/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/custom_docs_ui/tutorial001.py b/docs_src/custom_docs_ui/tutorial001_py39.py
similarity index 100%
rename from docs_src/custom_docs_ui/tutorial001.py
rename to docs_src/custom_docs_ui/tutorial001_py39.py
diff --git a/docs_src/custom_docs_ui/tutorial002.py b/docs_src/custom_docs_ui/tutorial002_py39.py
similarity index 100%
rename from docs_src/custom_docs_ui/tutorial002.py
rename to docs_src/custom_docs_ui/tutorial002_py39.py
diff --git a/docs_src/custom_request_and_route/__init__.py b/docs_src/custom_request_and_route/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/custom_request_and_route/tutorial001.py b/docs_src/custom_request_and_route/tutorial001.py
deleted file mode 100644
index 268ce9019..000000000
--- a/docs_src/custom_request_and_route/tutorial001.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import gzip
-from typing import Callable, List
-
-from fastapi import Body, FastAPI, Request, Response
-from fastapi.routing import APIRoute
-
-
-class GzipRequest(Request):
- async def body(self) -> bytes:
- if not hasattr(self, "_body"):
- body = await super().body()
- if "gzip" in self.headers.getlist("Content-Encoding"):
- body = gzip.decompress(body)
- self._body = body
- return self._body
-
-
-class GzipRoute(APIRoute):
- def get_route_handler(self) -> Callable:
- original_route_handler = super().get_route_handler()
-
- async def custom_route_handler(request: Request) -> Response:
- request = GzipRequest(request.scope, request.receive)
- return await original_route_handler(request)
-
- return custom_route_handler
-
-
-app = FastAPI()
-app.router.route_class = GzipRoute
-
-
-@app.post("/sum")
-async def sum_numbers(numbers: List[int] = Body()):
- return {"sum": sum(numbers)}
diff --git a/docs_src/custom_request_and_route/tutorial001_an_py310.py b/docs_src/custom_request_and_route/tutorial001_an_py310.py
new file mode 100644
index 000000000..381bab6d8
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial001_an_py310.py
@@ -0,0 +1,36 @@
+import gzip
+from collections.abc import Callable
+from typing import Annotated
+
+from fastapi import Body, FastAPI, Request, Response
+from fastapi.routing import APIRoute
+
+
+class GzipRequest(Request):
+ async def body(self) -> bytes:
+ if not hasattr(self, "_body"):
+ body = await super().body()
+ if "gzip" in self.headers.getlist("Content-Encoding"):
+ body = gzip.decompress(body)
+ self._body = body
+ return self._body
+
+
+class GzipRoute(APIRoute):
+ def get_route_handler(self) -> Callable:
+ original_route_handler = super().get_route_handler()
+
+ async def custom_route_handler(request: Request) -> Response:
+ request = GzipRequest(request.scope, request.receive)
+ return await original_route_handler(request)
+
+ return custom_route_handler
+
+
+app = FastAPI()
+app.router.route_class = GzipRoute
+
+
+@app.post("/sum")
+async def sum_numbers(numbers: Annotated[list[int], Body()]):
+ return {"sum": sum(numbers)}
diff --git a/docs_src/custom_request_and_route/tutorial001_an_py39.py b/docs_src/custom_request_and_route/tutorial001_an_py39.py
new file mode 100644
index 000000000..076727e64
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial001_an_py39.py
@@ -0,0 +1,35 @@
+import gzip
+from typing import Annotated, Callable
+
+from fastapi import Body, FastAPI, Request, Response
+from fastapi.routing import APIRoute
+
+
+class GzipRequest(Request):
+ async def body(self) -> bytes:
+ if not hasattr(self, "_body"):
+ body = await super().body()
+ if "gzip" in self.headers.getlist("Content-Encoding"):
+ body = gzip.decompress(body)
+ self._body = body
+ return self._body
+
+
+class GzipRoute(APIRoute):
+ def get_route_handler(self) -> Callable:
+ original_route_handler = super().get_route_handler()
+
+ async def custom_route_handler(request: Request) -> Response:
+ request = GzipRequest(request.scope, request.receive)
+ return await original_route_handler(request)
+
+ return custom_route_handler
+
+
+app = FastAPI()
+app.router.route_class = GzipRoute
+
+
+@app.post("/sum")
+async def sum_numbers(numbers: Annotated[list[int], Body()]):
+ return {"sum": sum(numbers)}
diff --git a/docs_src/custom_request_and_route/tutorial001_py310.py b/docs_src/custom_request_and_route/tutorial001_py310.py
new file mode 100644
index 000000000..c678088ce
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial001_py310.py
@@ -0,0 +1,35 @@
+import gzip
+from collections.abc import Callable
+
+from fastapi import Body, FastAPI, Request, Response
+from fastapi.routing import APIRoute
+
+
+class GzipRequest(Request):
+ async def body(self) -> bytes:
+ if not hasattr(self, "_body"):
+ body = await super().body()
+ if "gzip" in self.headers.getlist("Content-Encoding"):
+ body = gzip.decompress(body)
+ self._body = body
+ return self._body
+
+
+class GzipRoute(APIRoute):
+ def get_route_handler(self) -> Callable:
+ original_route_handler = super().get_route_handler()
+
+ async def custom_route_handler(request: Request) -> Response:
+ request = GzipRequest(request.scope, request.receive)
+ return await original_route_handler(request)
+
+ return custom_route_handler
+
+
+app = FastAPI()
+app.router.route_class = GzipRoute
+
+
+@app.post("/sum")
+async def sum_numbers(numbers: list[int] = Body()):
+ return {"sum": sum(numbers)}
diff --git a/docs_src/custom_request_and_route/tutorial001_py39.py b/docs_src/custom_request_and_route/tutorial001_py39.py
new file mode 100644
index 000000000..54b20b942
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial001_py39.py
@@ -0,0 +1,35 @@
+import gzip
+from typing import Callable
+
+from fastapi import Body, FastAPI, Request, Response
+from fastapi.routing import APIRoute
+
+
+class GzipRequest(Request):
+ async def body(self) -> bytes:
+ if not hasattr(self, "_body"):
+ body = await super().body()
+ if "gzip" in self.headers.getlist("Content-Encoding"):
+ body = gzip.decompress(body)
+ self._body = body
+ return self._body
+
+
+class GzipRoute(APIRoute):
+ def get_route_handler(self) -> Callable:
+ original_route_handler = super().get_route_handler()
+
+ async def custom_route_handler(request: Request) -> Response:
+ request = GzipRequest(request.scope, request.receive)
+ return await original_route_handler(request)
+
+ return custom_route_handler
+
+
+app = FastAPI()
+app.router.route_class = GzipRoute
+
+
+@app.post("/sum")
+async def sum_numbers(numbers: list[int] = Body()):
+ return {"sum": sum(numbers)}
diff --git a/docs_src/custom_request_and_route/tutorial002.py b/docs_src/custom_request_and_route/tutorial002.py
deleted file mode 100644
index cee4a95f0..000000000
--- a/docs_src/custom_request_and_route/tutorial002.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from typing import Callable, List
-
-from fastapi import Body, FastAPI, HTTPException, Request, Response
-from fastapi.exceptions import RequestValidationError
-from fastapi.routing import APIRoute
-
-
-class ValidationErrorLoggingRoute(APIRoute):
- def get_route_handler(self) -> Callable:
- original_route_handler = super().get_route_handler()
-
- async def custom_route_handler(request: Request) -> Response:
- try:
- return await original_route_handler(request)
- except RequestValidationError as exc:
- body = await request.body()
- detail = {"errors": exc.errors(), "body": body.decode()}
- raise HTTPException(status_code=422, detail=detail)
-
- return custom_route_handler
-
-
-app = FastAPI()
-app.router.route_class = ValidationErrorLoggingRoute
-
-
-@app.post("/")
-async def sum_numbers(numbers: List[int] = Body()):
- return sum(numbers)
diff --git a/docs_src/custom_request_and_route/tutorial002_an_py310.py b/docs_src/custom_request_and_route/tutorial002_an_py310.py
new file mode 100644
index 000000000..69b7de485
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial002_an_py310.py
@@ -0,0 +1,30 @@
+from collections.abc import Callable
+from typing import Annotated
+
+from fastapi import Body, FastAPI, HTTPException, Request, Response
+from fastapi.exceptions import RequestValidationError
+from fastapi.routing import APIRoute
+
+
+class ValidationErrorLoggingRoute(APIRoute):
+ def get_route_handler(self) -> Callable:
+ original_route_handler = super().get_route_handler()
+
+ async def custom_route_handler(request: Request) -> Response:
+ try:
+ return await original_route_handler(request)
+ except RequestValidationError as exc:
+ body = await request.body()
+ detail = {"errors": exc.errors(), "body": body.decode()}
+ raise HTTPException(status_code=422, detail=detail)
+
+ return custom_route_handler
+
+
+app = FastAPI()
+app.router.route_class = ValidationErrorLoggingRoute
+
+
+@app.post("/")
+async def sum_numbers(numbers: Annotated[list[int], Body()]):
+ return sum(numbers)
diff --git a/docs_src/custom_request_and_route/tutorial002_an_py39.py b/docs_src/custom_request_and_route/tutorial002_an_py39.py
new file mode 100644
index 000000000..e7de09de4
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial002_an_py39.py
@@ -0,0 +1,29 @@
+from typing import Annotated, Callable
+
+from fastapi import Body, FastAPI, HTTPException, Request, Response
+from fastapi.exceptions import RequestValidationError
+from fastapi.routing import APIRoute
+
+
+class ValidationErrorLoggingRoute(APIRoute):
+ def get_route_handler(self) -> Callable:
+ original_route_handler = super().get_route_handler()
+
+ async def custom_route_handler(request: Request) -> Response:
+ try:
+ return await original_route_handler(request)
+ except RequestValidationError as exc:
+ body = await request.body()
+ detail = {"errors": exc.errors(), "body": body.decode()}
+ raise HTTPException(status_code=422, detail=detail)
+
+ return custom_route_handler
+
+
+app = FastAPI()
+app.router.route_class = ValidationErrorLoggingRoute
+
+
+@app.post("/")
+async def sum_numbers(numbers: Annotated[list[int], Body()]):
+ return sum(numbers)
diff --git a/docs_src/custom_request_and_route/tutorial002_py310.py b/docs_src/custom_request_and_route/tutorial002_py310.py
new file mode 100644
index 000000000..13a5ca542
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial002_py310.py
@@ -0,0 +1,29 @@
+from collections.abc import Callable
+
+from fastapi import Body, FastAPI, HTTPException, Request, Response
+from fastapi.exceptions import RequestValidationError
+from fastapi.routing import APIRoute
+
+
+class ValidationErrorLoggingRoute(APIRoute):
+ def get_route_handler(self) -> Callable:
+ original_route_handler = super().get_route_handler()
+
+ async def custom_route_handler(request: Request) -> Response:
+ try:
+ return await original_route_handler(request)
+ except RequestValidationError as exc:
+ body = await request.body()
+ detail = {"errors": exc.errors(), "body": body.decode()}
+ raise HTTPException(status_code=422, detail=detail)
+
+ return custom_route_handler
+
+
+app = FastAPI()
+app.router.route_class = ValidationErrorLoggingRoute
+
+
+@app.post("/")
+async def sum_numbers(numbers: list[int] = Body()):
+ return sum(numbers)
diff --git a/docs_src/custom_request_and_route/tutorial002_py39.py b/docs_src/custom_request_and_route/tutorial002_py39.py
new file mode 100644
index 000000000..c4e474828
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial002_py39.py
@@ -0,0 +1,29 @@
+from typing import Callable
+
+from fastapi import Body, FastAPI, HTTPException, Request, Response
+from fastapi.exceptions import RequestValidationError
+from fastapi.routing import APIRoute
+
+
+class ValidationErrorLoggingRoute(APIRoute):
+ def get_route_handler(self) -> Callable:
+ original_route_handler = super().get_route_handler()
+
+ async def custom_route_handler(request: Request) -> Response:
+ try:
+ return await original_route_handler(request)
+ except RequestValidationError as exc:
+ body = await request.body()
+ detail = {"errors": exc.errors(), "body": body.decode()}
+ raise HTTPException(status_code=422, detail=detail)
+
+ return custom_route_handler
+
+
+app = FastAPI()
+app.router.route_class = ValidationErrorLoggingRoute
+
+
+@app.post("/")
+async def sum_numbers(numbers: list[int] = Body()):
+ return sum(numbers)
diff --git a/docs_src/custom_request_and_route/tutorial003_py310.py b/docs_src/custom_request_and_route/tutorial003_py310.py
new file mode 100644
index 000000000..f4e60be61
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial003_py310.py
@@ -0,0 +1,39 @@
+import time
+from collections.abc import Callable
+
+from fastapi import APIRouter, FastAPI, Request, Response
+from fastapi.routing import APIRoute
+
+
+class TimedRoute(APIRoute):
+ def get_route_handler(self) -> Callable:
+ original_route_handler = super().get_route_handler()
+
+ async def custom_route_handler(request: Request) -> Response:
+ before = time.time()
+ response: Response = await original_route_handler(request)
+ duration = time.time() - before
+ response.headers["X-Response-Time"] = str(duration)
+ print(f"route duration: {duration}")
+ print(f"route response: {response}")
+ print(f"route response headers: {response.headers}")
+ return response
+
+ return custom_route_handler
+
+
+app = FastAPI()
+router = APIRouter(route_class=TimedRoute)
+
+
+@app.get("/")
+async def not_timed():
+ return {"message": "Not timed"}
+
+
+@router.get("/timed")
+async def timed():
+ return {"message": "It's the time of my life"}
+
+
+app.include_router(router)
diff --git a/docs_src/custom_request_and_route/tutorial003.py b/docs_src/custom_request_and_route/tutorial003_py39.py
similarity index 100%
rename from docs_src/custom_request_and_route/tutorial003.py
rename to docs_src/custom_request_and_route/tutorial003_py39.py
diff --git a/docs_src/custom_response/__init__.py b/docs_src/custom_response/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/custom_response/tutorial001.py b/docs_src/custom_response/tutorial001_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial001.py
rename to docs_src/custom_response/tutorial001_py39.py
diff --git a/docs_src/custom_response/tutorial001b.py b/docs_src/custom_response/tutorial001b_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial001b.py
rename to docs_src/custom_response/tutorial001b_py39.py
diff --git a/docs_src/custom_response/tutorial002.py b/docs_src/custom_response/tutorial002_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial002.py
rename to docs_src/custom_response/tutorial002_py39.py
diff --git a/docs_src/custom_response/tutorial003.py b/docs_src/custom_response/tutorial003_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial003.py
rename to docs_src/custom_response/tutorial003_py39.py
diff --git a/docs_src/custom_response/tutorial004.py b/docs_src/custom_response/tutorial004_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial004.py
rename to docs_src/custom_response/tutorial004_py39.py
diff --git a/docs_src/custom_response/tutorial005.py b/docs_src/custom_response/tutorial005_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial005.py
rename to docs_src/custom_response/tutorial005_py39.py
diff --git a/docs_src/custom_response/tutorial006.py b/docs_src/custom_response/tutorial006_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial006.py
rename to docs_src/custom_response/tutorial006_py39.py
diff --git a/docs_src/custom_response/tutorial006b.py b/docs_src/custom_response/tutorial006b_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial006b.py
rename to docs_src/custom_response/tutorial006b_py39.py
diff --git a/docs_src/custom_response/tutorial006c.py b/docs_src/custom_response/tutorial006c_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial006c.py
rename to docs_src/custom_response/tutorial006c_py39.py
diff --git a/docs_src/custom_response/tutorial007.py b/docs_src/custom_response/tutorial007_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial007.py
rename to docs_src/custom_response/tutorial007_py39.py
diff --git a/docs_src/custom_response/tutorial008.py b/docs_src/custom_response/tutorial008_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial008.py
rename to docs_src/custom_response/tutorial008_py39.py
diff --git a/docs_src/custom_response/tutorial009.py b/docs_src/custom_response/tutorial009_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial009.py
rename to docs_src/custom_response/tutorial009_py39.py
diff --git a/docs_src/custom_response/tutorial009b.py b/docs_src/custom_response/tutorial009b_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial009b.py
rename to docs_src/custom_response/tutorial009b_py39.py
diff --git a/docs_src/custom_response/tutorial009c.py b/docs_src/custom_response/tutorial009c_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial009c.py
rename to docs_src/custom_response/tutorial009c_py39.py
diff --git a/docs_src/custom_response/tutorial010.py b/docs_src/custom_response/tutorial010_py39.py
similarity index 100%
rename from docs_src/custom_response/tutorial010.py
rename to docs_src/custom_response/tutorial010_py39.py
diff --git a/docs_src/dataclasses/tutorial002.py b/docs_src/dataclasses/tutorial002.py
deleted file mode 100644
index ece2f150c..000000000
--- a/docs_src/dataclasses/tutorial002.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from dataclasses import dataclass, field
-from typing import List, Union
-
-from fastapi import FastAPI
-
-
-@dataclass
-class Item:
- 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("/items/next", response_model=Item)
-async def read_next_item():
- return {
- "name": "Island In The Moon",
- "price": 12.99,
- "description": "A place to be playin' and havin' fun",
- "tags": ["breater"],
- }
diff --git a/docs_src/dataclasses/tutorial003.py b/docs_src/dataclasses/tutorial003.py
deleted file mode 100644
index c61315513..000000000
--- a/docs_src/dataclasses/tutorial003.py
+++ /dev/null
@@ -1,55 +0,0 @@
-from dataclasses import field # (1)
-from typing import List, Union
-
-from fastapi import FastAPI
-from pydantic.dataclasses import dataclass # (2)
-
-
-@dataclass
-class Item:
- name: str
- description: Union[str, None] = None
-
-
-@dataclass
-class Author:
- name: str
- items: List[Item] = field(default_factory=list) # (3)
-
-
-app = FastAPI()
-
-
-@app.post("/authors/{author_id}/items/", response_model=Author) # (4)
-async def create_author_items(author_id: str, items: List[Item]): # (5)
- return {"name": author_id, "items": items} # (6)
-
-
-@app.get("/authors/", response_model=List[Author]) # (7)
-def get_authors(): # (8)
- return [ # (9)
- {
- "name": "Breaters",
- "items": [
- {
- "name": "Island In The Moon",
- "description": "A place to be playin' and havin' fun",
- },
- {"name": "Holy Buddies"},
- ],
- },
- {
- "name": "System of an Up",
- "items": [
- {
- "name": "Salt",
- "description": "The kombucha mushroom people's favorite",
- },
- {"name": "Pad Thai"},
- {
- "name": "Lonely Night",
- "description": "The mostests lonliest nightiest of allest",
- },
- ],
- },
- ]
diff --git a/docs_src/dataclasses_/__init__.py b/docs_src/dataclasses_/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/dataclasses_/tutorial001_py310.py b/docs_src/dataclasses_/tutorial001_py310.py
new file mode 100644
index 000000000..ab709a7c8
--- /dev/null
+++ b/docs_src/dataclasses_/tutorial001_py310.py
@@ -0,0 +1,19 @@
+from dataclasses import dataclass
+
+from fastapi import FastAPI
+
+
+@dataclass
+class Item:
+ name: str
+ price: float
+ description: str | None = None
+ tax: float | None = None
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/dataclasses/tutorial001.py b/docs_src/dataclasses_/tutorial001_py39.py
similarity index 100%
rename from docs_src/dataclasses/tutorial001.py
rename to docs_src/dataclasses_/tutorial001_py39.py
diff --git a/docs_src/dataclasses_/tutorial002_py310.py b/docs_src/dataclasses_/tutorial002_py310.py
new file mode 100644
index 000000000..e16249f1e
--- /dev/null
+++ b/docs_src/dataclasses_/tutorial002_py310.py
@@ -0,0 +1,25 @@
+from dataclasses import dataclass, field
+
+from fastapi import FastAPI
+
+
+@dataclass
+class Item:
+ name: str
+ price: float
+ tags: list[str] = field(default_factory=list)
+ description: str | None = None
+ tax: float | None = None
+
+
+app = FastAPI()
+
+
+@app.get("/items/next", response_model=Item)
+async def read_next_item():
+ return {
+ "name": "Island In The Moon",
+ "price": 12.99,
+ "description": "A place to be playin' and havin' fun",
+ "tags": ["breater"],
+ }
diff --git a/docs_src/dataclasses_/tutorial002_py39.py b/docs_src/dataclasses_/tutorial002_py39.py
new file mode 100644
index 000000000..0c23765d8
--- /dev/null
+++ b/docs_src/dataclasses_/tutorial002_py39.py
@@ -0,0 +1,26 @@
+from dataclasses import dataclass, field
+from typing import Union
+
+from fastapi import FastAPI
+
+
+@dataclass
+class Item:
+ 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("/items/next", response_model=Item)
+async def read_next_item():
+ return {
+ "name": "Island In The Moon",
+ "price": 12.99,
+ "description": "A place to be playin' and havin' fun",
+ "tags": ["breater"],
+ }
diff --git a/docs_src/dataclasses_/tutorial003_py310.py b/docs_src/dataclasses_/tutorial003_py310.py
new file mode 100644
index 000000000..9b9a3fd63
--- /dev/null
+++ b/docs_src/dataclasses_/tutorial003_py310.py
@@ -0,0 +1,54 @@
+from dataclasses import field # (1)
+
+from fastapi import FastAPI
+from pydantic.dataclasses import dataclass # (2)
+
+
+@dataclass
+class Item:
+ name: str
+ description: str | None = None
+
+
+@dataclass
+class Author:
+ name: str
+ items: list[Item] = field(default_factory=list) # (3)
+
+
+app = FastAPI()
+
+
+@app.post("/authors/{author_id}/items/", response_model=Author) # (4)
+async def create_author_items(author_id: str, items: list[Item]): # (5)
+ return {"name": author_id, "items": items} # (6)
+
+
+@app.get("/authors/", response_model=list[Author]) # (7)
+def get_authors(): # (8)
+ return [ # (9)
+ {
+ "name": "Breaters",
+ "items": [
+ {
+ "name": "Island In The Moon",
+ "description": "A place to be playin' and havin' fun",
+ },
+ {"name": "Holy Buddies"},
+ ],
+ },
+ {
+ "name": "System of an Up",
+ "items": [
+ {
+ "name": "Salt",
+ "description": "The kombucha mushroom people's favorite",
+ },
+ {"name": "Pad Thai"},
+ {
+ "name": "Lonely Night",
+ "description": "The mostests lonliest nightiest of allest",
+ },
+ ],
+ },
+ ]
diff --git a/docs_src/dataclasses_/tutorial003_py39.py b/docs_src/dataclasses_/tutorial003_py39.py
new file mode 100644
index 000000000..991708c00
--- /dev/null
+++ b/docs_src/dataclasses_/tutorial003_py39.py
@@ -0,0 +1,55 @@
+from dataclasses import field # (1)
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic.dataclasses import dataclass # (2)
+
+
+@dataclass
+class Item:
+ name: str
+ description: Union[str, None] = None
+
+
+@dataclass
+class Author:
+ name: str
+ items: list[Item] = field(default_factory=list) # (3)
+
+
+app = FastAPI()
+
+
+@app.post("/authors/{author_id}/items/", response_model=Author) # (4)
+async def create_author_items(author_id: str, items: list[Item]): # (5)
+ return {"name": author_id, "items": items} # (6)
+
+
+@app.get("/authors/", response_model=list[Author]) # (7)
+def get_authors(): # (8)
+ return [ # (9)
+ {
+ "name": "Breaters",
+ "items": [
+ {
+ "name": "Island In The Moon",
+ "description": "A place to be playin' and havin' fun",
+ },
+ {"name": "Holy Buddies"},
+ ],
+ },
+ {
+ "name": "System of an Up",
+ "items": [
+ {
+ "name": "Salt",
+ "description": "The kombucha mushroom people's favorite",
+ },
+ {"name": "Pad Thai"},
+ {
+ "name": "Lonely Night",
+ "description": "The mostests lonliest nightiest of allest",
+ },
+ ],
+ },
+ ]
diff --git a/docs_src/debugging/__init__.py b/docs_src/debugging/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/debugging/tutorial001.py b/docs_src/debugging/tutorial001_py39.py
similarity index 100%
rename from docs_src/debugging/tutorial001.py
rename to docs_src/debugging/tutorial001_py39.py
diff --git a/docs_src/dependencies/__init__.py b/docs_src/dependencies/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/dependencies/tutorial001_02_an.py b/docs_src/dependencies/tutorial001_02_an.py
deleted file mode 100644
index 455d60c82..000000000
--- a/docs_src/dependencies/tutorial001_02_an.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from typing import Union
-
-from fastapi import Depends, FastAPI
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-async def common_parameters(
- q: Union[str, None] = None, skip: int = 0, limit: int = 100
-):
- return {"q": q, "skip": skip, "limit": limit}
-
-
-CommonsDep = Annotated[dict, Depends(common_parameters)]
-
-
-@app.get("/items/")
-async def read_items(commons: CommonsDep):
- return commons
-
-
-@app.get("/users/")
-async def read_users(commons: CommonsDep):
- return commons
diff --git a/docs_src/dependencies/tutorial001_an.py b/docs_src/dependencies/tutorial001_an.py
deleted file mode 100644
index 81e24fe86..000000000
--- a/docs_src/dependencies/tutorial001_an.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import Union
-
-from fastapi import Depends, FastAPI
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-async def common_parameters(
- q: Union[str, None] = None, skip: int = 0, limit: int = 100
-):
- return {"q": q, "skip": skip, "limit": limit}
-
-
-@app.get("/items/")
-async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
- return commons
-
-
-@app.get("/users/")
-async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
- return commons
diff --git a/docs_src/dependencies/tutorial001.py b/docs_src/dependencies/tutorial001_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial001.py
rename to docs_src/dependencies/tutorial001_py39.py
diff --git a/docs_src/dependencies/tutorial002_an.py b/docs_src/dependencies/tutorial002_an.py
deleted file mode 100644
index 964ccf66c..000000000
--- a/docs_src/dependencies/tutorial002_an.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Union
-
-from fastapi import Depends, FastAPI
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
-
-
-class CommonQueryParams:
- def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
- self.q = q
- self.skip = skip
- self.limit = limit
-
-
-@app.get("/items/")
-async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
- response = {}
- if commons.q:
- response.update({"q": commons.q})
- items = fake_items_db[commons.skip : commons.skip + commons.limit]
- response.update({"items": items})
- return response
diff --git a/docs_src/dependencies/tutorial002.py b/docs_src/dependencies/tutorial002_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial002.py
rename to docs_src/dependencies/tutorial002_py39.py
diff --git a/docs_src/dependencies/tutorial003_an.py b/docs_src/dependencies/tutorial003_an.py
deleted file mode 100644
index ba8e9f717..000000000
--- a/docs_src/dependencies/tutorial003_an.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Any, Union
-
-from fastapi import Depends, FastAPI
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
-
-
-class CommonQueryParams:
- def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
- self.q = q
- self.skip = skip
- self.limit = limit
-
-
-@app.get("/items/")
-async def read_items(commons: Annotated[Any, Depends(CommonQueryParams)]):
- response = {}
- if commons.q:
- response.update({"q": commons.q})
- items = fake_items_db[commons.skip : commons.skip + commons.limit]
- response.update({"items": items})
- return response
diff --git a/docs_src/dependencies/tutorial003.py b/docs_src/dependencies/tutorial003_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial003.py
rename to docs_src/dependencies/tutorial003_py39.py
diff --git a/docs_src/dependencies/tutorial004_an.py b/docs_src/dependencies/tutorial004_an.py
deleted file mode 100644
index 78881a354..000000000
--- a/docs_src/dependencies/tutorial004_an.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Union
-
-from fastapi import Depends, FastAPI
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
-
-
-class CommonQueryParams:
- def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
- self.q = q
- self.skip = skip
- self.limit = limit
-
-
-@app.get("/items/")
-async def read_items(commons: Annotated[CommonQueryParams, Depends()]):
- response = {}
- if commons.q:
- response.update({"q": commons.q})
- items = fake_items_db[commons.skip : commons.skip + commons.limit]
- response.update({"items": items})
- return response
diff --git a/docs_src/dependencies/tutorial004.py b/docs_src/dependencies/tutorial004_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial004.py
rename to docs_src/dependencies/tutorial004_py39.py
diff --git a/docs_src/dependencies/tutorial005_an.py b/docs_src/dependencies/tutorial005_an.py
deleted file mode 100644
index 1d78c17a2..000000000
--- a/docs_src/dependencies/tutorial005_an.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import Union
-
-from fastapi import Cookie, Depends, FastAPI
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-def query_extractor(q: Union[str, None] = None):
- return q
-
-
-def query_or_cookie_extractor(
- q: Annotated[str, Depends(query_extractor)],
- last_query: Annotated[Union[str, None], Cookie()] = None,
-):
- if not q:
- return last_query
- return q
-
-
-@app.get("/items/")
-async def read_query(
- query_or_default: Annotated[str, Depends(query_or_cookie_extractor)],
-):
- return {"q_or_cookie": query_or_default}
diff --git a/docs_src/dependencies/tutorial005.py b/docs_src/dependencies/tutorial005_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial005.py
rename to docs_src/dependencies/tutorial005_py39.py
diff --git a/docs_src/dependencies/tutorial006_an.py b/docs_src/dependencies/tutorial006_an.py
deleted file mode 100644
index 5aaea04d1..000000000
--- a/docs_src/dependencies/tutorial006_an.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from fastapi import Depends, FastAPI, Header, HTTPException
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-async def verify_token(x_token: Annotated[str, Header()]):
- if x_token != "fake-super-secret-token":
- raise HTTPException(status_code=400, detail="X-Token header invalid")
-
-
-async def verify_key(x_key: Annotated[str, Header()]):
- if x_key != "fake-super-secret-key":
- raise HTTPException(status_code=400, detail="X-Key header invalid")
- return x_key
-
-
-@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
-async def read_items():
- return [{"item": "Foo"}, {"item": "Bar"}]
diff --git a/docs_src/dependencies/tutorial006.py b/docs_src/dependencies/tutorial006_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial006.py
rename to docs_src/dependencies/tutorial006_py39.py
diff --git a/docs_src/dependencies/tutorial007.py b/docs_src/dependencies/tutorial007_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial007.py
rename to docs_src/dependencies/tutorial007_py39.py
diff --git a/docs_src/dependencies/tutorial008_an.py b/docs_src/dependencies/tutorial008_an.py
deleted file mode 100644
index 2de86f042..000000000
--- a/docs_src/dependencies/tutorial008_an.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from fastapi import Depends
-from typing_extensions import Annotated
-
-
-async def dependency_a():
- dep_a = generate_dep_a()
- try:
- yield dep_a
- finally:
- dep_a.close()
-
-
-async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
- dep_b = generate_dep_b()
- try:
- yield dep_b
- finally:
- dep_b.close(dep_a)
-
-
-async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
- dep_c = generate_dep_c()
- try:
- yield dep_c
- finally:
- dep_c.close(dep_b)
diff --git a/docs_src/dependencies/tutorial008.py b/docs_src/dependencies/tutorial008_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial008.py
rename to docs_src/dependencies/tutorial008_py39.py
diff --git a/docs_src/dependencies/tutorial008b_an.py b/docs_src/dependencies/tutorial008b_an.py
deleted file mode 100644
index 84d8f12c1..000000000
--- a/docs_src/dependencies/tutorial008b_an.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from fastapi import Depends, FastAPI, HTTPException
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-data = {
- "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"},
- "portal-gun": {"description": "Gun to create portals", "owner": "Rick"},
-}
-
-
-class OwnerError(Exception):
- pass
-
-
-def get_username():
- try:
- yield "Rick"
- except OwnerError as e:
- raise HTTPException(status_code=400, detail=f"Owner error: {e}")
-
-
-@app.get("/items/{item_id}")
-def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
- if item_id not in data:
- raise HTTPException(status_code=404, detail="Item not found")
- item = data[item_id]
- if item["owner"] != username:
- raise OwnerError(username)
- return item
diff --git a/docs_src/dependencies/tutorial008b.py b/docs_src/dependencies/tutorial008b_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial008b.py
rename to docs_src/dependencies/tutorial008b_py39.py
diff --git a/docs_src/dependencies/tutorial008c_an.py b/docs_src/dependencies/tutorial008c_an.py
deleted file mode 100644
index 94f59f9aa..000000000
--- a/docs_src/dependencies/tutorial008c_an.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from fastapi import Depends, FastAPI, HTTPException
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class InternalError(Exception):
- pass
-
-
-def get_username():
- try:
- yield "Rick"
- except InternalError:
- print("Oops, we didn't raise again, Britney 😱")
-
-
-@app.get("/items/{item_id}")
-def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
- if item_id == "portal-gun":
- raise InternalError(
- f"The portal gun is too dangerous to be owned by {username}"
- )
- if item_id != "plumbus":
- raise HTTPException(
- status_code=404, detail="Item not found, there's only a plumbus here"
- )
- return item_id
diff --git a/docs_src/dependencies/tutorial008c.py b/docs_src/dependencies/tutorial008c_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial008c.py
rename to docs_src/dependencies/tutorial008c_py39.py
diff --git a/docs_src/dependencies/tutorial008d_an.py b/docs_src/dependencies/tutorial008d_an.py
deleted file mode 100644
index c35424574..000000000
--- a/docs_src/dependencies/tutorial008d_an.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from fastapi import Depends, FastAPI, HTTPException
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class InternalError(Exception):
- pass
-
-
-def get_username():
- try:
- yield "Rick"
- except InternalError:
- print("We don't swallow the internal error here, we raise again 😎")
- raise
-
-
-@app.get("/items/{item_id}")
-def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
- if item_id == "portal-gun":
- raise InternalError(
- f"The portal gun is too dangerous to be owned by {username}"
- )
- if item_id != "plumbus":
- raise HTTPException(
- status_code=404, detail="Item not found, there's only a plumbus here"
- )
- return item_id
diff --git a/docs_src/dependencies/tutorial008d.py b/docs_src/dependencies/tutorial008d_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial008d.py
rename to docs_src/dependencies/tutorial008d_py39.py
diff --git a/docs_src/dependencies/tutorial008e_an_py39.py b/docs_src/dependencies/tutorial008e_an_py39.py
new file mode 100644
index 000000000..80a44c7e2
--- /dev/null
+++ b/docs_src/dependencies/tutorial008e_an_py39.py
@@ -0,0 +1,17 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+def get_username():
+ try:
+ yield "Rick"
+ finally:
+ print("Cleanup up before response is sent")
+
+
+@app.get("/users/me")
+def get_user_me(username: Annotated[str, Depends(get_username, scope="function")]):
+ return username
diff --git a/docs_src/dependencies/tutorial008e_py39.py b/docs_src/dependencies/tutorial008e_py39.py
new file mode 100644
index 000000000..1ed056e91
--- /dev/null
+++ b/docs_src/dependencies/tutorial008e_py39.py
@@ -0,0 +1,15 @@
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+def get_username():
+ try:
+ yield "Rick"
+ finally:
+ print("Cleanup up before response is sent")
+
+
+@app.get("/users/me")
+def get_user_me(username: str = Depends(get_username, scope="function")):
+ return username
diff --git a/docs_src/dependencies/tutorial009.py b/docs_src/dependencies/tutorial009.py
deleted file mode 100644
index 8472f642d..000000000
--- a/docs_src/dependencies/tutorial009.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from fastapi import Depends
-
-
-async def dependency_a():
- dep_a = generate_dep_a()
- try:
- yield dep_a
- finally:
- dep_a.close()
-
-
-async def dependency_b(dep_a=Depends(dependency_a)):
- dep_b = generate_dep_b()
- try:
- yield dep_b
- finally:
- dep_b.close(dep_a)
-
-
-async def dependency_c(dep_b=Depends(dependency_b)):
- dep_c = generate_dep_c()
- try:
- yield dep_c
- finally:
- dep_c.close(dep_b)
diff --git a/docs_src/dependencies/tutorial010.py b/docs_src/dependencies/tutorial010_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial010.py
rename to docs_src/dependencies/tutorial010_py39.py
diff --git a/docs_src/dependencies/tutorial011_an.py b/docs_src/dependencies/tutorial011_an.py
deleted file mode 100644
index 6c13d9033..000000000
--- a/docs_src/dependencies/tutorial011_an.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from fastapi import Depends, FastAPI
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class FixedContentQueryChecker:
- def __init__(self, fixed_content: str):
- self.fixed_content = fixed_content
-
- def __call__(self, q: str = ""):
- if q:
- return self.fixed_content in q
- return False
-
-
-checker = FixedContentQueryChecker("bar")
-
-
-@app.get("/query-checker/")
-async def read_query_check(fixed_content_included: Annotated[bool, Depends(checker)]):
- return {"fixed_content_in_query": fixed_content_included}
diff --git a/docs_src/dependencies/tutorial011.py b/docs_src/dependencies/tutorial011_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial011.py
rename to docs_src/dependencies/tutorial011_py39.py
diff --git a/docs_src/dependencies/tutorial012_an.py b/docs_src/dependencies/tutorial012_an.py
deleted file mode 100644
index 7541e6bf4..000000000
--- a/docs_src/dependencies/tutorial012_an.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from fastapi import Depends, FastAPI, Header, HTTPException
-from typing_extensions import Annotated
-
-
-async def verify_token(x_token: Annotated[str, Header()]):
- if x_token != "fake-super-secret-token":
- raise HTTPException(status_code=400, detail="X-Token header invalid")
-
-
-async def verify_key(x_key: Annotated[str, Header()]):
- if x_key != "fake-super-secret-key":
- raise HTTPException(status_code=400, detail="X-Key header invalid")
- return x_key
-
-
-app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
-
-
-@app.get("/items/")
-async def read_items():
- return [{"item": "Portal Gun"}, {"item": "Plumbus"}]
-
-
-@app.get("/users/")
-async def read_users():
- return [{"username": "Rick"}, {"username": "Morty"}]
diff --git a/docs_src/dependencies/tutorial012_an_py39.py b/docs_src/dependencies/tutorial012_an_py39.py
index 7541e6bf4..6503591fc 100644
--- a/docs_src/dependencies/tutorial012_an_py39.py
+++ b/docs_src/dependencies/tutorial012_an_py39.py
@@ -1,5 +1,6 @@
+from typing import Annotated
+
from fastapi import Depends, FastAPI, Header, HTTPException
-from typing_extensions import Annotated
async def verify_token(x_token: Annotated[str, Header()]):
diff --git a/docs_src/dependencies/tutorial012.py b/docs_src/dependencies/tutorial012_py39.py
similarity index 100%
rename from docs_src/dependencies/tutorial012.py
rename to docs_src/dependencies/tutorial012_py39.py
diff --git a/docs_src/dependency_testing/__init__.py b/docs_src/dependency_testing/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/dependency_testing/tutorial001_an.py b/docs_src/dependency_testing/tutorial001_an.py
deleted file mode 100644
index 4c76a87ff..000000000
--- a/docs_src/dependency_testing/tutorial001_an.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from typing import Union
-
-from fastapi import Depends, FastAPI
-from fastapi.testclient import TestClient
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-async def common_parameters(
- q: Union[str, None] = None, skip: int = 0, limit: int = 100
-):
- return {"q": q, "skip": skip, "limit": limit}
-
-
-@app.get("/items/")
-async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
- return {"message": "Hello Items!", "params": commons}
-
-
-@app.get("/users/")
-async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
- return {"message": "Hello Users!", "params": commons}
-
-
-client = TestClient(app)
-
-
-async def override_dependency(q: Union[str, None] = None):
- return {"q": q, "skip": 5, "limit": 10}
-
-
-app.dependency_overrides[common_parameters] = override_dependency
-
-
-def test_override_in_items():
- response = client.get("/items/")
- assert response.status_code == 200
- assert response.json() == {
- "message": "Hello Items!",
- "params": {"q": None, "skip": 5, "limit": 10},
- }
-
-
-def test_override_in_items_with_q():
- response = client.get("/items/?q=foo")
- assert response.status_code == 200
- assert response.json() == {
- "message": "Hello Items!",
- "params": {"q": "foo", "skip": 5, "limit": 10},
- }
-
-
-def test_override_in_items_with_params():
- response = client.get("/items/?q=foo&skip=100&limit=200")
- assert response.status_code == 200
- assert response.json() == {
- "message": "Hello Items!",
- "params": {"q": "foo", "skip": 5, "limit": 10},
- }
diff --git a/docs_src/dependency_testing/tutorial001.py b/docs_src/dependency_testing/tutorial001_py39.py
similarity index 100%
rename from docs_src/dependency_testing/tutorial001.py
rename to docs_src/dependency_testing/tutorial001_py39.py
diff --git a/docs_src/encoder/__init__.py b/docs_src/encoder/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/encoder/tutorial001.py b/docs_src/encoder/tutorial001_py39.py
similarity index 100%
rename from docs_src/encoder/tutorial001.py
rename to docs_src/encoder/tutorial001_py39.py
diff --git a/docs_src/events/__init__.py b/docs_src/events/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/events/tutorial001.py b/docs_src/events/tutorial001_py39.py
similarity index 100%
rename from docs_src/events/tutorial001.py
rename to docs_src/events/tutorial001_py39.py
diff --git a/docs_src/events/tutorial002.py b/docs_src/events/tutorial002_py39.py
similarity index 100%
rename from docs_src/events/tutorial002.py
rename to docs_src/events/tutorial002_py39.py
diff --git a/docs_src/events/tutorial003.py b/docs_src/events/tutorial003_py39.py
similarity index 100%
rename from docs_src/events/tutorial003.py
rename to docs_src/events/tutorial003_py39.py
diff --git a/docs_src/extending_openapi/__init__.py b/docs_src/extending_openapi/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/extending_openapi/tutorial001.py b/docs_src/extending_openapi/tutorial001_py39.py
similarity index 100%
rename from docs_src/extending_openapi/tutorial001.py
rename to docs_src/extending_openapi/tutorial001_py39.py
diff --git a/docs_src/extra_data_types/__init__.py b/docs_src/extra_data_types/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/extra_data_types/tutorial001_an.py b/docs_src/extra_data_types/tutorial001_an.py
deleted file mode 100644
index 257d0c7c8..000000000
--- a/docs_src/extra_data_types/tutorial001_an.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from datetime import datetime, time, timedelta
-from typing import Union
-from uuid import UUID
-
-from fastapi import Body, FastAPI
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.put("/items/{item_id}")
-async def read_items(
- item_id: UUID,
- start_datetime: Annotated[datetime, Body()],
- end_datetime: Annotated[datetime, Body()],
- process_after: Annotated[timedelta, Body()],
- repeat_at: Annotated[Union[time, None], Body()] = None,
-):
- start_process = start_datetime + process_after
- duration = end_datetime - start_process
- return {
- "item_id": item_id,
- "start_datetime": start_datetime,
- "end_datetime": end_datetime,
- "process_after": process_after,
- "repeat_at": repeat_at,
- "start_process": start_process,
- "duration": duration,
- }
diff --git a/docs_src/extra_data_types/tutorial001.py b/docs_src/extra_data_types/tutorial001_py39.py
similarity index 100%
rename from docs_src/extra_data_types/tutorial001.py
rename to docs_src/extra_data_types/tutorial001_py39.py
diff --git a/docs_src/extra_models/__init__.py b/docs_src/extra_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/extra_models/tutorial001.py b/docs_src/extra_models/tutorial001.py
deleted file mode 100644
index 4be56cd2a..000000000
--- a/docs_src/extra_models/tutorial001.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel, EmailStr
-
-app = FastAPI()
-
-
-class UserIn(BaseModel):
- username: str
- password: str
- email: EmailStr
- full_name: Union[str, None] = None
-
-
-class UserOut(BaseModel):
- username: str
- email: EmailStr
- full_name: Union[str, None] = None
-
-
-class UserInDB(BaseModel):
- username: str
- hashed_password: str
- email: EmailStr
- full_name: Union[str, None] = None
-
-
-def fake_password_hasher(raw_password: str):
- return "supersecret" + raw_password
-
-
-def fake_save_user(user_in: UserIn):
- hashed_password = fake_password_hasher(user_in.password)
- user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
- print("User saved! ..not really")
- return user_in_db
-
-
-@app.post("/user/", response_model=UserOut)
-async def create_user(user_in: UserIn):
- user_saved = fake_save_user(user_in)
- return user_saved
diff --git a/docs_src/extra_models/tutorial001_py310.py b/docs_src/extra_models/tutorial001_py310.py
index 669386ae6..cf39142e4 100644
--- a/docs_src/extra_models/tutorial001_py310.py
+++ b/docs_src/extra_models/tutorial001_py310.py
@@ -30,7 +30,7 @@ def fake_password_hasher(raw_password: str):
def fake_save_user(user_in: UserIn):
hashed_password = fake_password_hasher(user_in.password)
- user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
+ user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
print("User saved! ..not really")
return user_in_db
diff --git a/docs_src/extra_models/tutorial001_py39.py b/docs_src/extra_models/tutorial001_py39.py
new file mode 100644
index 000000000..327ffcdf0
--- /dev/null
+++ b/docs_src/extra_models/tutorial001_py39.py
@@ -0,0 +1,43 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class UserIn(BaseModel):
+ username: str
+ password: str
+ email: EmailStr
+ full_name: Union[str, None] = None
+
+
+class UserOut(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: Union[str, None] = None
+
+
+class UserInDB(BaseModel):
+ username: str
+ hashed_password: str
+ email: EmailStr
+ full_name: Union[str, None] = None
+
+
+def fake_password_hasher(raw_password: str):
+ return "supersecret" + raw_password
+
+
+def fake_save_user(user_in: UserIn):
+ hashed_password = fake_password_hasher(user_in.password)
+ user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
+ print("User saved! ..not really")
+ return user_in_db
+
+
+@app.post("/user/", response_model=UserOut)
+async def create_user(user_in: UserIn):
+ user_saved = fake_save_user(user_in)
+ return user_saved
diff --git a/docs_src/extra_models/tutorial002.py b/docs_src/extra_models/tutorial002.py
deleted file mode 100644
index 70fa16441..000000000
--- a/docs_src/extra_models/tutorial002.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel, EmailStr
-
-app = FastAPI()
-
-
-class UserBase(BaseModel):
- username: str
- email: EmailStr
- full_name: Union[str, None] = None
-
-
-class UserIn(UserBase):
- password: str
-
-
-class UserOut(UserBase):
- pass
-
-
-class UserInDB(UserBase):
- hashed_password: str
-
-
-def fake_password_hasher(raw_password: str):
- return "supersecret" + raw_password
-
-
-def fake_save_user(user_in: UserIn):
- hashed_password = fake_password_hasher(user_in.password)
- user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
- print("User saved! ..not really")
- return user_in_db
-
-
-@app.post("/user/", response_model=UserOut)
-async def create_user(user_in: UserIn):
- user_saved = fake_save_user(user_in)
- return user_saved
diff --git a/docs_src/extra_models/tutorial002_py310.py b/docs_src/extra_models/tutorial002_py310.py
index 5b8ed7de3..e8a4f5f29 100644
--- a/docs_src/extra_models/tutorial002_py310.py
+++ b/docs_src/extra_models/tutorial002_py310.py
@@ -28,7 +28,7 @@ def fake_password_hasher(raw_password: str):
def fake_save_user(user_in: UserIn):
hashed_password = fake_password_hasher(user_in.password)
- user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
+ user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
print("User saved! ..not really")
return user_in_db
diff --git a/docs_src/extra_models/tutorial002_py39.py b/docs_src/extra_models/tutorial002_py39.py
new file mode 100644
index 000000000..654379601
--- /dev/null
+++ b/docs_src/extra_models/tutorial002_py39.py
@@ -0,0 +1,41 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class UserBase(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: Union[str, None] = None
+
+
+class UserIn(UserBase):
+ password: str
+
+
+class UserOut(UserBase):
+ pass
+
+
+class UserInDB(UserBase):
+ hashed_password: str
+
+
+def fake_password_hasher(raw_password: str):
+ return "supersecret" + raw_password
+
+
+def fake_save_user(user_in: UserIn):
+ hashed_password = fake_password_hasher(user_in.password)
+ user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
+ print("User saved! ..not really")
+ return user_in_db
+
+
+@app.post("/user/", response_model=UserOut)
+async def create_user(user_in: UserIn):
+ user_saved = fake_save_user(user_in)
+ return user_saved
diff --git a/docs_src/extra_models/tutorial003.py b/docs_src/extra_models/tutorial003_py39.py
similarity index 100%
rename from docs_src/extra_models/tutorial003.py
rename to docs_src/extra_models/tutorial003_py39.py
diff --git a/docs_src/extra_models/tutorial004.py b/docs_src/extra_models/tutorial004.py
deleted file mode 100644
index a8e0f7af5..000000000
--- a/docs_src/extra_models/tutorial004.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: str
-
-
-items = [
- {"name": "Foo", "description": "There comes my hero"},
- {"name": "Red", "description": "It's my aeroplane"},
-]
-
-
-@app.get("/items/", response_model=List[Item])
-async def read_items():
- return items
diff --git a/docs_src/extra_models/tutorial005.py b/docs_src/extra_models/tutorial005.py
deleted file mode 100644
index a81cbc2c5..000000000
--- a/docs_src/extra_models/tutorial005.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from typing import Dict
-
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/keyword-weights/", response_model=Dict[str, float])
-async def read_keyword_weights():
- return {"foo": 2.3, "bar": 3.4}
diff --git a/docs_src/first_steps/__init__.py b/docs_src/first_steps/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/first_steps/tutorial001.py b/docs_src/first_steps/tutorial001_py39.py
similarity index 100%
rename from docs_src/first_steps/tutorial001.py
rename to docs_src/first_steps/tutorial001_py39.py
diff --git a/docs_src/first_steps/tutorial002.py b/docs_src/first_steps/tutorial002.py
deleted file mode 100644
index ca7d48cff..000000000
--- a/docs_src/first_steps/tutorial002.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from fastapi import FastAPI
-
-my_awesome_api = FastAPI()
-
-
-@my_awesome_api.get("/")
-async def root():
- return {"message": "Hello World"}
diff --git a/docs_src/first_steps/tutorial003.py b/docs_src/first_steps/tutorial003_py39.py
similarity index 100%
rename from docs_src/first_steps/tutorial003.py
rename to docs_src/first_steps/tutorial003_py39.py
diff --git a/docs_src/generate_clients/__init__.py b/docs_src/generate_clients/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/generate_clients/tutorial001.py b/docs_src/generate_clients/tutorial001.py
deleted file mode 100644
index 2d1f91bc6..000000000
--- a/docs_src/generate_clients/tutorial001.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- price: float
-
-
-class ResponseMessage(BaseModel):
- message: str
-
-
-@app.post("/items/", response_model=ResponseMessage)
-async def create_item(item: Item):
- return {"message": "item received"}
-
-
-@app.get("/items/", response_model=List[Item])
-async def get_items():
- return [
- {"name": "Plumbus", "price": 3},
- {"name": "Portal Gun", "price": 9001},
- ]
diff --git a/docs_src/generate_clients/tutorial002.py b/docs_src/generate_clients/tutorial002.py
deleted file mode 100644
index bd80449af..000000000
--- a/docs_src/generate_clients/tutorial002.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- price: float
-
-
-class ResponseMessage(BaseModel):
- message: str
-
-
-class User(BaseModel):
- username: str
- email: str
-
-
-@app.post("/items/", response_model=ResponseMessage, tags=["items"])
-async def create_item(item: Item):
- return {"message": "Item received"}
-
-
-@app.get("/items/", response_model=List[Item], tags=["items"])
-async def get_items():
- return [
- {"name": "Plumbus", "price": 3},
- {"name": "Portal Gun", "price": 9001},
- ]
-
-
-@app.post("/users/", response_model=ResponseMessage, tags=["users"])
-async def create_user(user: User):
- return {"message": "User received"}
diff --git a/docs_src/generate_clients/tutorial003.py b/docs_src/generate_clients/tutorial003.py
deleted file mode 100644
index 49eab73a1..000000000
--- a/docs_src/generate_clients/tutorial003.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI
-from fastapi.routing import APIRoute
-from pydantic import BaseModel
-
-
-def custom_generate_unique_id(route: APIRoute):
- return f"{route.tags[0]}-{route.name}"
-
-
-app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
-
-
-class Item(BaseModel):
- name: str
- price: float
-
-
-class ResponseMessage(BaseModel):
- message: str
-
-
-class User(BaseModel):
- username: str
- email: str
-
-
-@app.post("/items/", response_model=ResponseMessage, tags=["items"])
-async def create_item(item: Item):
- return {"message": "Item received"}
-
-
-@app.get("/items/", response_model=List[Item], tags=["items"])
-async def get_items():
- return [
- {"name": "Plumbus", "price": 3},
- {"name": "Portal Gun", "price": 9001},
- ]
-
-
-@app.post("/users/", response_model=ResponseMessage, tags=["users"])
-async def create_user(user: User):
- return {"message": "User received"}
diff --git a/docs_src/generate_clients/tutorial004.py b/docs_src/generate_clients/tutorial004_py39.py
similarity index 100%
rename from docs_src/generate_clients/tutorial004.py
rename to docs_src/generate_clients/tutorial004_py39.py
diff --git a/docs_src/graphql_/__init__.py b/docs_src/graphql_/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/graphql/tutorial001.py b/docs_src/graphql_/tutorial001_py39.py
similarity index 100%
rename from docs_src/graphql/tutorial001.py
rename to docs_src/graphql_/tutorial001_py39.py
diff --git a/docs_src/handling_errors/__init__.py b/docs_src/handling_errors/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/handling_errors/tutorial001.py b/docs_src/handling_errors/tutorial001_py39.py
similarity index 100%
rename from docs_src/handling_errors/tutorial001.py
rename to docs_src/handling_errors/tutorial001_py39.py
diff --git a/docs_src/handling_errors/tutorial002.py b/docs_src/handling_errors/tutorial002_py39.py
similarity index 100%
rename from docs_src/handling_errors/tutorial002.py
rename to docs_src/handling_errors/tutorial002_py39.py
diff --git a/docs_src/handling_errors/tutorial003.py b/docs_src/handling_errors/tutorial003_py39.py
similarity index 100%
rename from docs_src/handling_errors/tutorial003.py
rename to docs_src/handling_errors/tutorial003_py39.py
diff --git a/docs_src/handling_errors/tutorial004.py b/docs_src/handling_errors/tutorial004.py
deleted file mode 100644
index 300a3834f..000000000
--- a/docs_src/handling_errors/tutorial004.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from fastapi import FastAPI, HTTPException
-from fastapi.exceptions import RequestValidationError
-from fastapi.responses import PlainTextResponse
-from starlette.exceptions import HTTPException as StarletteHTTPException
-
-app = FastAPI()
-
-
-@app.exception_handler(StarletteHTTPException)
-async def http_exception_handler(request, exc):
- return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
-
-
-@app.exception_handler(RequestValidationError)
-async def validation_exception_handler(request, exc):
- return PlainTextResponse(str(exc), status_code=400)
-
-
-@app.get("/items/{item_id}")
-async def read_item(item_id: int):
- if item_id == 3:
- raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
- return {"item_id": item_id}
diff --git a/docs_src/handling_errors/tutorial004_py39.py b/docs_src/handling_errors/tutorial004_py39.py
new file mode 100644
index 000000000..ae50807e9
--- /dev/null
+++ b/docs_src/handling_errors/tutorial004_py39.py
@@ -0,0 +1,26 @@
+from fastapi import FastAPI, HTTPException
+from fastapi.exceptions import RequestValidationError
+from fastapi.responses import PlainTextResponse
+from starlette.exceptions import HTTPException as StarletteHTTPException
+
+app = FastAPI()
+
+
+@app.exception_handler(StarletteHTTPException)
+async def http_exception_handler(request, exc):
+ return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
+
+
+@app.exception_handler(RequestValidationError)
+async def validation_exception_handler(request, exc: RequestValidationError):
+ message = "Validation errors:"
+ for error in exc.errors():
+ message += f"\nField: {error['loc']}, Error: {error['msg']}"
+ return PlainTextResponse(message, status_code=400)
+
+
+@app.get("/items/{item_id}")
+async def read_item(item_id: int):
+ if item_id == 3:
+ raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
+ return {"item_id": item_id}
diff --git a/docs_src/handling_errors/tutorial005.py b/docs_src/handling_errors/tutorial005_py39.py
similarity index 100%
rename from docs_src/handling_errors/tutorial005.py
rename to docs_src/handling_errors/tutorial005_py39.py
diff --git a/docs_src/handling_errors/tutorial006.py b/docs_src/handling_errors/tutorial006_py39.py
similarity index 100%
rename from docs_src/handling_errors/tutorial006.py
rename to docs_src/handling_errors/tutorial006_py39.py
diff --git a/docs_src/header_param_models/__init__.py b/docs_src/header_param_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/header_param_models/tutorial001.py b/docs_src/header_param_models/tutorial001.py
deleted file mode 100644
index 4caaba87b..000000000
--- a/docs_src/header_param_models/tutorial001.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- host: str
- save_data: bool
- if_modified_since: Union[str, None] = None
- traceparent: Union[str, None] = None
- x_tag: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(headers: CommonHeaders = Header()):
- return headers
diff --git a/docs_src/header_param_models/tutorial001_an.py b/docs_src/header_param_models/tutorial001_an.py
deleted file mode 100644
index b55c6b56b..000000000
--- a/docs_src/header_param_models/tutorial001_an.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- host: str
- save_data: bool
- if_modified_since: Union[str, None] = None
- traceparent: Union[str, None] = None
- x_tag: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(headers: Annotated[CommonHeaders, Header()]):
- return headers
diff --git a/docs_src/header_param_models/tutorial002.py b/docs_src/header_param_models/tutorial002.py
deleted file mode 100644
index 3f9aac58d..000000000
--- a/docs_src/header_param_models/tutorial002.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- model_config = {"extra": "forbid"}
-
- host: str
- save_data: bool
- if_modified_since: Union[str, None] = None
- traceparent: Union[str, None] = None
- x_tag: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(headers: CommonHeaders = Header()):
- return headers
diff --git a/docs_src/header_param_models/tutorial002_an.py b/docs_src/header_param_models/tutorial002_an.py
deleted file mode 100644
index 771135d77..000000000
--- a/docs_src/header_param_models/tutorial002_an.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- model_config = {"extra": "forbid"}
-
- host: str
- save_data: bool
- if_modified_since: Union[str, None] = None
- traceparent: Union[str, None] = None
- x_tag: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(headers: Annotated[CommonHeaders, Header()]):
- return headers
diff --git a/docs_src/header_param_models/tutorial002_pv1.py b/docs_src/header_param_models/tutorial002_pv1.py
deleted file mode 100644
index 7e56cd993..000000000
--- a/docs_src/header_param_models/tutorial002_pv1.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- class Config:
- extra = "forbid"
-
- host: str
- save_data: bool
- if_modified_since: Union[str, None] = None
- traceparent: Union[str, None] = None
- x_tag: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(headers: CommonHeaders = Header()):
- return headers
diff --git a/docs_src/header_param_models/tutorial002_pv1_an.py b/docs_src/header_param_models/tutorial002_pv1_an.py
deleted file mode 100644
index 236778231..000000000
--- a/docs_src/header_param_models/tutorial002_pv1_an.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- class Config:
- extra = "forbid"
-
- host: str
- save_data: bool
- if_modified_since: Union[str, None] = None
- traceparent: Union[str, None] = None
- x_tag: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(headers: Annotated[CommonHeaders, Header()]):
- return headers
diff --git a/docs_src/header_param_models/tutorial002_pv1_an_py310.py b/docs_src/header_param_models/tutorial002_pv1_an_py310.py
deleted file mode 100644
index e99e24ea5..000000000
--- a/docs_src/header_param_models/tutorial002_pv1_an_py310.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import Annotated
-
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- class Config:
- extra = "forbid"
-
- host: str
- save_data: bool
- if_modified_since: str | None = None
- traceparent: str | None = None
- x_tag: list[str] = []
-
-
-@app.get("/items/")
-async def read_items(headers: Annotated[CommonHeaders, Header()]):
- return headers
diff --git a/docs_src/header_param_models/tutorial002_pv1_an_py39.py b/docs_src/header_param_models/tutorial002_pv1_an_py39.py
deleted file mode 100644
index 18398b726..000000000
--- a/docs_src/header_param_models/tutorial002_pv1_an_py39.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import Annotated, Union
-
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- class Config:
- extra = "forbid"
-
- host: str
- save_data: bool
- if_modified_since: Union[str, None] = None
- traceparent: Union[str, None] = None
- x_tag: list[str] = []
-
-
-@app.get("/items/")
-async def read_items(headers: Annotated[CommonHeaders, Header()]):
- return headers
diff --git a/docs_src/header_param_models/tutorial002_pv1_py310.py b/docs_src/header_param_models/tutorial002_pv1_py310.py
deleted file mode 100644
index 3dbff9d7b..000000000
--- a/docs_src/header_param_models/tutorial002_pv1_py310.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- class Config:
- extra = "forbid"
-
- host: str
- save_data: bool
- if_modified_since: str | None = None
- traceparent: str | None = None
- x_tag: list[str] = []
-
-
-@app.get("/items/")
-async def read_items(headers: CommonHeaders = Header()):
- return headers
diff --git a/docs_src/header_param_models/tutorial002_pv1_py39.py b/docs_src/header_param_models/tutorial002_pv1_py39.py
deleted file mode 100644
index 86e19be0d..000000000
--- a/docs_src/header_param_models/tutorial002_pv1_py39.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- class Config:
- extra = "forbid"
-
- host: str
- save_data: bool
- if_modified_since: Union[str, None] = None
- traceparent: Union[str, None] = None
- x_tag: list[str] = []
-
-
-@app.get("/items/")
-async def read_items(headers: CommonHeaders = Header()):
- return headers
diff --git a/docs_src/header_param_models/tutorial003.py b/docs_src/header_param_models/tutorial003.py
deleted file mode 100644
index dc2eb74bd..000000000
--- a/docs_src/header_param_models/tutorial003.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- host: str
- save_data: bool
- if_modified_since: Union[str, None] = None
- traceparent: Union[str, None] = None
- x_tag: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(headers: CommonHeaders = Header(convert_underscores=False)):
- return headers
diff --git a/docs_src/header_param_models/tutorial003_an.py b/docs_src/header_param_models/tutorial003_an.py
deleted file mode 100644
index e3edb1189..000000000
--- a/docs_src/header_param_models/tutorial003_an.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Header
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class CommonHeaders(BaseModel):
- host: str
- save_data: bool
- if_modified_since: Union[str, None] = None
- traceparent: Union[str, None] = None
- x_tag: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(
- headers: Annotated[CommonHeaders, Header(convert_underscores=False)],
-):
- return headers
diff --git a/docs_src/header_params/__init__.py b/docs_src/header_params/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/header_params/tutorial001_an.py b/docs_src/header_params/tutorial001_an.py
deleted file mode 100644
index 816c00086..000000000
--- a/docs_src/header_params/tutorial001_an.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Header
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(user_agent: Annotated[Union[str, None], Header()] = None):
- return {"User-Agent": user_agent}
diff --git a/docs_src/header_params/tutorial001.py b/docs_src/header_params/tutorial001_py39.py
similarity index 100%
rename from docs_src/header_params/tutorial001.py
rename to docs_src/header_params/tutorial001_py39.py
diff --git a/docs_src/header_params/tutorial002_an.py b/docs_src/header_params/tutorial002_an.py
deleted file mode 100644
index 82fe49ba2..000000000
--- a/docs_src/header_params/tutorial002_an.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Header
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(
- strange_header: Annotated[
- Union[str, None], Header(convert_underscores=False)
- ] = None,
-):
- return {"strange_header": strange_header}
diff --git a/docs_src/header_params/tutorial002.py b/docs_src/header_params/tutorial002_py39.py
similarity index 100%
rename from docs_src/header_params/tutorial002.py
rename to docs_src/header_params/tutorial002_py39.py
diff --git a/docs_src/header_params/tutorial003.py b/docs_src/header_params/tutorial003.py
deleted file mode 100644
index a61314aed..000000000
--- a/docs_src/header_params/tutorial003.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Header
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(x_token: Union[List[str], None] = Header(default=None)):
- return {"X-Token values": x_token}
diff --git a/docs_src/header_params/tutorial003_an.py b/docs_src/header_params/tutorial003_an.py
deleted file mode 100644
index 5406fd1f8..000000000
--- a/docs_src/header_params/tutorial003_an.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Header
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(x_token: Annotated[Union[List[str], None], Header()] = None):
- return {"X-Token values": x_token}
diff --git a/docs_src/header_params/tutorial003_an_py39.py b/docs_src/header_params/tutorial003_an_py39.py
index c1dd49961..5aad89407 100644
--- a/docs_src/header_params/tutorial003_an_py39.py
+++ b/docs_src/header_params/tutorial003_an_py39.py
@@ -1,4 +1,4 @@
-from typing import Annotated, List, Union
+from typing import Annotated, Union
from fastapi import FastAPI, Header
@@ -6,5 +6,5 @@ app = FastAPI()
@app.get("/items/")
-async def read_items(x_token: Annotated[Union[List[str], None], Header()] = None):
+async def read_items(x_token: Annotated[Union[list[str], None], Header()] = None):
return {"X-Token values": x_token}
diff --git a/docs_src/metadata/__init__.py b/docs_src/metadata/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/metadata/tutorial001_1.py b/docs_src/metadata/tutorial001_1_py39.py
similarity index 100%
rename from docs_src/metadata/tutorial001_1.py
rename to docs_src/metadata/tutorial001_1_py39.py
diff --git a/docs_src/metadata/tutorial001.py b/docs_src/metadata/tutorial001_py39.py
similarity index 100%
rename from docs_src/metadata/tutorial001.py
rename to docs_src/metadata/tutorial001_py39.py
diff --git a/docs_src/metadata/tutorial002.py b/docs_src/metadata/tutorial002_py39.py
similarity index 100%
rename from docs_src/metadata/tutorial002.py
rename to docs_src/metadata/tutorial002_py39.py
diff --git a/docs_src/metadata/tutorial003.py b/docs_src/metadata/tutorial003_py39.py
similarity index 100%
rename from docs_src/metadata/tutorial003.py
rename to docs_src/metadata/tutorial003_py39.py
diff --git a/docs_src/metadata/tutorial004.py b/docs_src/metadata/tutorial004_py39.py
similarity index 100%
rename from docs_src/metadata/tutorial004.py
rename to docs_src/metadata/tutorial004_py39.py
diff --git a/docs_src/middleware/__init__.py b/docs_src/middleware/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/middleware/tutorial001.py b/docs_src/middleware/tutorial001_py39.py
similarity index 100%
rename from docs_src/middleware/tutorial001.py
rename to docs_src/middleware/tutorial001_py39.py
diff --git a/docs_src/openapi_callbacks/__init__.py b/docs_src/openapi_callbacks/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/openapi_callbacks/tutorial001_py310.py b/docs_src/openapi_callbacks/tutorial001_py310.py
new file mode 100644
index 000000000..3efe0ee25
--- /dev/null
+++ b/docs_src/openapi_callbacks/tutorial001_py310.py
@@ -0,0 +1,51 @@
+from fastapi import APIRouter, FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Invoice(BaseModel):
+ id: str
+ title: str | None = None
+ customer: str
+ total: float
+
+
+class InvoiceEvent(BaseModel):
+ description: str
+ paid: bool
+
+
+class InvoiceEventReceived(BaseModel):
+ ok: bool
+
+
+invoices_callback_router = APIRouter()
+
+
+@invoices_callback_router.post(
+ "{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived
+)
+def invoice_notification(body: InvoiceEvent):
+ pass
+
+
+@app.post("/invoices/", callbacks=invoices_callback_router.routes)
+def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None):
+ """
+ Create an invoice.
+
+ This will (let's imagine) let the API user (some external developer) create an
+ invoice.
+
+ And this path operation will:
+
+ * Send the invoice to the client.
+ * Collect the money from the client.
+ * Send a notification back to the API user (the external developer), as a callback.
+ * At this point is that the API will somehow send a POST request to the
+ external API with the notification of the invoice event
+ (e.g. "payment successful").
+ """
+ # Send the invoice, collect the money, send the notification (the callback)
+ return {"msg": "Invoice received"}
diff --git a/docs_src/openapi_callbacks/tutorial001.py b/docs_src/openapi_callbacks/tutorial001_py39.py
similarity index 100%
rename from docs_src/openapi_callbacks/tutorial001.py
rename to docs_src/openapi_callbacks/tutorial001_py39.py
diff --git a/docs_src/openapi_webhooks/__init__.py b/docs_src/openapi_webhooks/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/openapi_webhooks/tutorial001.py b/docs_src/openapi_webhooks/tutorial001_py39.py
similarity index 100%
rename from docs_src/openapi_webhooks/tutorial001.py
rename to docs_src/openapi_webhooks/tutorial001_py39.py
diff --git a/docs_src/path_operation_advanced_configuration/__init__.py b/docs_src/path_operation_advanced_configuration/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/path_operation_advanced_configuration/tutorial001.py b/docs_src/path_operation_advanced_configuration/tutorial001_py39.py
similarity index 100%
rename from docs_src/path_operation_advanced_configuration/tutorial001.py
rename to docs_src/path_operation_advanced_configuration/tutorial001_py39.py
diff --git a/docs_src/path_operation_advanced_configuration/tutorial002.py b/docs_src/path_operation_advanced_configuration/tutorial002_py39.py
similarity index 100%
rename from docs_src/path_operation_advanced_configuration/tutorial002.py
rename to docs_src/path_operation_advanced_configuration/tutorial002_py39.py
diff --git a/docs_src/path_operation_advanced_configuration/tutorial003.py b/docs_src/path_operation_advanced_configuration/tutorial003_py39.py
similarity index 100%
rename from docs_src/path_operation_advanced_configuration/tutorial003.py
rename to docs_src/path_operation_advanced_configuration/tutorial003_py39.py
diff --git a/docs_src/path_operation_advanced_configuration/tutorial004.py b/docs_src/path_operation_advanced_configuration/tutorial004.py
deleted file mode 100644
index a3aad4ac4..000000000
--- a/docs_src/path_operation_advanced_configuration/tutorial004.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from typing import Set, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: Set[str] = set()
-
-
-@app.post("/items/", response_model=Item, summary="Create an item")
-async def create_item(item: Item):
- """
- 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
- \f
- :param item: User input.
- """
- return item
diff --git a/docs_src/path_operation_advanced_configuration/tutorial004_py310.py b/docs_src/path_operation_advanced_configuration/tutorial004_py310.py
new file mode 100644
index 000000000..a815a564b
--- /dev/null
+++ b/docs_src/path_operation_advanced_configuration/tutorial004_py310.py
@@ -0,0 +1,28 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, summary="Create an item")
+async def create_item(item: Item):
+ """
+ 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
+ \f
+ :param item: User input.
+ """
+ return item
diff --git a/docs_src/path_operation_advanced_configuration/tutorial004_py39.py b/docs_src/path_operation_advanced_configuration/tutorial004_py39.py
new file mode 100644
index 000000000..d5fe6705c
--- /dev/null
+++ b/docs_src/path_operation_advanced_configuration/tutorial004_py39.py
@@ -0,0 +1,30 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, summary="Create an item")
+async def create_item(item: Item):
+ """
+ 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
+ \f
+ :param item: User input.
+ """
+ return item
diff --git a/docs_src/path_operation_advanced_configuration/tutorial005.py b/docs_src/path_operation_advanced_configuration/tutorial005_py39.py
similarity index 100%
rename from docs_src/path_operation_advanced_configuration/tutorial005.py
rename to docs_src/path_operation_advanced_configuration/tutorial005_py39.py
diff --git a/docs_src/path_operation_advanced_configuration/tutorial006.py b/docs_src/path_operation_advanced_configuration/tutorial006_py39.py
similarity index 100%
rename from docs_src/path_operation_advanced_configuration/tutorial006.py
rename to docs_src/path_operation_advanced_configuration/tutorial006_py39.py
diff --git a/docs_src/path_operation_advanced_configuration/tutorial007.py b/docs_src/path_operation_advanced_configuration/tutorial007.py
deleted file mode 100644
index 54e2e9399..000000000
--- a/docs_src/path_operation_advanced_configuration/tutorial007.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from typing import List
-
-import yaml
-from fastapi import FastAPI, HTTPException, Request
-from pydantic import BaseModel, ValidationError
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- tags: List[str]
-
-
-@app.post(
- "/items/",
- openapi_extra={
- "requestBody": {
- "content": {"application/x-yaml": {"schema": Item.model_json_schema()}},
- "required": True,
- },
- },
-)
-async def create_item(request: Request):
- raw_body = await request.body()
- try:
- data = yaml.safe_load(raw_body)
- except yaml.YAMLError:
- raise HTTPException(status_code=422, detail="Invalid YAML")
- try:
- item = Item.model_validate(data)
- except ValidationError as e:
- raise HTTPException(status_code=422, detail=e.errors(include_url=False))
- return item
diff --git a/docs_src/path_operation_advanced_configuration/tutorial007_pv1.py b/docs_src/path_operation_advanced_configuration/tutorial007_pv1.py
deleted file mode 100644
index d51752bb8..000000000
--- a/docs_src/path_operation_advanced_configuration/tutorial007_pv1.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from typing import List
-
-import yaml
-from fastapi import FastAPI, HTTPException, Request
-from pydantic import BaseModel, ValidationError
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- tags: List[str]
-
-
-@app.post(
- "/items/",
- openapi_extra={
- "requestBody": {
- "content": {"application/x-yaml": {"schema": Item.schema()}},
- "required": True,
- },
- },
-)
-async def create_item(request: Request):
- raw_body = await request.body()
- try:
- data = yaml.safe_load(raw_body)
- except yaml.YAMLError:
- raise HTTPException(status_code=422, detail="Invalid YAML")
- try:
- item = Item.parse_obj(data)
- except ValidationError as e:
- raise HTTPException(status_code=422, detail=e.errors())
- return item
diff --git a/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py b/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py
new file mode 100644
index 000000000..849f648e1
--- /dev/null
+++ b/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py
@@ -0,0 +1,32 @@
+import yaml
+from fastapi import FastAPI, HTTPException, Request
+from pydantic.v1 import BaseModel, ValidationError
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ tags: list[str]
+
+
+@app.post(
+ "/items/",
+ openapi_extra={
+ "requestBody": {
+ "content": {"application/x-yaml": {"schema": Item.schema()}},
+ "required": True,
+ },
+ },
+)
+async def create_item(request: Request):
+ raw_body = await request.body()
+ try:
+ data = yaml.safe_load(raw_body)
+ except yaml.YAMLError:
+ raise HTTPException(status_code=422, detail="Invalid YAML")
+ try:
+ item = Item.parse_obj(data)
+ except ValidationError as e:
+ raise HTTPException(status_code=422, detail=e.errors())
+ return item
diff --git a/docs_src/path_operation_advanced_configuration/tutorial007_py39.py b/docs_src/path_operation_advanced_configuration/tutorial007_py39.py
new file mode 100644
index 000000000..ff64ef792
--- /dev/null
+++ b/docs_src/path_operation_advanced_configuration/tutorial007_py39.py
@@ -0,0 +1,32 @@
+import yaml
+from fastapi import FastAPI, HTTPException, Request
+from pydantic import BaseModel, ValidationError
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ tags: list[str]
+
+
+@app.post(
+ "/items/",
+ openapi_extra={
+ "requestBody": {
+ "content": {"application/x-yaml": {"schema": Item.model_json_schema()}},
+ "required": True,
+ },
+ },
+)
+async def create_item(request: Request):
+ raw_body = await request.body()
+ try:
+ data = yaml.safe_load(raw_body)
+ except yaml.YAMLError:
+ raise HTTPException(status_code=422, detail="Invalid YAML")
+ try:
+ item = Item.model_validate(data)
+ except ValidationError as e:
+ raise HTTPException(status_code=422, detail=e.errors(include_url=False))
+ return item
diff --git a/docs_src/path_operation_configuration/__init__.py b/docs_src/path_operation_configuration/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/path_operation_configuration/tutorial001.py b/docs_src/path_operation_configuration/tutorial001.py
deleted file mode 100644
index 83fd8377a..000000000
--- a/docs_src/path_operation_configuration/tutorial001.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Set, Union
-
-from fastapi import FastAPI, status
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: Set[str] = set()
-
-
-@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED)
-async def create_item(item: Item):
- return item
diff --git a/docs_src/path_operation_configuration/tutorial002.py b/docs_src/path_operation_configuration/tutorial002.py
deleted file mode 100644
index 798b0c231..000000000
--- a/docs_src/path_operation_configuration/tutorial002.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from typing import Set, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: Set[str] = set()
-
-
-@app.post("/items/", response_model=Item, tags=["items"])
-async def create_item(item: Item):
- return item
-
-
-@app.get("/items/", tags=["items"])
-async def read_items():
- return [{"name": "Foo", "price": 42}]
-
-
-@app.get("/users/", tags=["users"])
-async def read_users():
- return [{"username": "johndoe"}]
diff --git a/docs_src/path_operation_configuration/tutorial002b.py b/docs_src/path_operation_configuration/tutorial002b_py39.py
similarity index 100%
rename from docs_src/path_operation_configuration/tutorial002b.py
rename to docs_src/path_operation_configuration/tutorial002b_py39.py
diff --git a/docs_src/path_operation_configuration/tutorial003.py b/docs_src/path_operation_configuration/tutorial003.py
deleted file mode 100644
index 26bf7daba..000000000
--- a/docs_src/path_operation_configuration/tutorial003.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from typing import Set, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: Set[str] = set()
-
-
-@app.post(
- "/items/",
- response_model=Item,
- summary="Create an item",
- description="Create an item with all the information, name, description, price, tax and a set of unique tags",
-)
-async def create_item(item: Item):
- return item
diff --git a/docs_src/path_operation_configuration/tutorial004.py b/docs_src/path_operation_configuration/tutorial004.py
deleted file mode 100644
index 8f865c58a..000000000
--- a/docs_src/path_operation_configuration/tutorial004.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from typing import Set, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: Set[str] = set()
-
-
-@app.post("/items/", response_model=Item, summary="Create an item")
-async def create_item(item: Item):
- """
- 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
- """
- return item
diff --git a/docs_src/path_operation_configuration/tutorial005.py b/docs_src/path_operation_configuration/tutorial005.py
deleted file mode 100644
index 2c1be4a34..000000000
--- a/docs_src/path_operation_configuration/tutorial005.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from typing import Set, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: Set[str] = set()
-
-
-@app.post(
- "/items/",
- response_model=Item,
- summary="Create an item",
- response_description="The created item",
-)
-async def create_item(item: Item):
- """
- 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
- """
- return item
diff --git a/docs_src/path_operation_configuration/tutorial006.py b/docs_src/path_operation_configuration/tutorial006_py39.py
similarity index 100%
rename from docs_src/path_operation_configuration/tutorial006.py
rename to docs_src/path_operation_configuration/tutorial006_py39.py
diff --git a/docs_src/path_params/__init__.py b/docs_src/path_params/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/path_params/tutorial001.py b/docs_src/path_params/tutorial001_py39.py
similarity index 100%
rename from docs_src/path_params/tutorial001.py
rename to docs_src/path_params/tutorial001_py39.py
diff --git a/docs_src/path_params/tutorial002.py b/docs_src/path_params/tutorial002_py39.py
similarity index 100%
rename from docs_src/path_params/tutorial002.py
rename to docs_src/path_params/tutorial002_py39.py
diff --git a/docs_src/path_params/tutorial003.py b/docs_src/path_params/tutorial003_py39.py
similarity index 100%
rename from docs_src/path_params/tutorial003.py
rename to docs_src/path_params/tutorial003_py39.py
diff --git a/docs_src/path_params/tutorial003b.py b/docs_src/path_params/tutorial003b_py39.py
similarity index 100%
rename from docs_src/path_params/tutorial003b.py
rename to docs_src/path_params/tutorial003b_py39.py
diff --git a/docs_src/path_params/tutorial004.py b/docs_src/path_params/tutorial004_py39.py
similarity index 100%
rename from docs_src/path_params/tutorial004.py
rename to docs_src/path_params/tutorial004_py39.py
diff --git a/docs_src/path_params/tutorial005.py b/docs_src/path_params/tutorial005_py39.py
similarity index 100%
rename from docs_src/path_params/tutorial005.py
rename to docs_src/path_params/tutorial005_py39.py
diff --git a/docs_src/path_params_numeric_validations/__init__.py b/docs_src/path_params_numeric_validations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/path_params_numeric_validations/tutorial001_an.py b/docs_src/path_params_numeric_validations/tutorial001_an.py
deleted file mode 100644
index 621be7b04..000000000
--- a/docs_src/path_params_numeric_validations/tutorial001_an.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Path, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/{item_id}")
-async def read_items(
- item_id: Annotated[int, Path(title="The ID of the item to get")],
- q: Annotated[Union[str, None], Query(alias="item-query")] = None,
-):
- results = {"item_id": item_id}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/path_params_numeric_validations/tutorial001.py b/docs_src/path_params_numeric_validations/tutorial001_py39.py
similarity index 100%
rename from docs_src/path_params_numeric_validations/tutorial001.py
rename to docs_src/path_params_numeric_validations/tutorial001_py39.py
diff --git a/docs_src/path_params_numeric_validations/tutorial002_an.py b/docs_src/path_params_numeric_validations/tutorial002_an.py
deleted file mode 100644
index 322f8cf0b..000000000
--- a/docs_src/path_params_numeric_validations/tutorial002_an.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from fastapi import FastAPI, Path
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/{item_id}")
-async def read_items(
- q: str, item_id: Annotated[int, Path(title="The ID of the item to get")]
-):
- results = {"item_id": item_id}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/path_params_numeric_validations/tutorial002.py b/docs_src/path_params_numeric_validations/tutorial002_py39.py
similarity index 100%
rename from docs_src/path_params_numeric_validations/tutorial002.py
rename to docs_src/path_params_numeric_validations/tutorial002_py39.py
diff --git a/docs_src/path_params_numeric_validations/tutorial003_an.py b/docs_src/path_params_numeric_validations/tutorial003_an.py
deleted file mode 100644
index d0fa8b3db..000000000
--- a/docs_src/path_params_numeric_validations/tutorial003_an.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from fastapi import FastAPI, Path
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/{item_id}")
-async def read_items(
- item_id: Annotated[int, Path(title="The ID of the item to get")], q: str
-):
- results = {"item_id": item_id}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/path_params_numeric_validations/tutorial003.py b/docs_src/path_params_numeric_validations/tutorial003_py39.py
similarity index 100%
rename from docs_src/path_params_numeric_validations/tutorial003.py
rename to docs_src/path_params_numeric_validations/tutorial003_py39.py
diff --git a/docs_src/path_params_numeric_validations/tutorial004_an.py b/docs_src/path_params_numeric_validations/tutorial004_an.py
deleted file mode 100644
index ffc50f6c5..000000000
--- a/docs_src/path_params_numeric_validations/tutorial004_an.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from fastapi import FastAPI, Path
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/{item_id}")
-async def read_items(
- item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)], q: str
-):
- results = {"item_id": item_id}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/path_params_numeric_validations/tutorial004.py b/docs_src/path_params_numeric_validations/tutorial004_py39.py
similarity index 100%
rename from docs_src/path_params_numeric_validations/tutorial004.py
rename to docs_src/path_params_numeric_validations/tutorial004_py39.py
diff --git a/docs_src/path_params_numeric_validations/tutorial005_an.py b/docs_src/path_params_numeric_validations/tutorial005_an.py
deleted file mode 100644
index 433c69129..000000000
--- a/docs_src/path_params_numeric_validations/tutorial005_an.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from fastapi import FastAPI, Path
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/{item_id}")
-async def read_items(
- item_id: Annotated[int, Path(title="The ID of the item to get", gt=0, le=1000)],
- q: str,
-):
- results = {"item_id": item_id}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/path_params_numeric_validations/tutorial005.py b/docs_src/path_params_numeric_validations/tutorial005_py39.py
similarity index 100%
rename from docs_src/path_params_numeric_validations/tutorial005.py
rename to docs_src/path_params_numeric_validations/tutorial005_py39.py
diff --git a/docs_src/path_params_numeric_validations/tutorial006_an.py b/docs_src/path_params_numeric_validations/tutorial006_an.py
deleted file mode 100644
index ac4732573..000000000
--- a/docs_src/path_params_numeric_validations/tutorial006_an.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from fastapi import FastAPI, Path, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/{item_id}")
-async def read_items(
- *,
- item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
- q: str,
- size: Annotated[float, Query(gt=0, lt=10.5)],
-):
- results = {"item_id": item_id}
- if q:
- results.update({"q": q})
- if size:
- results.update({"size": size})
- return results
diff --git a/docs_src/path_params_numeric_validations/tutorial006.py b/docs_src/path_params_numeric_validations/tutorial006_py39.py
similarity index 100%
rename from docs_src/path_params_numeric_validations/tutorial006.py
rename to docs_src/path_params_numeric_validations/tutorial006_py39.py
diff --git a/docs_src/pydantic_v1_in_v2/__init__.py b/docs_src/pydantic_v1_in_v2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py b/docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py
new file mode 100644
index 000000000..a8ec729b3
--- /dev/null
+++ b/docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py
@@ -0,0 +1,7 @@
+from pydantic.v1 import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ size: float
diff --git a/docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py b/docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py
new file mode 100644
index 000000000..62a4b2c21
--- /dev/null
+++ b/docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py
@@ -0,0 +1,9 @@
+from typing import Union
+
+from pydantic.v1 import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ size: float
diff --git a/docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py b/docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py
new file mode 100644
index 000000000..4934e7004
--- /dev/null
+++ b/docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py
@@ -0,0 +1,16 @@
+from fastapi import FastAPI
+from pydantic.v1 import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ size: float
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+async def create_item(item: Item) -> Item:
+ return item
diff --git a/docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py b/docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py
new file mode 100644
index 000000000..3c6a06080
--- /dev/null
+++ b/docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py
@@ -0,0 +1,18 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic.v1 import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ size: float
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+async def create_item(item: Item) -> Item:
+ return item
diff --git a/docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py b/docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py
new file mode 100644
index 000000000..6e3013644
--- /dev/null
+++ b/docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py
@@ -0,0 +1,23 @@
+from fastapi import FastAPI
+from pydantic import BaseModel as BaseModelV2
+from pydantic.v1 import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ size: float
+
+
+class ItemV2(BaseModelV2):
+ name: str
+ description: str | None = None
+ size: float
+
+
+app = FastAPI()
+
+
+@app.post("/items/", response_model=ItemV2)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py b/docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py
new file mode 100644
index 000000000..117d6f7a4
--- /dev/null
+++ b/docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py
@@ -0,0 +1,25 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel as BaseModelV2
+from pydantic.v1 import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ size: float
+
+
+class ItemV2(BaseModelV2):
+ name: str
+ description: Union[str, None] = None
+ size: float
+
+
+app = FastAPI()
+
+
+@app.post("/items/", response_model=ItemV2)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py b/docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py
new file mode 100644
index 000000000..c251311e0
--- /dev/null
+++ b/docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py
@@ -0,0 +1,19 @@
+from typing import Annotated
+
+from fastapi import FastAPI
+from fastapi.temp_pydantic_v1_params import Body
+from pydantic.v1 import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ size: float
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+async def create_item(item: Annotated[Item, Body(embed=True)]) -> Item:
+ return item
diff --git a/docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py b/docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py
new file mode 100644
index 000000000..150ab20ae
--- /dev/null
+++ b/docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py
@@ -0,0 +1,19 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI
+from fastapi.temp_pydantic_v1_params import Body
+from pydantic.v1 import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ size: float
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+async def create_item(item: Annotated[Item, Body(embed=True)]) -> Item:
+ return item
diff --git a/docs_src/python_types/__init__.py b/docs_src/python_types/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/python_types/tutorial001.py b/docs_src/python_types/tutorial001_py39.py
similarity index 100%
rename from docs_src/python_types/tutorial001.py
rename to docs_src/python_types/tutorial001_py39.py
diff --git a/docs_src/python_types/tutorial002.py b/docs_src/python_types/tutorial002_py39.py
similarity index 100%
rename from docs_src/python_types/tutorial002.py
rename to docs_src/python_types/tutorial002_py39.py
diff --git a/docs_src/python_types/tutorial003.py b/docs_src/python_types/tutorial003_py39.py
similarity index 100%
rename from docs_src/python_types/tutorial003.py
rename to docs_src/python_types/tutorial003_py39.py
diff --git a/docs_src/python_types/tutorial004.py b/docs_src/python_types/tutorial004_py39.py
similarity index 100%
rename from docs_src/python_types/tutorial004.py
rename to docs_src/python_types/tutorial004_py39.py
diff --git a/docs_src/python_types/tutorial005.py b/docs_src/python_types/tutorial005.py
deleted file mode 100644
index 08ab44a94..000000000
--- a/docs_src/python_types/tutorial005.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
- return item_a, item_b, item_c, item_d, item_d, item_e
diff --git a/docs_src/python_types/tutorial005_py39.py b/docs_src/python_types/tutorial005_py39.py
new file mode 100644
index 000000000..6c8edb0ec
--- /dev/null
+++ b/docs_src/python_types/tutorial005_py39.py
@@ -0,0 +1,2 @@
+def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
+ return item_a, item_b, item_c, item_d, item_e
diff --git a/docs_src/python_types/tutorial006.py b/docs_src/python_types/tutorial006.py
deleted file mode 100644
index 87394ecb0..000000000
--- a/docs_src/python_types/tutorial006.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from typing import List
-
-
-def process_items(items: List[str]):
- for item in items:
- print(item)
diff --git a/docs_src/python_types/tutorial007.py b/docs_src/python_types/tutorial007.py
deleted file mode 100644
index 5b13f1549..000000000
--- a/docs_src/python_types/tutorial007.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from typing import Set, Tuple
-
-
-def process_items(items_t: Tuple[int, int, str], items_s: Set[bytes]):
- return items_t, items_s
diff --git a/docs_src/python_types/tutorial008.py b/docs_src/python_types/tutorial008.py
deleted file mode 100644
index 9fb1043bb..000000000
--- a/docs_src/python_types/tutorial008.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from typing import Dict
-
-
-def process_items(prices: Dict[str, float]):
- for item_name, item_price in prices.items():
- print(item_name)
- print(item_price)
diff --git a/docs_src/python_types/tutorial008b.py b/docs_src/python_types/tutorial008b_py39.py
similarity index 100%
rename from docs_src/python_types/tutorial008b.py
rename to docs_src/python_types/tutorial008b_py39.py
diff --git a/docs_src/python_types/tutorial009.py b/docs_src/python_types/tutorial009_py39.py
similarity index 100%
rename from docs_src/python_types/tutorial009.py
rename to docs_src/python_types/tutorial009_py39.py
diff --git a/docs_src/python_types/tutorial009b.py b/docs_src/python_types/tutorial009b_py39.py
similarity index 100%
rename from docs_src/python_types/tutorial009b.py
rename to docs_src/python_types/tutorial009b_py39.py
diff --git a/docs_src/python_types/tutorial009c.py b/docs_src/python_types/tutorial009c_py39.py
similarity index 100%
rename from docs_src/python_types/tutorial009c.py
rename to docs_src/python_types/tutorial009c_py39.py
diff --git a/docs_src/python_types/tutorial010.py b/docs_src/python_types/tutorial010_py39.py
similarity index 100%
rename from docs_src/python_types/tutorial010.py
rename to docs_src/python_types/tutorial010_py39.py
diff --git a/docs_src/python_types/tutorial011.py b/docs_src/python_types/tutorial011.py
deleted file mode 100644
index 297a84db6..000000000
--- a/docs_src/python_types/tutorial011.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from datetime import datetime
-from typing import List, Union
-
-from pydantic import BaseModel
-
-
-class User(BaseModel):
- id: int
- name: str = "John Doe"
- signup_ts: Union[datetime, None] = None
- friends: List[int] = []
-
-
-external_data = {
- "id": "123",
- "signup_ts": "2017-06-01 12:22",
- "friends": [1, "2", b"3"],
-}
-user = User(**external_data)
-print(user)
-# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
-print(user.id)
-# > 123
diff --git a/docs_src/python_types/tutorial012.py b/docs_src/python_types/tutorial012_py39.py
similarity index 100%
rename from docs_src/python_types/tutorial012.py
rename to docs_src/python_types/tutorial012_py39.py
diff --git a/docs_src/python_types/tutorial013.py b/docs_src/python_types/tutorial013.py
deleted file mode 100644
index 0ec773519..000000000
--- a/docs_src/python_types/tutorial013.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from typing_extensions import Annotated
-
-
-def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
- return f"Hello {name}"
diff --git a/docs_src/query_param_models/__init__.py b/docs_src/query_param_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/query_param_models/tutorial001.py b/docs_src/query_param_models/tutorial001.py
deleted file mode 100644
index 0c0ab315e..000000000
--- a/docs_src/query_param_models/tutorial001.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, Query
-from pydantic import BaseModel, Field
-from typing_extensions import Literal
-
-app = FastAPI()
-
-
-class FilterParams(BaseModel):
- limit: int = Field(100, gt=0, le=100)
- offset: int = Field(0, ge=0)
- order_by: Literal["created_at", "updated_at"] = "created_at"
- tags: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(filter_query: FilterParams = Query()):
- return filter_query
diff --git a/docs_src/query_param_models/tutorial001_an.py b/docs_src/query_param_models/tutorial001_an.py
deleted file mode 100644
index 28375057c..000000000
--- a/docs_src/query_param_models/tutorial001_an.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, Query
-from pydantic import BaseModel, Field
-from typing_extensions import Annotated, Literal
-
-app = FastAPI()
-
-
-class FilterParams(BaseModel):
- limit: int = Field(100, gt=0, le=100)
- offset: int = Field(0, ge=0)
- order_by: Literal["created_at", "updated_at"] = "created_at"
- tags: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(filter_query: Annotated[FilterParams, Query()]):
- return filter_query
diff --git a/docs_src/query_param_models/tutorial001_an_py39.py b/docs_src/query_param_models/tutorial001_an_py39.py
index ba690d3e3..71427acae 100644
--- a/docs_src/query_param_models/tutorial001_an_py39.py
+++ b/docs_src/query_param_models/tutorial001_an_py39.py
@@ -1,6 +1,7 @@
+from typing import Annotated, Literal
+
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field
-from typing_extensions import Annotated, Literal
app = FastAPI()
diff --git a/docs_src/query_param_models/tutorial001_py39.py b/docs_src/query_param_models/tutorial001_py39.py
index 54b52a054..3ebf9f4d7 100644
--- a/docs_src/query_param_models/tutorial001_py39.py
+++ b/docs_src/query_param_models/tutorial001_py39.py
@@ -1,6 +1,7 @@
+from typing import Literal
+
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field
-from typing_extensions import Literal
app = FastAPI()
diff --git a/docs_src/query_param_models/tutorial002.py b/docs_src/query_param_models/tutorial002.py
deleted file mode 100644
index 1633bc464..000000000
--- a/docs_src/query_param_models/tutorial002.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, Query
-from pydantic import BaseModel, Field
-from typing_extensions import Literal
-
-app = FastAPI()
-
-
-class FilterParams(BaseModel):
- model_config = {"extra": "forbid"}
-
- limit: int = Field(100, gt=0, le=100)
- offset: int = Field(0, ge=0)
- order_by: Literal["created_at", "updated_at"] = "created_at"
- tags: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(filter_query: FilterParams = Query()):
- return filter_query
diff --git a/docs_src/query_param_models/tutorial002_an.py b/docs_src/query_param_models/tutorial002_an.py
deleted file mode 100644
index 69705d4b4..000000000
--- a/docs_src/query_param_models/tutorial002_an.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, Query
-from pydantic import BaseModel, Field
-from typing_extensions import Annotated, Literal
-
-app = FastAPI()
-
-
-class FilterParams(BaseModel):
- model_config = {"extra": "forbid"}
-
- limit: int = Field(100, gt=0, le=100)
- offset: int = Field(0, ge=0)
- order_by: Literal["created_at", "updated_at"] = "created_at"
- tags: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(filter_query: Annotated[FilterParams, Query()]):
- return filter_query
diff --git a/docs_src/query_param_models/tutorial002_an_py39.py b/docs_src/query_param_models/tutorial002_an_py39.py
index 2d4c1a62b..975956502 100644
--- a/docs_src/query_param_models/tutorial002_an_py39.py
+++ b/docs_src/query_param_models/tutorial002_an_py39.py
@@ -1,6 +1,7 @@
+from typing import Annotated, Literal
+
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field
-from typing_extensions import Annotated, Literal
app = FastAPI()
diff --git a/docs_src/query_param_models/tutorial002_pv1.py b/docs_src/query_param_models/tutorial002_pv1.py
deleted file mode 100644
index 71ccd961d..000000000
--- a/docs_src/query_param_models/tutorial002_pv1.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, Query
-from pydantic import BaseModel, Field
-from typing_extensions import Literal
-
-app = FastAPI()
-
-
-class FilterParams(BaseModel):
- class Config:
- extra = "forbid"
-
- limit: int = Field(100, gt=0, le=100)
- offset: int = Field(0, ge=0)
- order_by: Literal["created_at", "updated_at"] = "created_at"
- tags: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(filter_query: FilterParams = Query()):
- return filter_query
diff --git a/docs_src/query_param_models/tutorial002_pv1_an.py b/docs_src/query_param_models/tutorial002_pv1_an.py
deleted file mode 100644
index 1dd29157a..000000000
--- a/docs_src/query_param_models/tutorial002_pv1_an.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, Query
-from pydantic import BaseModel, Field
-from typing_extensions import Annotated, Literal
-
-app = FastAPI()
-
-
-class FilterParams(BaseModel):
- class Config:
- extra = "forbid"
-
- limit: int = Field(100, gt=0, le=100)
- offset: int = Field(0, ge=0)
- order_by: Literal["created_at", "updated_at"] = "created_at"
- tags: List[str] = []
-
-
-@app.get("/items/")
-async def read_items(filter_query: Annotated[FilterParams, Query()]):
- return filter_query
diff --git a/docs_src/query_param_models/tutorial002_pv1_an_py310.py b/docs_src/query_param_models/tutorial002_pv1_an_py310.py
deleted file mode 100644
index d635aae88..000000000
--- a/docs_src/query_param_models/tutorial002_pv1_an_py310.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from typing import Annotated, Literal
-
-from fastapi import FastAPI, Query
-from pydantic import BaseModel, Field
-
-app = FastAPI()
-
-
-class FilterParams(BaseModel):
- class Config:
- extra = "forbid"
-
- limit: int = Field(100, gt=0, le=100)
- offset: int = Field(0, ge=0)
- order_by: Literal["created_at", "updated_at"] = "created_at"
- tags: list[str] = []
-
-
-@app.get("/items/")
-async def read_items(filter_query: Annotated[FilterParams, Query()]):
- return filter_query
diff --git a/docs_src/query_param_models/tutorial002_pv1_an_py39.py b/docs_src/query_param_models/tutorial002_pv1_an_py39.py
deleted file mode 100644
index 494fef11f..000000000
--- a/docs_src/query_param_models/tutorial002_pv1_an_py39.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from fastapi import FastAPI, Query
-from pydantic import BaseModel, Field
-from typing_extensions import Annotated, Literal
-
-app = FastAPI()
-
-
-class FilterParams(BaseModel):
- class Config:
- extra = "forbid"
-
- limit: int = Field(100, gt=0, le=100)
- offset: int = Field(0, ge=0)
- order_by: Literal["created_at", "updated_at"] = "created_at"
- tags: list[str] = []
-
-
-@app.get("/items/")
-async def read_items(filter_query: Annotated[FilterParams, Query()]):
- return filter_query
diff --git a/docs_src/query_param_models/tutorial002_pv1_py310.py b/docs_src/query_param_models/tutorial002_pv1_py310.py
deleted file mode 100644
index 9ffdeefc0..000000000
--- a/docs_src/query_param_models/tutorial002_pv1_py310.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from typing import Literal
-
-from fastapi import FastAPI, Query
-from pydantic import BaseModel, Field
-
-app = FastAPI()
-
-
-class FilterParams(BaseModel):
- class Config:
- extra = "forbid"
-
- limit: int = Field(100, gt=0, le=100)
- offset: int = Field(0, ge=0)
- order_by: Literal["created_at", "updated_at"] = "created_at"
- tags: list[str] = []
-
-
-@app.get("/items/")
-async def read_items(filter_query: FilterParams = Query()):
- return filter_query
diff --git a/docs_src/query_param_models/tutorial002_pv1_py39.py b/docs_src/query_param_models/tutorial002_pv1_py39.py
deleted file mode 100644
index 7fa456a79..000000000
--- a/docs_src/query_param_models/tutorial002_pv1_py39.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from fastapi import FastAPI, Query
-from pydantic import BaseModel, Field
-from typing_extensions import Literal
-
-app = FastAPI()
-
-
-class FilterParams(BaseModel):
- class Config:
- extra = "forbid"
-
- limit: int = Field(100, gt=0, le=100)
- offset: int = Field(0, ge=0)
- order_by: Literal["created_at", "updated_at"] = "created_at"
- tags: list[str] = []
-
-
-@app.get("/items/")
-async def read_items(filter_query: FilterParams = Query()):
- return filter_query
diff --git a/docs_src/query_param_models/tutorial002_py39.py b/docs_src/query_param_models/tutorial002_py39.py
index f9bba028c..6ec418499 100644
--- a/docs_src/query_param_models/tutorial002_py39.py
+++ b/docs_src/query_param_models/tutorial002_py39.py
@@ -1,6 +1,7 @@
+from typing import Literal
+
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field
-from typing_extensions import Literal
app = FastAPI()
diff --git a/docs_src/query_params/__init__.py b/docs_src/query_params/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/query_params/tutorial001.py b/docs_src/query_params/tutorial001_py39.py
similarity index 100%
rename from docs_src/query_params/tutorial001.py
rename to docs_src/query_params/tutorial001_py39.py
diff --git a/docs_src/query_params/tutorial002.py b/docs_src/query_params/tutorial002_py39.py
similarity index 100%
rename from docs_src/query_params/tutorial002.py
rename to docs_src/query_params/tutorial002_py39.py
diff --git a/docs_src/query_params/tutorial003.py b/docs_src/query_params/tutorial003_py39.py
similarity index 100%
rename from docs_src/query_params/tutorial003.py
rename to docs_src/query_params/tutorial003_py39.py
diff --git a/docs_src/query_params/tutorial004.py b/docs_src/query_params/tutorial004_py39.py
similarity index 100%
rename from docs_src/query_params/tutorial004.py
rename to docs_src/query_params/tutorial004_py39.py
diff --git a/docs_src/query_params/tutorial005.py b/docs_src/query_params/tutorial005_py39.py
similarity index 100%
rename from docs_src/query_params/tutorial005.py
rename to docs_src/query_params/tutorial005_py39.py
diff --git a/docs_src/query_params/tutorial006.py b/docs_src/query_params/tutorial006_py39.py
similarity index 100%
rename from docs_src/query_params/tutorial006.py
rename to docs_src/query_params/tutorial006_py39.py
diff --git a/docs_src/query_params/tutorial006b.py b/docs_src/query_params/tutorial006b.py
deleted file mode 100644
index f0dbfe08f..000000000
--- a/docs_src/query_params/tutorial006b.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/items/{item_id}")
-async def read_user_item(
- item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None
-):
- item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
- return item
diff --git a/docs_src/query_params_str_validations/__init__.py b/docs_src/query_params_str_validations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/query_params_str_validations/tutorial001.py b/docs_src/query_params_str_validations/tutorial001_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial001.py
rename to docs_src/query_params_str_validations/tutorial001_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial002_an.py b/docs_src/query_params_str_validations/tutorial002_an.py
deleted file mode 100644
index cb1b38940..000000000
--- a/docs_src/query_params_str_validations/tutorial002_an.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[Union[str, None], Query(max_length=50)] = None):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial002_an_py39.py b/docs_src/query_params_str_validations/tutorial002_an_py39.py
new file mode 100644
index 000000000..2d8fc9798
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial002_an_py39.py
@@ -0,0 +1,13 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[Union[str, None], Query(max_length=50)] = None):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial002.py b/docs_src/query_params_str_validations/tutorial002_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial002.py
rename to docs_src/query_params_str_validations/tutorial002_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial003_an.py b/docs_src/query_params_str_validations/tutorial003_an.py
deleted file mode 100644
index 0dd14086c..000000000
--- a/docs_src/query_params_str_validations/tutorial003_an.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(
- q: Annotated[Union[str, None], Query(min_length=3, max_length=50)] = None,
-):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial003.py b/docs_src/query_params_str_validations/tutorial003_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial003.py
rename to docs_src/query_params_str_validations/tutorial003_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial004_an.py b/docs_src/query_params_str_validations/tutorial004_an.py
deleted file mode 100644
index c75d45d63..000000000
--- a/docs_src/query_params_str_validations/tutorial004_an.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(
- q: Annotated[
- Union[str, None], Query(min_length=3, max_length=50, pattern="^fixedquery$")
- ] = None,
-):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial004.py b/docs_src/query_params_str_validations/tutorial004_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial004.py
rename to docs_src/query_params_str_validations/tutorial004_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial005_an.py b/docs_src/query_params_str_validations/tutorial005_an.py
deleted file mode 100644
index 452d4d38d..000000000
--- a/docs_src/query_params_str_validations/tutorial005_an.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial005.py b/docs_src/query_params_str_validations/tutorial005_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial005.py
rename to docs_src/query_params_str_validations/tutorial005_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial006_an.py b/docs_src/query_params_str_validations/tutorial006_an.py
deleted file mode 100644
index 559480d2b..000000000
--- a/docs_src/query_params_str_validations/tutorial006_an.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[str, Query(min_length=3)]):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial006.py b/docs_src/query_params_str_validations/tutorial006_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial006.py
rename to docs_src/query_params_str_validations/tutorial006_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial006c_an.py b/docs_src/query_params_str_validations/tutorial006c_an.py
deleted file mode 100644
index 55c4f4adc..000000000
--- a/docs_src/query_params_str_validations/tutorial006c_an.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial006c.py b/docs_src/query_params_str_validations/tutorial006c_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial006c.py
rename to docs_src/query_params_str_validations/tutorial006c_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial007_an.py b/docs_src/query_params_str_validations/tutorial007_an.py
deleted file mode 100644
index 4b3c8de4b..000000000
--- a/docs_src/query_params_str_validations/tutorial007_an.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(
- q: Annotated[Union[str, None], Query(title="Query string", min_length=3)] = None,
-):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial007.py b/docs_src/query_params_str_validations/tutorial007_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial007.py
rename to docs_src/query_params_str_validations/tutorial007_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial008_an.py b/docs_src/query_params_str_validations/tutorial008_an.py
deleted file mode 100644
index 01606a920..000000000
--- a/docs_src/query_params_str_validations/tutorial008_an.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(
- q: Annotated[
- Union[str, None],
- Query(
- title="Query string",
- description="Query string for the items to search in the database that have a good match",
- min_length=3,
- ),
- ] = None,
-):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial008.py b/docs_src/query_params_str_validations/tutorial008_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial008.py
rename to docs_src/query_params_str_validations/tutorial008_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial009_an.py b/docs_src/query_params_str_validations/tutorial009_an.py
deleted file mode 100644
index 2894e2d51..000000000
--- a/docs_src/query_params_str_validations/tutorial009_an.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[Union[str, None], Query(alias="item-query")] = None):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial009.py b/docs_src/query_params_str_validations/tutorial009_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial009.py
rename to docs_src/query_params_str_validations/tutorial009_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial010_an.py b/docs_src/query_params_str_validations/tutorial010_an.py
deleted file mode 100644
index ed343230f..000000000
--- a/docs_src/query_params_str_validations/tutorial010_an.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(
- q: Annotated[
- Union[str, None],
- Query(
- alias="item-query",
- title="Query string",
- description="Query string for the items to search in the database that have a good match",
- min_length=3,
- max_length=50,
- pattern="^fixedquery$",
- deprecated=True,
- ),
- ] = None,
-):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial010.py b/docs_src/query_params_str_validations/tutorial010_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial010.py
rename to docs_src/query_params_str_validations/tutorial010_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial011.py b/docs_src/query_params_str_validations/tutorial011.py
deleted file mode 100644
index 65bbce781..000000000
--- a/docs_src/query_params_str_validations/tutorial011.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Query
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Union[List[str], None] = Query(default=None)):
- query_items = {"q": q}
- return query_items
diff --git a/docs_src/query_params_str_validations/tutorial011_an.py b/docs_src/query_params_str_validations/tutorial011_an.py
deleted file mode 100644
index 8ed699337..000000000
--- a/docs_src/query_params_str_validations/tutorial011_an.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[Union[List[str], None], Query()] = None):
- query_items = {"q": q}
- return query_items
diff --git a/docs_src/query_params_str_validations/tutorial012.py b/docs_src/query_params_str_validations/tutorial012.py
deleted file mode 100644
index e77d56974..000000000
--- a/docs_src/query_params_str_validations/tutorial012.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, Query
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: List[str] = Query(default=["foo", "bar"])):
- query_items = {"q": q}
- return query_items
diff --git a/docs_src/query_params_str_validations/tutorial012_an.py b/docs_src/query_params_str_validations/tutorial012_an.py
deleted file mode 100644
index 261af250a..000000000
--- a/docs_src/query_params_str_validations/tutorial012_an.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[List[str], Query()] = ["foo", "bar"]):
- query_items = {"q": q}
- return query_items
diff --git a/docs_src/query_params_str_validations/tutorial013_an.py b/docs_src/query_params_str_validations/tutorial013_an.py
deleted file mode 100644
index f12a25055..000000000
--- a/docs_src/query_params_str_validations/tutorial013_an.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[list, Query()] = []):
- query_items = {"q": q}
- return query_items
diff --git a/docs_src/query_params_str_validations/tutorial013.py b/docs_src/query_params_str_validations/tutorial013_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial013.py
rename to docs_src/query_params_str_validations/tutorial013_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial014_an.py b/docs_src/query_params_str_validations/tutorial014_an.py
deleted file mode 100644
index 2eaa58540..000000000
--- a/docs_src/query_params_str_validations/tutorial014_an.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(
- hidden_query: Annotated[Union[str, None], Query(include_in_schema=False)] = None,
-):
- if hidden_query:
- return {"hidden_query": hidden_query}
- else:
- return {"hidden_query": "Not found"}
diff --git a/docs_src/query_params_str_validations/tutorial014.py b/docs_src/query_params_str_validations/tutorial014_py39.py
similarity index 100%
rename from docs_src/query_params_str_validations/tutorial014.py
rename to docs_src/query_params_str_validations/tutorial014_py39.py
diff --git a/docs_src/query_params_str_validations/tutorial015_an.py b/docs_src/query_params_str_validations/tutorial015_an.py
deleted file mode 100644
index f2ec6db12..000000000
--- a/docs_src/query_params_str_validations/tutorial015_an.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import random
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import AfterValidator
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-data = {
- "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
- "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
- "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
-}
-
-
-def check_valid_id(id: str):
- if not id.startswith(("isbn-", "imdb-")):
- raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
- return id
-
-
-@app.get("/items/")
-async def read_items(
- id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None,
-):
- if id:
- item = data.get(id)
- else:
- id, item = random.choice(list(data.items()))
- return {"id": id, "name": item}
diff --git a/docs_src/request_files/__init__.py b/docs_src/request_files/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/request_files/tutorial001_02_an.py b/docs_src/request_files/tutorial001_02_an.py
deleted file mode 100644
index 5007fef15..000000000
--- a/docs_src/request_files/tutorial001_02_an.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI, File, UploadFile
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.post("/files/")
-async def create_file(file: Annotated[Union[bytes, None], File()] = None):
- if not file:
- return {"message": "No file sent"}
- else:
- return {"file_size": len(file)}
-
-
-@app.post("/uploadfile/")
-async def create_upload_file(file: Union[UploadFile, None] = None):
- if not file:
- return {"message": "No upload file sent"}
- else:
- return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_02.py b/docs_src/request_files/tutorial001_02_py39.py
similarity index 100%
rename from docs_src/request_files/tutorial001_02.py
rename to docs_src/request_files/tutorial001_02_py39.py
diff --git a/docs_src/request_files/tutorial001_03_an.py b/docs_src/request_files/tutorial001_03_an.py
deleted file mode 100644
index 8a6b0a245..000000000
--- a/docs_src/request_files/tutorial001_03_an.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from fastapi import FastAPI, File, UploadFile
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.post("/files/")
-async def create_file(file: Annotated[bytes, File(description="A file read as bytes")]):
- return {"file_size": len(file)}
-
-
-@app.post("/uploadfile/")
-async def create_upload_file(
- file: Annotated[UploadFile, File(description="A file read as UploadFile")],
-):
- return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_03.py b/docs_src/request_files/tutorial001_03_py39.py
similarity index 100%
rename from docs_src/request_files/tutorial001_03.py
rename to docs_src/request_files/tutorial001_03_py39.py
diff --git a/docs_src/request_files/tutorial001_an.py b/docs_src/request_files/tutorial001_an.py
deleted file mode 100644
index ca2f76d5c..000000000
--- a/docs_src/request_files/tutorial001_an.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from fastapi import FastAPI, File, UploadFile
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.post("/files/")
-async def create_file(file: Annotated[bytes, File()]):
- return {"file_size": len(file)}
-
-
-@app.post("/uploadfile/")
-async def create_upload_file(file: UploadFile):
- return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001.py b/docs_src/request_files/tutorial001_py39.py
similarity index 100%
rename from docs_src/request_files/tutorial001.py
rename to docs_src/request_files/tutorial001_py39.py
diff --git a/docs_src/request_files/tutorial002.py b/docs_src/request_files/tutorial002.py
deleted file mode 100644
index b4d0acc68..000000000
--- a/docs_src/request_files/tutorial002.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, File, UploadFile
-from fastapi.responses import HTMLResponse
-
-app = FastAPI()
-
-
-@app.post("/files/")
-async def create_files(files: List[bytes] = File()):
- return {"file_sizes": [len(file) for file in files]}
-
-
-@app.post("/uploadfiles/")
-async def create_upload_files(files: List[UploadFile]):
- return {"filenames": [file.filename for file in files]}
-
-
-@app.get("/")
-async def main():
- content = """
-
-
-
-
- """
- return HTMLResponse(content=content)
diff --git a/docs_src/request_files/tutorial002_an.py b/docs_src/request_files/tutorial002_an.py
deleted file mode 100644
index eaa90da2b..000000000
--- a/docs_src/request_files/tutorial002_an.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, File, UploadFile
-from fastapi.responses import HTMLResponse
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.post("/files/")
-async def create_files(files: Annotated[List[bytes], File()]):
- return {"file_sizes": [len(file) for file in files]}
-
-
-@app.post("/uploadfiles/")
-async def create_upload_files(files: List[UploadFile]):
- return {"filenames": [file.filename for file in files]}
-
-
-@app.get("/")
-async def main():
- content = """
-
-
-
-
- """
- return HTMLResponse(content=content)
diff --git a/docs_src/request_files/tutorial003.py b/docs_src/request_files/tutorial003.py
deleted file mode 100644
index e3f805f60..000000000
--- a/docs_src/request_files/tutorial003.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, File, UploadFile
-from fastapi.responses import HTMLResponse
-
-app = FastAPI()
-
-
-@app.post("/files/")
-async def create_files(
- files: List[bytes] = File(description="Multiple files as bytes"),
-):
- return {"file_sizes": [len(file) for file in files]}
-
-
-@app.post("/uploadfiles/")
-async def create_upload_files(
- files: List[UploadFile] = File(description="Multiple files as UploadFile"),
-):
- return {"filenames": [file.filename for file in files]}
-
-
-@app.get("/")
-async def main():
- content = """
-
-
-
-
- """
- return HTMLResponse(content=content)
diff --git a/docs_src/request_files/tutorial003_an.py b/docs_src/request_files/tutorial003_an.py
deleted file mode 100644
index 2238e3c94..000000000
--- a/docs_src/request_files/tutorial003_an.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from typing import List
-
-from fastapi import FastAPI, File, UploadFile
-from fastapi.responses import HTMLResponse
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.post("/files/")
-async def create_files(
- files: Annotated[List[bytes], File(description="Multiple files as bytes")],
-):
- return {"file_sizes": [len(file) for file in files]}
-
-
-@app.post("/uploadfiles/")
-async def create_upload_files(
- files: Annotated[
- List[UploadFile], File(description="Multiple files as UploadFile")
- ],
-):
- return {"filenames": [file.filename for file in files]}
-
-
-@app.get("/")
-async def main():
- content = """
-
-
-
-
- """
- return HTMLResponse(content=content)
diff --git a/docs_src/request_form_models/__init__.py b/docs_src/request_form_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/request_form_models/tutorial001_an.py b/docs_src/request_form_models/tutorial001_an.py
deleted file mode 100644
index 30483d445..000000000
--- a/docs_src/request_form_models/tutorial001_an.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from fastapi import FastAPI, Form
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class FormData(BaseModel):
- username: str
- password: str
-
-
-@app.post("/login/")
-async def login(data: Annotated[FormData, Form()]):
- return data
diff --git a/docs_src/request_form_models/tutorial001.py b/docs_src/request_form_models/tutorial001_py39.py
similarity index 100%
rename from docs_src/request_form_models/tutorial001.py
rename to docs_src/request_form_models/tutorial001_py39.py
diff --git a/docs_src/request_form_models/tutorial002_an.py b/docs_src/request_form_models/tutorial002_an.py
deleted file mode 100644
index bcb022795..000000000
--- a/docs_src/request_form_models/tutorial002_an.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from fastapi import FastAPI, Form
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class FormData(BaseModel):
- username: str
- password: str
- model_config = {"extra": "forbid"}
-
-
-@app.post("/login/")
-async def login(data: Annotated[FormData, Form()]):
- return data
diff --git a/docs_src/request_form_models/tutorial002_pv1.py b/docs_src/request_form_models/tutorial002_pv1.py
deleted file mode 100644
index d5f7db2a6..000000000
--- a/docs_src/request_form_models/tutorial002_pv1.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from fastapi import FastAPI, Form
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class FormData(BaseModel):
- username: str
- password: str
-
- class Config:
- extra = "forbid"
-
-
-@app.post("/login/")
-async def login(data: FormData = Form()):
- return data
diff --git a/docs_src/request_form_models/tutorial002_pv1_an.py b/docs_src/request_form_models/tutorial002_pv1_an.py
deleted file mode 100644
index fe9dbc344..000000000
--- a/docs_src/request_form_models/tutorial002_pv1_an.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from fastapi import FastAPI, Form
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class FormData(BaseModel):
- username: str
- password: str
-
- class Config:
- extra = "forbid"
-
-
-@app.post("/login/")
-async def login(data: Annotated[FormData, Form()]):
- return data
diff --git a/docs_src/request_form_models/tutorial002_pv1_an_py39.py b/docs_src/request_form_models/tutorial002_pv1_an_py39.py
deleted file mode 100644
index 942d5d411..000000000
--- a/docs_src/request_form_models/tutorial002_pv1_an_py39.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Annotated
-
-from fastapi import FastAPI, Form
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class FormData(BaseModel):
- username: str
- password: str
-
- class Config:
- extra = "forbid"
-
-
-@app.post("/login/")
-async def login(data: Annotated[FormData, Form()]):
- return data
diff --git a/docs_src/request_form_models/tutorial002.py b/docs_src/request_form_models/tutorial002_py39.py
similarity index 100%
rename from docs_src/request_form_models/tutorial002.py
rename to docs_src/request_form_models/tutorial002_py39.py
diff --git a/docs_src/request_forms/__init__.py b/docs_src/request_forms/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/request_forms/tutorial001_an.py b/docs_src/request_forms/tutorial001_an.py
deleted file mode 100644
index 677fbf2db..000000000
--- a/docs_src/request_forms/tutorial001_an.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from fastapi import FastAPI, Form
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.post("/login/")
-async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]):
- return {"username": username}
diff --git a/docs_src/request_forms/tutorial001.py b/docs_src/request_forms/tutorial001_py39.py
similarity index 100%
rename from docs_src/request_forms/tutorial001.py
rename to docs_src/request_forms/tutorial001_py39.py
diff --git a/docs_src/request_forms_and_files/__init__.py b/docs_src/request_forms_and_files/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/request_forms_and_files/tutorial001_an.py b/docs_src/request_forms_and_files/tutorial001_an.py
deleted file mode 100644
index 0ea285ac8..000000000
--- a/docs_src/request_forms_and_files/tutorial001_an.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from fastapi import FastAPI, File, Form, UploadFile
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.post("/files/")
-async def create_file(
- file: Annotated[bytes, File()],
- fileb: Annotated[UploadFile, File()],
- token: Annotated[str, Form()],
-):
- return {
- "file_size": len(file),
- "token": token,
- "fileb_content_type": fileb.content_type,
- }
diff --git a/docs_src/request_forms_and_files/tutorial001.py b/docs_src/request_forms_and_files/tutorial001_py39.py
similarity index 100%
rename from docs_src/request_forms_and_files/tutorial001.py
rename to docs_src/request_forms_and_files/tutorial001_py39.py
diff --git a/docs_src/response_change_status_code/__init__.py b/docs_src/response_change_status_code/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_change_status_code/tutorial001.py b/docs_src/response_change_status_code/tutorial001_py39.py
similarity index 100%
rename from docs_src/response_change_status_code/tutorial001.py
rename to docs_src/response_change_status_code/tutorial001_py39.py
diff --git a/docs_src/response_cookies/__init__.py b/docs_src/response_cookies/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_cookies/tutorial001.py b/docs_src/response_cookies/tutorial001_py39.py
similarity index 100%
rename from docs_src/response_cookies/tutorial001.py
rename to docs_src/response_cookies/tutorial001_py39.py
diff --git a/docs_src/response_cookies/tutorial002.py b/docs_src/response_cookies/tutorial002_py39.py
similarity index 100%
rename from docs_src/response_cookies/tutorial002.py
rename to docs_src/response_cookies/tutorial002_py39.py
diff --git a/docs_src/response_directly/__init__.py b/docs_src/response_directly/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_directly/tutorial001_py310.py b/docs_src/response_directly/tutorial001_py310.py
new file mode 100644
index 000000000..81e094dc6
--- /dev/null
+++ b/docs_src/response_directly/tutorial001_py310.py
@@ -0,0 +1,21 @@
+from datetime import datetime
+
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from fastapi.responses import JSONResponse
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ title: str
+ timestamp: datetime
+ description: str | None = None
+
+
+app = FastAPI()
+
+
+@app.put("/items/{id}")
+def update_item(id: str, item: Item):
+ json_compatible_item_data = jsonable_encoder(item)
+ return JSONResponse(content=json_compatible_item_data)
diff --git a/docs_src/response_directly/tutorial001.py b/docs_src/response_directly/tutorial001_py39.py
similarity index 100%
rename from docs_src/response_directly/tutorial001.py
rename to docs_src/response_directly/tutorial001_py39.py
diff --git a/docs_src/response_directly/tutorial002.py b/docs_src/response_directly/tutorial002_py39.py
similarity index 100%
rename from docs_src/response_directly/tutorial002.py
rename to docs_src/response_directly/tutorial002_py39.py
diff --git a/docs_src/response_headers/__init__.py b/docs_src/response_headers/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_headers/tutorial001.py b/docs_src/response_headers/tutorial001_py39.py
similarity index 100%
rename from docs_src/response_headers/tutorial001.py
rename to docs_src/response_headers/tutorial001_py39.py
diff --git a/docs_src/response_headers/tutorial002.py b/docs_src/response_headers/tutorial002_py39.py
similarity index 100%
rename from docs_src/response_headers/tutorial002.py
rename to docs_src/response_headers/tutorial002_py39.py
diff --git a/docs_src/response_model/__init__.py b/docs_src/response_model/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_model/tutorial001.py b/docs_src/response_model/tutorial001.py
deleted file mode 100644
index fd1c902a5..000000000
--- a/docs_src/response_model/tutorial001.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from typing import Any, List, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: List[str] = []
-
-
-@app.post("/items/", response_model=Item)
-async def create_item(item: Item) -> Any:
- return item
-
-
-@app.get("/items/", response_model=List[Item])
-async def read_items() -> Any:
- return [
- {"name": "Portal Gun", "price": 42.0},
- {"name": "Plumbus", "price": 32.0},
- ]
diff --git a/docs_src/response_model/tutorial001_01.py b/docs_src/response_model/tutorial001_01.py
deleted file mode 100644
index 98d30d540..000000000
--- a/docs_src/response_model/tutorial001_01.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
- tags: List[str] = []
-
-
-@app.post("/items/")
-async def create_item(item: Item) -> Item:
- return item
-
-
-@app.get("/items/")
-async def read_items() -> List[Item]:
- return [
- Item(name="Portal Gun", price=42.0),
- Item(name="Plumbus", price=32.0),
- ]
diff --git a/docs_src/response_model/tutorial002.py b/docs_src/response_model/tutorial002_py39.py
similarity index 100%
rename from docs_src/response_model/tutorial002.py
rename to docs_src/response_model/tutorial002_py39.py
diff --git a/docs_src/response_model/tutorial003_01.py b/docs_src/response_model/tutorial003_01_py39.py
similarity index 100%
rename from docs_src/response_model/tutorial003_01.py
rename to docs_src/response_model/tutorial003_01_py39.py
diff --git a/docs_src/response_model/tutorial003_02.py b/docs_src/response_model/tutorial003_02_py39.py
similarity index 100%
rename from docs_src/response_model/tutorial003_02.py
rename to docs_src/response_model/tutorial003_02_py39.py
diff --git a/docs_src/response_model/tutorial003_03.py b/docs_src/response_model/tutorial003_03_py39.py
similarity index 100%
rename from docs_src/response_model/tutorial003_03.py
rename to docs_src/response_model/tutorial003_03_py39.py
diff --git a/docs_src/response_model/tutorial003_04.py b/docs_src/response_model/tutorial003_04_py39.py
similarity index 100%
rename from docs_src/response_model/tutorial003_04.py
rename to docs_src/response_model/tutorial003_04_py39.py
diff --git a/docs_src/response_model/tutorial003_05.py b/docs_src/response_model/tutorial003_05_py39.py
similarity index 100%
rename from docs_src/response_model/tutorial003_05.py
rename to docs_src/response_model/tutorial003_05_py39.py
diff --git a/docs_src/response_model/tutorial003.py b/docs_src/response_model/tutorial003_py39.py
similarity index 100%
rename from docs_src/response_model/tutorial003.py
rename to docs_src/response_model/tutorial003_py39.py
diff --git a/docs_src/response_model/tutorial004.py b/docs_src/response_model/tutorial004.py
deleted file mode 100644
index 10b48039a..000000000
--- a/docs_src/response_model/tutorial004.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: float = 10.5
- tags: List[str] = []
-
-
-items = {
- "foo": {"name": "Foo", "price": 50.2},
- "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
- "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
-}
-
-
-@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
-async def read_item(item_id: str):
- return items[item_id]
diff --git a/docs_src/response_model/tutorial005.py b/docs_src/response_model/tutorial005_py39.py
similarity index 100%
rename from docs_src/response_model/tutorial005.py
rename to docs_src/response_model/tutorial005_py39.py
diff --git a/docs_src/response_model/tutorial006.py b/docs_src/response_model/tutorial006_py39.py
similarity index 100%
rename from docs_src/response_model/tutorial006.py
rename to docs_src/response_model/tutorial006_py39.py
diff --git a/docs_src/response_status_code/__init__.py b/docs_src/response_status_code/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/response_status_code/tutorial001.py b/docs_src/response_status_code/tutorial001_py39.py
similarity index 100%
rename from docs_src/response_status_code/tutorial001.py
rename to docs_src/response_status_code/tutorial001_py39.py
diff --git a/docs_src/response_status_code/tutorial002.py b/docs_src/response_status_code/tutorial002_py39.py
similarity index 100%
rename from docs_src/response_status_code/tutorial002.py
rename to docs_src/response_status_code/tutorial002_py39.py
diff --git a/docs_src/schema_extra_example/__init__.py b/docs_src/schema_extra_example/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/schema_extra_example/tutorial001_pv1.py b/docs_src/schema_extra_example/tutorial001_pv1.py
deleted file mode 100644
index 6ab96ff85..000000000
--- a/docs_src/schema_extra_example/tutorial001_pv1.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from typing import Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
- class Config:
- schema_extra = {
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- }
- ]
- }
-
-
-@app.put("/items/{item_id}")
-async def update_item(item_id: int, item: Item):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/schema_extra_example/tutorial001_pv1_py310.py b/docs_src/schema_extra_example/tutorial001_pv1_py310.py
index ec83f1112..b13b8a8c2 100644
--- a/docs_src/schema_extra_example/tutorial001_pv1_py310.py
+++ b/docs_src/schema_extra_example/tutorial001_pv1_py310.py
@@ -1,5 +1,5 @@
from fastapi import FastAPI
-from pydantic import BaseModel
+from pydantic.v1 import BaseModel
app = FastAPI()
diff --git a/docs_src/schema_extra_example/tutorial001_pv1_py39.py b/docs_src/schema_extra_example/tutorial001_pv1_py39.py
new file mode 100644
index 000000000..3240b35d6
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial001_pv1_py39.py
@@ -0,0 +1,31 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic.v1 import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+ class Config:
+ schema_extra = {
+ "examples": [
+ {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ }
+ ]
+ }
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial001.py b/docs_src/schema_extra_example/tutorial001_py39.py
similarity index 100%
rename from docs_src/schema_extra_example/tutorial001.py
rename to docs_src/schema_extra_example/tutorial001_py39.py
diff --git a/docs_src/schema_extra_example/tutorial002.py b/docs_src/schema_extra_example/tutorial002_py39.py
similarity index 100%
rename from docs_src/schema_extra_example/tutorial002.py
rename to docs_src/schema_extra_example/tutorial002_py39.py
diff --git a/docs_src/schema_extra_example/tutorial003_an.py b/docs_src/schema_extra_example/tutorial003_an.py
deleted file mode 100644
index 23675aba1..000000000
--- a/docs_src/schema_extra_example/tutorial003_an.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(
- item_id: int,
- item: Annotated[
- Item,
- Body(
- examples=[
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- }
- ],
- ),
- ],
-):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/schema_extra_example/tutorial003.py b/docs_src/schema_extra_example/tutorial003_py39.py
similarity index 100%
rename from docs_src/schema_extra_example/tutorial003.py
rename to docs_src/schema_extra_example/tutorial003_py39.py
diff --git a/docs_src/schema_extra_example/tutorial004_an.py b/docs_src/schema_extra_example/tutorial004_an.py
deleted file mode 100644
index e817302a2..000000000
--- a/docs_src/schema_extra_example/tutorial004_an.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(
- *,
- item_id: int,
- item: Annotated[
- Item,
- Body(
- 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",
- },
- ],
- ),
- ],
-):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/schema_extra_example/tutorial004.py b/docs_src/schema_extra_example/tutorial004_py39.py
similarity index 100%
rename from docs_src/schema_extra_example/tutorial004.py
rename to docs_src/schema_extra_example/tutorial004_py39.py
diff --git a/docs_src/schema_extra_example/tutorial005_an.py b/docs_src/schema_extra_example/tutorial005_an.py
deleted file mode 100644
index 4b2d9c662..000000000
--- a/docs_src/schema_extra_example/tutorial005_an.py
+++ /dev/null
@@ -1,55 +0,0 @@
-from typing import Union
-
-from fastapi import Body, FastAPI
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
- price: float
- tax: Union[float, None] = None
-
-
-@app.put("/items/{item_id}")
-async def update_item(
- *,
- item_id: int,
- item: Annotated[
- Item,
- Body(
- openapi_examples={
- "normal": {
- "summary": "A normal example",
- "description": "A **normal** item works correctly.",
- "value": {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- },
- "converted": {
- "summary": "An example with converted data",
- "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
- "value": {
- "name": "Bar",
- "price": "35.4",
- },
- },
- "invalid": {
- "summary": "Invalid data is rejected with an error",
- "value": {
- "name": "Baz",
- "price": "thirty five point four",
- },
- },
- },
- ),
- ],
-):
- results = {"item_id": item_id, "item": item}
- return results
diff --git a/docs_src/schema_extra_example/tutorial005.py b/docs_src/schema_extra_example/tutorial005_py39.py
similarity index 100%
rename from docs_src/schema_extra_example/tutorial005.py
rename to docs_src/schema_extra_example/tutorial005_py39.py
diff --git a/docs_src/security/__init__.py b/docs_src/security/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/security/tutorial001_an.py b/docs_src/security/tutorial001_an.py
deleted file mode 100644
index dac915b7c..000000000
--- a/docs_src/security/tutorial001_an.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from fastapi import Depends, FastAPI
-from fastapi.security import OAuth2PasswordBearer
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
-
-
-@app.get("/items/")
-async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
- return {"token": token}
diff --git a/docs_src/security/tutorial001.py b/docs_src/security/tutorial001_py39.py
similarity index 100%
rename from docs_src/security/tutorial001.py
rename to docs_src/security/tutorial001_py39.py
diff --git a/docs_src/security/tutorial002_an.py b/docs_src/security/tutorial002_an.py
deleted file mode 100644
index 291b3bf53..000000000
--- a/docs_src/security/tutorial002_an.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from typing import Union
-
-from fastapi import Depends, FastAPI
-from fastapi.security import OAuth2PasswordBearer
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
-
-
-class User(BaseModel):
- username: str
- email: Union[str, None] = None
- full_name: Union[str, None] = None
- disabled: Union[bool, None] = None
-
-
-def fake_decode_token(token):
- return User(
- username=token + "fakedecoded", email="john@example.com", full_name="John Doe"
- )
-
-
-async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
- user = fake_decode_token(token)
- return user
-
-
-@app.get("/users/me")
-async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
- return current_user
diff --git a/docs_src/security/tutorial002.py b/docs_src/security/tutorial002_py39.py
similarity index 100%
rename from docs_src/security/tutorial002.py
rename to docs_src/security/tutorial002_py39.py
diff --git a/docs_src/security/tutorial003.py b/docs_src/security/tutorial003.py
deleted file mode 100644
index 4b324866f..000000000
--- a/docs_src/security/tutorial003.py
+++ /dev/null
@@ -1,90 +0,0 @@
-from typing import Union
-
-from fastapi import Depends, FastAPI, HTTPException, status
-from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
-from pydantic import BaseModel
-
-fake_users_db = {
- "johndoe": {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "hashed_password": "fakehashedsecret",
- "disabled": False,
- },
- "alice": {
- "username": "alice",
- "full_name": "Alice Wonderson",
- "email": "alice@example.com",
- "hashed_password": "fakehashedsecret2",
- "disabled": True,
- },
-}
-
-app = FastAPI()
-
-
-def fake_hash_password(password: str):
- return "fakehashed" + password
-
-
-oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
-
-
-class User(BaseModel):
- username: str
- email: Union[str, None] = None
- full_name: Union[str, None] = None
- disabled: Union[bool, None] = None
-
-
-class UserInDB(User):
- hashed_password: str
-
-
-def get_user(db, username: str):
- if username in db:
- user_dict = db[username]
- return UserInDB(**user_dict)
-
-
-def fake_decode_token(token):
- # This doesn't provide any security at all
- # Check the next version
- user = get_user(fake_users_db, token)
- return user
-
-
-async def get_current_user(token: str = Depends(oauth2_scheme)):
- user = fake_decode_token(token)
- if not user:
- raise HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Invalid authentication credentials",
- headers={"WWW-Authenticate": "Bearer"},
- )
- return user
-
-
-async def get_current_active_user(current_user: User = Depends(get_current_user)):
- if current_user.disabled:
- raise HTTPException(status_code=400, detail="Inactive user")
- return current_user
-
-
-@app.post("/token")
-async def login(form_data: OAuth2PasswordRequestForm = Depends()):
- user_dict = fake_users_db.get(form_data.username)
- if not user_dict:
- raise HTTPException(status_code=400, detail="Incorrect username or password")
- user = UserInDB(**user_dict)
- hashed_password = fake_hash_password(form_data.password)
- if not hashed_password == user.hashed_password:
- raise HTTPException(status_code=400, detail="Incorrect username or password")
-
- return {"access_token": user.username, "token_type": "bearer"}
-
-
-@app.get("/users/me")
-async def read_users_me(current_user: User = Depends(get_current_active_user)):
- return current_user
diff --git a/docs_src/security/tutorial003_an.py b/docs_src/security/tutorial003_an.py
deleted file mode 100644
index 8fb40dd4a..000000000
--- a/docs_src/security/tutorial003_an.py
+++ /dev/null
@@ -1,95 +0,0 @@
-from typing import Union
-
-from fastapi import Depends, FastAPI, HTTPException, status
-from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-fake_users_db = {
- "johndoe": {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "hashed_password": "fakehashedsecret",
- "disabled": False,
- },
- "alice": {
- "username": "alice",
- "full_name": "Alice Wonderson",
- "email": "alice@example.com",
- "hashed_password": "fakehashedsecret2",
- "disabled": True,
- },
-}
-
-app = FastAPI()
-
-
-def fake_hash_password(password: str):
- return "fakehashed" + password
-
-
-oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
-
-
-class User(BaseModel):
- username: str
- email: Union[str, None] = None
- full_name: Union[str, None] = None
- disabled: Union[bool, None] = None
-
-
-class UserInDB(User):
- hashed_password: str
-
-
-def get_user(db, username: str):
- if username in db:
- user_dict = db[username]
- return UserInDB(**user_dict)
-
-
-def fake_decode_token(token):
- # This doesn't provide any security at all
- # Check the next version
- user = get_user(fake_users_db, token)
- return user
-
-
-async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
- user = fake_decode_token(token)
- if not user:
- raise HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Invalid authentication credentials",
- headers={"WWW-Authenticate": "Bearer"},
- )
- return user
-
-
-async def get_current_active_user(
- current_user: Annotated[User, Depends(get_current_user)],
-):
- if current_user.disabled:
- raise HTTPException(status_code=400, detail="Inactive user")
- return current_user
-
-
-@app.post("/token")
-async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
- user_dict = fake_users_db.get(form_data.username)
- if not user_dict:
- raise HTTPException(status_code=400, detail="Incorrect username or password")
- user = UserInDB(**user_dict)
- hashed_password = fake_hash_password(form_data.password)
- if not hashed_password == user.hashed_password:
- raise HTTPException(status_code=400, detail="Incorrect username or password")
-
- return {"access_token": user.username, "token_type": "bearer"}
-
-
-@app.get("/users/me")
-async def read_users_me(
- current_user: Annotated[User, Depends(get_current_active_user)],
-):
- return current_user
diff --git a/docs_src/security/tutorial003_an_py310.py b/docs_src/security/tutorial003_an_py310.py
index ced4a2fbc..4a2743f6f 100644
--- a/docs_src/security/tutorial003_an_py310.py
+++ b/docs_src/security/tutorial003_an_py310.py
@@ -60,7 +60,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Invalid authentication credentials",
+ detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
return user
diff --git a/docs_src/security/tutorial003_an_py39.py b/docs_src/security/tutorial003_an_py39.py
index 068a3933e..b396210c8 100644
--- a/docs_src/security/tutorial003_an_py39.py
+++ b/docs_src/security/tutorial003_an_py39.py
@@ -60,7 +60,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Invalid authentication credentials",
+ detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
return user
diff --git a/docs_src/security/tutorial003_py310.py b/docs_src/security/tutorial003_py310.py
index af935e997..081259b31 100644
--- a/docs_src/security/tutorial003_py310.py
+++ b/docs_src/security/tutorial003_py310.py
@@ -58,7 +58,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Invalid authentication credentials",
+ detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
return user
diff --git a/docs_src/security/tutorial003_py39.py b/docs_src/security/tutorial003_py39.py
new file mode 100644
index 000000000..ce7a71b68
--- /dev/null
+++ b/docs_src/security/tutorial003_py39.py
@@ -0,0 +1,90 @@
+from typing import Union
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from pydantic import BaseModel
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "fakehashedsecret",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Wonderson",
+ "email": "alice@example.com",
+ "hashed_password": "fakehashedsecret2",
+ "disabled": True,
+ },
+}
+
+app = FastAPI()
+
+
+def fake_hash_password(password: str):
+ return "fakehashed" + password
+
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+class User(BaseModel):
+ username: str
+ email: Union[str, None] = None
+ full_name: Union[str, None] = None
+ disabled: Union[bool, None] = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def fake_decode_token(token):
+ # This doesn't provide any security at all
+ # Check the next version
+ user = get_user(fake_users_db, token)
+ return user
+
+
+async def get_current_user(token: str = Depends(oauth2_scheme)):
+ user = fake_decode_token(token)
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Not authenticated",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ return user
+
+
+async def get_current_active_user(current_user: User = Depends(get_current_user)):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token")
+async def login(form_data: OAuth2PasswordRequestForm = Depends()):
+ user_dict = fake_users_db.get(form_data.username)
+ if not user_dict:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ user = UserInDB(**user_dict)
+ hashed_password = fake_hash_password(form_data.password)
+ if not hashed_password == user.hashed_password:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+
+ return {"access_token": user.username, "token_type": "bearer"}
+
+
+@app.get("/users/me")
+async def read_users_me(current_user: User = Depends(get_current_active_user)):
+ return current_user
diff --git a/docs_src/security/tutorial004_an.py b/docs_src/security/tutorial004_an.py
deleted file mode 100644
index 018234e30..000000000
--- a/docs_src/security/tutorial004_an.py
+++ /dev/null
@@ -1,148 +0,0 @@
-from datetime import datetime, timedelta, timezone
-from typing import Union
-
-import jwt
-from fastapi import Depends, FastAPI, HTTPException, status
-from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
-from jwt.exceptions import InvalidTokenError
-from pwdlib import PasswordHash
-from pydantic import BaseModel
-from typing_extensions import Annotated
-
-# to get a string like this run:
-# openssl rand -hex 32
-SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
-ALGORITHM = "HS256"
-ACCESS_TOKEN_EXPIRE_MINUTES = 30
-
-
-fake_users_db = {
- "johndoe": {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc",
- "disabled": False,
- }
-}
-
-
-class Token(BaseModel):
- access_token: str
- token_type: str
-
-
-class TokenData(BaseModel):
- username: Union[str, None] = None
-
-
-class User(BaseModel):
- username: str
- email: Union[str, None] = None
- full_name: Union[str, None] = None
- disabled: Union[bool, None] = None
-
-
-class UserInDB(User):
- hashed_password: str
-
-
-password_hash = PasswordHash.recommended()
-
-oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
-
-app = FastAPI()
-
-
-def verify_password(plain_password, hashed_password):
- return password_hash.verify(plain_password, hashed_password)
-
-
-def get_password_hash(password):
- return password_hash.hash(password)
-
-
-def get_user(db, username: str):
- if username in db:
- user_dict = db[username]
- return UserInDB(**user_dict)
-
-
-def authenticate_user(fake_db, username: str, password: str):
- user = get_user(fake_db, username)
- if not user:
- return False
- if not verify_password(password, user.hashed_password):
- return False
- return user
-
-
-def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
- to_encode = data.copy()
- if expires_delta:
- expire = datetime.now(timezone.utc) + expires_delta
- else:
- expire = datetime.now(timezone.utc) + timedelta(minutes=15)
- to_encode.update({"exp": expire})
- encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
- return encoded_jwt
-
-
-async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
- credentials_exception = HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Could not validate credentials",
- headers={"WWW-Authenticate": "Bearer"},
- )
- try:
- payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username = payload.get("sub")
- if username is None:
- raise credentials_exception
- token_data = TokenData(username=username)
- except InvalidTokenError:
- raise credentials_exception
- user = get_user(fake_users_db, username=token_data.username)
- if user is None:
- raise credentials_exception
- return user
-
-
-async def get_current_active_user(
- current_user: Annotated[User, Depends(get_current_user)],
-):
- if current_user.disabled:
- raise HTTPException(status_code=400, detail="Inactive user")
- return current_user
-
-
-@app.post("/token")
-async def login_for_access_token(
- form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
-) -> Token:
- user = authenticate_user(fake_users_db, form_data.username, form_data.password)
- if not user:
- raise HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Incorrect username or password",
- headers={"WWW-Authenticate": "Bearer"},
- )
- access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
- access_token = create_access_token(
- data={"sub": user.username}, expires_delta=access_token_expires
- )
- return Token(access_token=access_token, token_type="bearer")
-
-
-@app.get("/users/me/", response_model=User)
-async def read_users_me(
- current_user: Annotated[User, Depends(get_current_active_user)],
-):
- return current_user
-
-
-@app.get("/users/me/items/")
-async def read_own_items(
- current_user: Annotated[User, Depends(get_current_active_user)],
-):
- return [{"item_id": "Foo", "owner": current_user.username}]
diff --git a/docs_src/security/tutorial004.py b/docs_src/security/tutorial004_py39.py
similarity index 100%
rename from docs_src/security/tutorial004.py
rename to docs_src/security/tutorial004_py39.py
diff --git a/docs_src/security/tutorial005.py b/docs_src/security/tutorial005.py
deleted file mode 100644
index fdd73bcd8..000000000
--- a/docs_src/security/tutorial005.py
+++ /dev/null
@@ -1,177 +0,0 @@
-from datetime import datetime, timedelta, timezone
-from typing import List, Union
-
-import jwt
-from fastapi import Depends, FastAPI, HTTPException, Security, status
-from fastapi.security import (
- OAuth2PasswordBearer,
- OAuth2PasswordRequestForm,
- SecurityScopes,
-)
-from jwt.exceptions import InvalidTokenError
-from pwdlib import PasswordHash
-from pydantic import BaseModel, ValidationError
-
-# to get a string like this run:
-# openssl rand -hex 32
-SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
-ALGORITHM = "HS256"
-ACCESS_TOKEN_EXPIRE_MINUTES = 30
-
-
-fake_users_db = {
- "johndoe": {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc",
- "disabled": False,
- },
- "alice": {
- "username": "alice",
- "full_name": "Alice Chains",
- "email": "alicechains@example.com",
- "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE",
- "disabled": True,
- },
-}
-
-
-class Token(BaseModel):
- access_token: str
- token_type: str
-
-
-class TokenData(BaseModel):
- username: Union[str, None] = None
- scopes: List[str] = []
-
-
-class User(BaseModel):
- username: str
- email: Union[str, None] = None
- full_name: Union[str, None] = None
- disabled: Union[bool, None] = None
-
-
-class UserInDB(User):
- hashed_password: str
-
-
-password_hash = PasswordHash.recommended()
-
-oauth2_scheme = OAuth2PasswordBearer(
- tokenUrl="token",
- scopes={"me": "Read information about the current user.", "items": "Read items."},
-)
-
-app = FastAPI()
-
-
-def verify_password(plain_password, hashed_password):
- return password_hash.verify(plain_password, hashed_password)
-
-
-def get_password_hash(password):
- return password_hash.hash(password)
-
-
-def get_user(db, username: str):
- if username in db:
- user_dict = db[username]
- return UserInDB(**user_dict)
-
-
-def authenticate_user(fake_db, username: str, password: str):
- user = get_user(fake_db, username)
- if not user:
- return False
- if not verify_password(password, user.hashed_password):
- return False
- return user
-
-
-def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
- to_encode = data.copy()
- if expires_delta:
- expire = datetime.now(timezone.utc) + expires_delta
- else:
- expire = datetime.now(timezone.utc) + timedelta(minutes=15)
- to_encode.update({"exp": expire})
- encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
- return encoded_jwt
-
-
-async def get_current_user(
- security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
-):
- if security_scopes.scopes:
- authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
- else:
- authenticate_value = "Bearer"
- credentials_exception = HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Could not validate credentials",
- headers={"WWW-Authenticate": authenticate_value},
- )
- try:
- payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username: str = payload.get("sub")
- if username is None:
- raise credentials_exception
- scope: str = payload.get("scope", "")
- token_scopes = scope.split(" ")
- token_data = TokenData(scopes=token_scopes, username=username)
- except (InvalidTokenError, ValidationError):
- raise credentials_exception
- user = get_user(fake_users_db, username=token_data.username)
- if user is None:
- raise credentials_exception
- for scope in security_scopes.scopes:
- if scope not in token_data.scopes:
- raise HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Not enough permissions",
- headers={"WWW-Authenticate": authenticate_value},
- )
- return user
-
-
-async def get_current_active_user(
- current_user: User = Security(get_current_user, scopes=["me"]),
-):
- if current_user.disabled:
- raise HTTPException(status_code=400, detail="Inactive user")
- return current_user
-
-
-@app.post("/token")
-async def login_for_access_token(
- form_data: OAuth2PasswordRequestForm = Depends(),
-) -> Token:
- user = authenticate_user(fake_users_db, form_data.username, form_data.password)
- if not user:
- raise HTTPException(status_code=400, detail="Incorrect username or password")
- access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
- access_token = create_access_token(
- data={"sub": user.username, "scope": " ".join(form_data.scopes)},
- expires_delta=access_token_expires,
- )
- return Token(access_token=access_token, token_type="bearer")
-
-
-@app.get("/users/me/", response_model=User)
-async def read_users_me(current_user: User = Depends(get_current_active_user)):
- return current_user
-
-
-@app.get("/users/me/items/")
-async def read_own_items(
- current_user: User = Security(get_current_active_user, scopes=["items"]),
-):
- return [{"item_id": "Foo", "owner": current_user.username}]
-
-
-@app.get("/status/")
-async def read_system_status(current_user: User = Depends(get_current_user)):
- return {"status": "ok"}
diff --git a/docs_src/security/tutorial005_an.py b/docs_src/security/tutorial005_an.py
deleted file mode 100644
index e1d7b4f62..000000000
--- a/docs_src/security/tutorial005_an.py
+++ /dev/null
@@ -1,180 +0,0 @@
-from datetime import datetime, timedelta, timezone
-from typing import List, Union
-
-import jwt
-from fastapi import Depends, FastAPI, HTTPException, Security, status
-from fastapi.security import (
- OAuth2PasswordBearer,
- OAuth2PasswordRequestForm,
- SecurityScopes,
-)
-from jwt.exceptions import InvalidTokenError
-from pwdlib import PasswordHash
-from pydantic import BaseModel, ValidationError
-from typing_extensions import Annotated
-
-# to get a string like this run:
-# openssl rand -hex 32
-SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
-ALGORITHM = "HS256"
-ACCESS_TOKEN_EXPIRE_MINUTES = 30
-
-
-fake_users_db = {
- "johndoe": {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc",
- "disabled": False,
- },
- "alice": {
- "username": "alice",
- "full_name": "Alice Chains",
- "email": "alicechains@example.com",
- "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE",
- "disabled": True,
- },
-}
-
-
-class Token(BaseModel):
- access_token: str
- token_type: str
-
-
-class TokenData(BaseModel):
- username: Union[str, None] = None
- scopes: List[str] = []
-
-
-class User(BaseModel):
- username: str
- email: Union[str, None] = None
- full_name: Union[str, None] = None
- disabled: Union[bool, None] = None
-
-
-class UserInDB(User):
- hashed_password: str
-
-
-password_hash = PasswordHash.recommended()
-
-oauth2_scheme = OAuth2PasswordBearer(
- tokenUrl="token",
- scopes={"me": "Read information about the current user.", "items": "Read items."},
-)
-
-app = FastAPI()
-
-
-def verify_password(plain_password, hashed_password):
- return password_hash.verify(plain_password, hashed_password)
-
-
-def get_password_hash(password):
- return password_hash.hash(password)
-
-
-def get_user(db, username: str):
- if username in db:
- user_dict = db[username]
- return UserInDB(**user_dict)
-
-
-def authenticate_user(fake_db, username: str, password: str):
- user = get_user(fake_db, username)
- if not user:
- return False
- if not verify_password(password, user.hashed_password):
- return False
- return user
-
-
-def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
- to_encode = data.copy()
- if expires_delta:
- expire = datetime.now(timezone.utc) + expires_delta
- else:
- expire = datetime.now(timezone.utc) + timedelta(minutes=15)
- to_encode.update({"exp": expire})
- encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
- return encoded_jwt
-
-
-async def get_current_user(
- security_scopes: SecurityScopes, token: Annotated[str, Depends(oauth2_scheme)]
-):
- if security_scopes.scopes:
- authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
- else:
- authenticate_value = "Bearer"
- credentials_exception = HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Could not validate credentials",
- headers={"WWW-Authenticate": authenticate_value},
- )
- try:
- payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username = payload.get("sub")
- if username is None:
- raise credentials_exception
- scope: str = payload.get("scope", "")
- token_scopes = scope.split(" ")
- token_data = TokenData(scopes=token_scopes, username=username)
- except (InvalidTokenError, ValidationError):
- raise credentials_exception
- user = get_user(fake_users_db, username=token_data.username)
- if user is None:
- raise credentials_exception
- for scope in security_scopes.scopes:
- if scope not in token_data.scopes:
- raise HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Not enough permissions",
- headers={"WWW-Authenticate": authenticate_value},
- )
- return user
-
-
-async def get_current_active_user(
- current_user: Annotated[User, Security(get_current_user, scopes=["me"])],
-):
- if current_user.disabled:
- raise HTTPException(status_code=400, detail="Inactive user")
- return current_user
-
-
-@app.post("/token")
-async def login_for_access_token(
- form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
-) -> Token:
- user = authenticate_user(fake_users_db, form_data.username, form_data.password)
- if not user:
- raise HTTPException(status_code=400, detail="Incorrect username or password")
- access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
- access_token = create_access_token(
- data={"sub": user.username, "scope": " ".join(form_data.scopes)},
- expires_delta=access_token_expires,
- )
- return Token(access_token=access_token, token_type="bearer")
-
-
-@app.get("/users/me/", response_model=User)
-async def read_users_me(
- current_user: Annotated[User, Depends(get_current_active_user)],
-):
- return current_user
-
-
-@app.get("/users/me/items/")
-async def read_own_items(
- current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])],
-):
- return [{"item_id": "Foo", "owner": current_user.username}]
-
-
-@app.get("/status/")
-async def read_system_status(current_user: Annotated[User, Depends(get_current_user)]):
- return {"status": "ok"}
diff --git a/docs_src/security/tutorial006_an.py b/docs_src/security/tutorial006_an.py
deleted file mode 100644
index 985e4b2ad..000000000
--- a/docs_src/security/tutorial006_an.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from fastapi import Depends, FastAPI
-from fastapi.security import HTTPBasic, HTTPBasicCredentials
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-security = HTTPBasic()
-
-
-@app.get("/users/me")
-def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]):
- return {"username": credentials.username, "password": credentials.password}
diff --git a/docs_src/security/tutorial006.py b/docs_src/security/tutorial006_py39.py
similarity index 100%
rename from docs_src/security/tutorial006.py
rename to docs_src/security/tutorial006_py39.py
diff --git a/docs_src/security/tutorial007_an.py b/docs_src/security/tutorial007_an.py
deleted file mode 100644
index 0d211dfde..000000000
--- a/docs_src/security/tutorial007_an.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import secrets
-
-from fastapi import Depends, FastAPI, HTTPException, status
-from fastapi.security import HTTPBasic, HTTPBasicCredentials
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-security = HTTPBasic()
-
-
-def get_current_username(
- credentials: Annotated[HTTPBasicCredentials, Depends(security)],
-):
- current_username_bytes = credentials.username.encode("utf8")
- correct_username_bytes = b"stanleyjobson"
- is_correct_username = secrets.compare_digest(
- current_username_bytes, correct_username_bytes
- )
- current_password_bytes = credentials.password.encode("utf8")
- correct_password_bytes = b"swordfish"
- is_correct_password = secrets.compare_digest(
- current_password_bytes, correct_password_bytes
- )
- if not (is_correct_username and is_correct_password):
- raise HTTPException(
- status_code=status.HTTP_401_UNAUTHORIZED,
- detail="Incorrect username or password",
- headers={"WWW-Authenticate": "Basic"},
- )
- return credentials.username
-
-
-@app.get("/users/me")
-def read_current_user(username: Annotated[str, Depends(get_current_username)]):
- return {"username": username}
diff --git a/docs_src/security/tutorial007.py b/docs_src/security/tutorial007_py39.py
similarity index 100%
rename from docs_src/security/tutorial007.py
rename to docs_src/security/tutorial007_py39.py
diff --git a/docs_src/separate_openapi_schemas/__init__.py b/docs_src/separate_openapi_schemas/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/separate_openapi_schemas/tutorial001.py b/docs_src/separate_openapi_schemas/tutorial001.py
deleted file mode 100644
index 415eef8e2..000000000
--- a/docs_src/separate_openapi_schemas/tutorial001.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
-
-
-app = FastAPI()
-
-
-@app.post("/items/")
-def create_item(item: Item):
- return item
-
-
-@app.get("/items/")
-def read_items() -> List[Item]:
- return [
- Item(
- name="Portal Gun",
- description="Device to travel through the multi-rick-verse",
- ),
- Item(name="Plumbus"),
- ]
diff --git a/docs_src/separate_openapi_schemas/tutorial002.py b/docs_src/separate_openapi_schemas/tutorial002.py
deleted file mode 100644
index 7df93783b..000000000
--- a/docs_src/separate_openapi_schemas/tutorial002.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from typing import List, Union
-
-from fastapi import FastAPI
-from pydantic import BaseModel
-
-
-class Item(BaseModel):
- name: str
- description: Union[str, None] = None
-
-
-app = FastAPI(separate_input_output_schemas=False)
-
-
-@app.post("/items/")
-def create_item(item: Item):
- return item
-
-
-@app.get("/items/")
-def read_items() -> List[Item]:
- return [
- Item(
- name="Portal Gun",
- description="Device to travel through the multi-rick-verse",
- ),
- Item(name="Plumbus"),
- ]
diff --git a/docs_src/settings/__init__.py b/docs_src/settings/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/settings/app01_py39/__init__.py b/docs_src/settings/app01_py39/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/settings/app01/config.py b/docs_src/settings/app01_py39/config.py
similarity index 100%
rename from docs_src/settings/app01/config.py
rename to docs_src/settings/app01_py39/config.py
diff --git a/docs_src/settings/app01/main.py b/docs_src/settings/app01_py39/main.py
similarity index 100%
rename from docs_src/settings/app01/main.py
rename to docs_src/settings/app01_py39/main.py
diff --git a/docs_src/settings/app02_an/config.py b/docs_src/settings/app02_an/config.py
deleted file mode 100644
index e17b5035d..000000000
--- a/docs_src/settings/app02_an/config.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from pydantic_settings import BaseSettings
-
-
-class Settings(BaseSettings):
- app_name: str = "Awesome API"
- admin_email: str
- items_per_user: int = 50
diff --git a/docs_src/settings/app02_an/main.py b/docs_src/settings/app02_an/main.py
deleted file mode 100644
index 3a578cc33..000000000
--- a/docs_src/settings/app02_an/main.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from functools import lru_cache
-
-from fastapi import Depends, FastAPI
-from typing_extensions import Annotated
-
-from .config import Settings
-
-app = FastAPI()
-
-
-@lru_cache
-def get_settings():
- return Settings()
-
-
-@app.get("/info")
-async def info(settings: Annotated[Settings, Depends(get_settings)]):
- return {
- "app_name": settings.app_name,
- "admin_email": settings.admin_email,
- "items_per_user": settings.items_per_user,
- }
diff --git a/docs_src/settings/app02_an/test_main.py b/docs_src/settings/app02_an/test_main.py
deleted file mode 100644
index 7a04d7e8e..000000000
--- a/docs_src/settings/app02_an/test_main.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from fastapi.testclient import TestClient
-
-from .config import Settings
-from .main import app, get_settings
-
-client = TestClient(app)
-
-
-def get_settings_override():
- return Settings(admin_email="testing_admin@example.com")
-
-
-app.dependency_overrides[get_settings] = get_settings_override
-
-
-def test_app():
- response = client.get("/info")
- data = response.json()
- assert data == {
- "app_name": "Awesome API",
- "admin_email": "testing_admin@example.com",
- "items_per_user": 50,
- }
diff --git a/docs_src/settings/app02_py39/__init__.py b/docs_src/settings/app02_py39/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/settings/app02/config.py b/docs_src/settings/app02_py39/config.py
similarity index 100%
rename from docs_src/settings/app02/config.py
rename to docs_src/settings/app02_py39/config.py
diff --git a/docs_src/settings/app02/main.py b/docs_src/settings/app02_py39/main.py
similarity index 100%
rename from docs_src/settings/app02/main.py
rename to docs_src/settings/app02_py39/main.py
diff --git a/docs_src/settings/app02/test_main.py b/docs_src/settings/app02_py39/test_main.py
similarity index 100%
rename from docs_src/settings/app02/test_main.py
rename to docs_src/settings/app02_py39/test_main.py
diff --git a/docs_src/settings/app03/config.py b/docs_src/settings/app03/config.py
deleted file mode 100644
index 942aea3e5..000000000
--- a/docs_src/settings/app03/config.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from pydantic_settings import BaseSettings
-
-
-class Settings(BaseSettings):
- app_name: str = "Awesome API"
- admin_email: str
- items_per_user: int = 50
-
- class Config:
- env_file = ".env"
diff --git a/docs_src/settings/app03_an/config_pv1.py b/docs_src/settings/app03_an/config_pv1.py
deleted file mode 100644
index e1c3ee300..000000000
--- a/docs_src/settings/app03_an/config_pv1.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from pydantic import BaseSettings
-
-
-class Settings(BaseSettings):
- app_name: str = "Awesome API"
- admin_email: str
- items_per_user: int = 50
-
- class Config:
- env_file = ".env"
diff --git a/docs_src/settings/app03_an/main.py b/docs_src/settings/app03_an/main.py
deleted file mode 100644
index 2f64b9cd1..000000000
--- a/docs_src/settings/app03_an/main.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from functools import lru_cache
-from typing import Annotated
-
-from fastapi import Depends, FastAPI
-
-from . import config
-
-app = FastAPI()
-
-
-@lru_cache
-def get_settings():
- return config.Settings()
-
-
-@app.get("/info")
-async def info(settings: Annotated[config.Settings, Depends(get_settings)]):
- return {
- "app_name": settings.app_name,
- "admin_email": settings.admin_email,
- "items_per_user": settings.items_per_user,
- }
diff --git a/docs_src/settings/app03_an_py39/config.py b/docs_src/settings/app03_an_py39/config.py
index 942aea3e5..08f8f88c2 100644
--- a/docs_src/settings/app03_an_py39/config.py
+++ b/docs_src/settings/app03_an_py39/config.py
@@ -1,4 +1,4 @@
-from pydantic_settings import BaseSettings
+from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
@@ -6,5 +6,4 @@ class Settings(BaseSettings):
admin_email: str
items_per_user: int = 50
- class Config:
- env_file = ".env"
+ model_config = SettingsConfigDict(env_file=".env")
diff --git a/docs_src/settings/app03_an_py39/config_pv1.py b/docs_src/settings/app03_an_py39/config_pv1.py
new file mode 100644
index 000000000..7ae66ef77
--- /dev/null
+++ b/docs_src/settings/app03_an_py39/config_pv1.py
@@ -0,0 +1,10 @@
+from pydantic.v1 import BaseSettings
+
+
+class Settings(BaseSettings):
+ app_name: str = "Awesome API"
+ admin_email: str
+ items_per_user: int = 50
+
+ class Config:
+ env_file = ".env"
diff --git a/docs_src/settings/app03_an_py39/main.py b/docs_src/settings/app03_an_py39/main.py
index 62f347639..2f64b9cd1 100644
--- a/docs_src/settings/app03_an_py39/main.py
+++ b/docs_src/settings/app03_an_py39/main.py
@@ -1,7 +1,7 @@
from functools import lru_cache
+from typing import Annotated
from fastapi import Depends, FastAPI
-from typing_extensions import Annotated
from . import config
diff --git a/docs_src/settings/app03_py39/__init__.py b/docs_src/settings/app03_py39/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/settings/app03_an/config.py b/docs_src/settings/app03_py39/config.py
similarity index 100%
rename from docs_src/settings/app03_an/config.py
rename to docs_src/settings/app03_py39/config.py
diff --git a/docs_src/settings/app03_py39/config_pv1.py b/docs_src/settings/app03_py39/config_pv1.py
new file mode 100644
index 000000000..7ae66ef77
--- /dev/null
+++ b/docs_src/settings/app03_py39/config_pv1.py
@@ -0,0 +1,10 @@
+from pydantic.v1 import BaseSettings
+
+
+class Settings(BaseSettings):
+ app_name: str = "Awesome API"
+ admin_email: str
+ items_per_user: int = 50
+
+ class Config:
+ env_file = ".env"
diff --git a/docs_src/settings/app03/main.py b/docs_src/settings/app03_py39/main.py
similarity index 100%
rename from docs_src/settings/app03/main.py
rename to docs_src/settings/app03_py39/main.py
diff --git a/docs_src/settings/tutorial001_pv1.py b/docs_src/settings/tutorial001_pv1.py
deleted file mode 100644
index 0cfd1b663..000000000
--- a/docs_src/settings/tutorial001_pv1.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from fastapi import FastAPI
-from pydantic import BaseSettings
-
-
-class Settings(BaseSettings):
- app_name: str = "Awesome API"
- admin_email: str
- items_per_user: int = 50
-
-
-settings = Settings()
-app = FastAPI()
-
-
-@app.get("/info")
-async def info():
- return {
- "app_name": settings.app_name,
- "admin_email": settings.admin_email,
- "items_per_user": settings.items_per_user,
- }
diff --git a/docs_src/settings/tutorial001_pv1_py39.py b/docs_src/settings/tutorial001_pv1_py39.py
new file mode 100644
index 000000000..20ad2bbf6
--- /dev/null
+++ b/docs_src/settings/tutorial001_pv1_py39.py
@@ -0,0 +1,21 @@
+from fastapi import FastAPI
+from pydantic.v1 import BaseSettings
+
+
+class Settings(BaseSettings):
+ app_name: str = "Awesome API"
+ admin_email: str
+ items_per_user: int = 50
+
+
+settings = Settings()
+app = FastAPI()
+
+
+@app.get("/info")
+async def info():
+ return {
+ "app_name": settings.app_name,
+ "admin_email": settings.admin_email,
+ "items_per_user": settings.items_per_user,
+ }
diff --git a/docs_src/settings/tutorial001.py b/docs_src/settings/tutorial001_py39.py
similarity index 100%
rename from docs_src/settings/tutorial001.py
rename to docs_src/settings/tutorial001_py39.py
diff --git a/docs_src/sql_databases/tutorial001.py b/docs_src/sql_databases/tutorial001.py
deleted file mode 100644
index be86ec0ee..000000000
--- a/docs_src/sql_databases/tutorial001.py
+++ /dev/null
@@ -1,71 +0,0 @@
-from typing import List, Union
-
-from fastapi import Depends, FastAPI, HTTPException, Query
-from sqlmodel import Field, Session, SQLModel, create_engine, select
-
-
-class Hero(SQLModel, table=True):
- id: Union[int, None] = Field(default=None, primary_key=True)
- name: str = Field(index=True)
- age: Union[int, None] = Field(default=None, index=True)
- secret_name: str
-
-
-sqlite_file_name = "database.db"
-sqlite_url = f"sqlite:///{sqlite_file_name}"
-
-connect_args = {"check_same_thread": False}
-engine = create_engine(sqlite_url, connect_args=connect_args)
-
-
-def create_db_and_tables():
- SQLModel.metadata.create_all(engine)
-
-
-def get_session():
- with Session(engine) as session:
- yield session
-
-
-app = FastAPI()
-
-
-@app.on_event("startup")
-def on_startup():
- create_db_and_tables()
-
-
-@app.post("/heroes/")
-def create_hero(hero: Hero, session: Session = Depends(get_session)) -> Hero:
- session.add(hero)
- session.commit()
- session.refresh(hero)
- return hero
-
-
-@app.get("/heroes/")
-def read_heroes(
- session: Session = Depends(get_session),
- offset: int = 0,
- limit: int = Query(default=100, le=100),
-) -> List[Hero]:
- heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()
- return heroes
-
-
-@app.get("/heroes/{hero_id}")
-def read_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero:
- hero = session.get(Hero, hero_id)
- if not hero:
- raise HTTPException(status_code=404, detail="Hero not found")
- return hero
-
-
-@app.delete("/heroes/{hero_id}")
-def delete_hero(hero_id: int, session: Session = Depends(get_session)):
- hero = session.get(Hero, hero_id)
- if not hero:
- raise HTTPException(status_code=404, detail="Hero not found")
- session.delete(hero)
- session.commit()
- return {"ok": True}
diff --git a/docs_src/sql_databases/tutorial001_an.py b/docs_src/sql_databases/tutorial001_an.py
deleted file mode 100644
index 8c000d31c..000000000
--- a/docs_src/sql_databases/tutorial001_an.py
+++ /dev/null
@@ -1,74 +0,0 @@
-from typing import List, Union
-
-from fastapi import Depends, FastAPI, HTTPException, Query
-from sqlmodel import Field, Session, SQLModel, create_engine, select
-from typing_extensions import Annotated
-
-
-class Hero(SQLModel, table=True):
- id: Union[int, None] = Field(default=None, primary_key=True)
- name: str = Field(index=True)
- age: Union[int, None] = Field(default=None, index=True)
- secret_name: str
-
-
-sqlite_file_name = "database.db"
-sqlite_url = f"sqlite:///{sqlite_file_name}"
-
-connect_args = {"check_same_thread": False}
-engine = create_engine(sqlite_url, connect_args=connect_args)
-
-
-def create_db_and_tables():
- SQLModel.metadata.create_all(engine)
-
-
-def get_session():
- with Session(engine) as session:
- yield session
-
-
-SessionDep = Annotated[Session, Depends(get_session)]
-
-app = FastAPI()
-
-
-@app.on_event("startup")
-def on_startup():
- create_db_and_tables()
-
-
-@app.post("/heroes/")
-def create_hero(hero: Hero, session: SessionDep) -> Hero:
- session.add(hero)
- session.commit()
- session.refresh(hero)
- return hero
-
-
-@app.get("/heroes/")
-def read_heroes(
- session: SessionDep,
- offset: int = 0,
- limit: Annotated[int, Query(le=100)] = 100,
-) -> List[Hero]:
- heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()
- return heroes
-
-
-@app.get("/heroes/{hero_id}")
-def read_hero(hero_id: int, session: SessionDep) -> Hero:
- hero = session.get(Hero, hero_id)
- if not hero:
- raise HTTPException(status_code=404, detail="Hero not found")
- return hero
-
-
-@app.delete("/heroes/{hero_id}")
-def delete_hero(hero_id: int, session: SessionDep):
- hero = session.get(Hero, hero_id)
- if not hero:
- raise HTTPException(status_code=404, detail="Hero not found")
- session.delete(hero)
- session.commit()
- return {"ok": True}
diff --git a/docs_src/sql_databases/tutorial002.py b/docs_src/sql_databases/tutorial002.py
deleted file mode 100644
index 4350d19c6..000000000
--- a/docs_src/sql_databases/tutorial002.py
+++ /dev/null
@@ -1,104 +0,0 @@
-from typing import List, Union
-
-from fastapi import Depends, FastAPI, HTTPException, Query
-from sqlmodel import Field, Session, SQLModel, create_engine, select
-
-
-class HeroBase(SQLModel):
- name: str = Field(index=True)
- age: Union[int, None] = Field(default=None, index=True)
-
-
-class Hero(HeroBase, table=True):
- id: Union[int, None] = Field(default=None, primary_key=True)
- secret_name: str
-
-
-class HeroPublic(HeroBase):
- id: int
-
-
-class HeroCreate(HeroBase):
- secret_name: str
-
-
-class HeroUpdate(HeroBase):
- name: Union[str, None] = None
- age: Union[int, None] = None
- secret_name: Union[str, None] = None
-
-
-sqlite_file_name = "database.db"
-sqlite_url = f"sqlite:///{sqlite_file_name}"
-
-connect_args = {"check_same_thread": False}
-engine = create_engine(sqlite_url, connect_args=connect_args)
-
-
-def create_db_and_tables():
- SQLModel.metadata.create_all(engine)
-
-
-def get_session():
- with Session(engine) as session:
- yield session
-
-
-app = FastAPI()
-
-
-@app.on_event("startup")
-def on_startup():
- create_db_and_tables()
-
-
-@app.post("/heroes/", response_model=HeroPublic)
-def create_hero(hero: HeroCreate, session: Session = Depends(get_session)):
- db_hero = Hero.model_validate(hero)
- session.add(db_hero)
- session.commit()
- session.refresh(db_hero)
- return db_hero
-
-
-@app.get("/heroes/", response_model=List[HeroPublic])
-def read_heroes(
- session: Session = Depends(get_session),
- offset: int = 0,
- limit: int = Query(default=100, le=100),
-):
- heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()
- return heroes
-
-
-@app.get("/heroes/{hero_id}", response_model=HeroPublic)
-def read_hero(hero_id: int, session: Session = Depends(get_session)):
- hero = session.get(Hero, hero_id)
- if not hero:
- raise HTTPException(status_code=404, detail="Hero not found")
- return hero
-
-
-@app.patch("/heroes/{hero_id}", response_model=HeroPublic)
-def update_hero(
- hero_id: int, hero: HeroUpdate, session: Session = Depends(get_session)
-):
- hero_db = session.get(Hero, hero_id)
- if not hero_db:
- raise HTTPException(status_code=404, detail="Hero not found")
- hero_data = hero.model_dump(exclude_unset=True)
- hero_db.sqlmodel_update(hero_data)
- session.add(hero_db)
- session.commit()
- session.refresh(hero_db)
- return hero_db
-
-
-@app.delete("/heroes/{hero_id}")
-def delete_hero(hero_id: int, session: Session = Depends(get_session)):
- hero = session.get(Hero, hero_id)
- if not hero:
- raise HTTPException(status_code=404, detail="Hero not found")
- session.delete(hero)
- session.commit()
- return {"ok": True}
diff --git a/docs_src/sql_databases/tutorial002_an.py b/docs_src/sql_databases/tutorial002_an.py
deleted file mode 100644
index 15e3d7c3a..000000000
--- a/docs_src/sql_databases/tutorial002_an.py
+++ /dev/null
@@ -1,104 +0,0 @@
-from typing import List, Union
-
-from fastapi import Depends, FastAPI, HTTPException, Query
-from sqlmodel import Field, Session, SQLModel, create_engine, select
-from typing_extensions import Annotated
-
-
-class HeroBase(SQLModel):
- name: str = Field(index=True)
- age: Union[int, None] = Field(default=None, index=True)
-
-
-class Hero(HeroBase, table=True):
- id: Union[int, None] = Field(default=None, primary_key=True)
- secret_name: str
-
-
-class HeroPublic(HeroBase):
- id: int
-
-
-class HeroCreate(HeroBase):
- secret_name: str
-
-
-class HeroUpdate(HeroBase):
- name: Union[str, None] = None
- age: Union[int, None] = None
- secret_name: Union[str, None] = None
-
-
-sqlite_file_name = "database.db"
-sqlite_url = f"sqlite:///{sqlite_file_name}"
-
-connect_args = {"check_same_thread": False}
-engine = create_engine(sqlite_url, connect_args=connect_args)
-
-
-def create_db_and_tables():
- SQLModel.metadata.create_all(engine)
-
-
-def get_session():
- with Session(engine) as session:
- yield session
-
-
-SessionDep = Annotated[Session, Depends(get_session)]
-app = FastAPI()
-
-
-@app.on_event("startup")
-def on_startup():
- create_db_and_tables()
-
-
-@app.post("/heroes/", response_model=HeroPublic)
-def create_hero(hero: HeroCreate, session: SessionDep):
- db_hero = Hero.model_validate(hero)
- session.add(db_hero)
- session.commit()
- session.refresh(db_hero)
- return db_hero
-
-
-@app.get("/heroes/", response_model=List[HeroPublic])
-def read_heroes(
- session: SessionDep,
- offset: int = 0,
- limit: Annotated[int, Query(le=100)] = 100,
-):
- heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()
- return heroes
-
-
-@app.get("/heroes/{hero_id}", response_model=HeroPublic)
-def read_hero(hero_id: int, session: SessionDep):
- hero = session.get(Hero, hero_id)
- if not hero:
- raise HTTPException(status_code=404, detail="Hero not found")
- return hero
-
-
-@app.patch("/heroes/{hero_id}", response_model=HeroPublic)
-def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep):
- hero_db = session.get(Hero, hero_id)
- if not hero_db:
- raise HTTPException(status_code=404, detail="Hero not found")
- hero_data = hero.model_dump(exclude_unset=True)
- hero_db.sqlmodel_update(hero_data)
- session.add(hero_db)
- session.commit()
- session.refresh(hero_db)
- return hero_db
-
-
-@app.delete("/heroes/{hero_id}")
-def delete_hero(hero_id: int, session: SessionDep):
- hero = session.get(Hero, hero_id)
- if not hero:
- raise HTTPException(status_code=404, detail="Hero not found")
- session.delete(hero)
- session.commit()
- return {"ok": True}
diff --git a/docs_src/static_files/__init__.py b/docs_src/static_files/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/static_files/tutorial001.py b/docs_src/static_files/tutorial001_py39.py
similarity index 100%
rename from docs_src/static_files/tutorial001.py
rename to docs_src/static_files/tutorial001_py39.py
diff --git a/docs_src/sub_applications/__init__.py b/docs_src/sub_applications/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/sub_applications/tutorial001.py b/docs_src/sub_applications/tutorial001_py39.py
similarity index 100%
rename from docs_src/sub_applications/tutorial001.py
rename to docs_src/sub_applications/tutorial001_py39.py
diff --git a/docs_src/templates/__init__.py b/docs_src/templates/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/templates/static/__init__.py b/docs_src/templates/static/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/templates/templates/__init__.py b/docs_src/templates/templates/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/templates/tutorial001.py b/docs_src/templates/tutorial001_py39.py
similarity index 100%
rename from docs_src/templates/tutorial001.py
rename to docs_src/templates/tutorial001_py39.py
diff --git a/docs_src/using_request_directly/__init__.py b/docs_src/using_request_directly/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/using_request_directly/tutorial001.py b/docs_src/using_request_directly/tutorial001_py39.py
similarity index 100%
rename from docs_src/using_request_directly/tutorial001.py
rename to docs_src/using_request_directly/tutorial001_py39.py
diff --git a/docs_src/websockets/tutorial001.py b/docs_src/websockets/tutorial001_py39.py
similarity index 100%
rename from docs_src/websockets/tutorial001.py
rename to docs_src/websockets/tutorial001_py39.py
diff --git a/docs_src/websockets/tutorial002_an.py b/docs_src/websockets/tutorial002_an.py
deleted file mode 100644
index c838fbd30..000000000
--- a/docs_src/websockets/tutorial002_an.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from typing import Union
-
-from fastapi import (
- Cookie,
- Depends,
- FastAPI,
- Query,
- WebSocket,
- WebSocketException,
- status,
-)
-from fastapi.responses import HTMLResponse
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-html = """
-
-
-
- .*?)(?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 56ffb9d36..20bf1c168 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*````")
+
+
+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()
-@lru_cache
-def is_mkdocs_insiders() -> bool:
- version = metadata.version("mkdocs-material")
- return "insiders" in version
+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]:
+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:
- """
- Verify main mkdocs.yml content to make sure it uses the latest language names.
- """
- 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:
+def ensure_non_translated() -> None:
"""
- Verify there are no files in the non translatable pages.
+ Ensure 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 000000000..d45ab8eb0
--- /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 e4a49165c..4b781270a 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,6 +132,15 @@ 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 metadata["social"]["cards_layout_options"]["title"] to clean title (without
+ # permalink)
+ title = page.title
+ clean_title = title.split("{ #")[0]
+ if clean_title:
+ page.meta.setdefault("social", {})
+ page.meta["social"].setdefault("cards_layout_options", {})
+ page.meta["social"]["cards_layout_options"]["title"] = clean_title
+
if isinstance(page.file, EnFile):
for excluded_section in non_translated_sections:
if page.file.src_path.startswith(excluded_section):
diff --git a/scripts/notify_translations.py b/scripts/notify_translations.py
index c300624db..74cdf0dff 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 7418b4595..207ab4649 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 45e02bd62..fdcabc737 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-cov-html.sh b/scripts/test-cov-html.sh
index 517ac6422..f87f906dc 100755
--- a/scripts/test-cov-html.sh
+++ b/scripts/test-cov-html.sh
@@ -4,6 +4,4 @@ set -e
set -x
bash scripts/test.sh ${@}
-coverage combine
-coverage report
-coverage html
+bash scripts/coverage.sh
diff --git a/scripts/test.sh b/scripts/test.sh
index 7d17add8f..0bffcd1cd 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 000000000..006f519f4
--- /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 000000000..cad20e2c7
--- /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 000000000..f46070156
--- /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 000000000..e08baa70b
--- /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 000000000..cacb9546d
--- /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 000000000..d03dca53e
--- /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 000000000..e77050cc9
--- /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 000000000..918cb883f
--- /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 000000000..88aed900d
--- /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 000000000..a7fbb39f5
--- /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 000000000..9cdbe8323
--- /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 000000000..8b80c70f3
--- /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 000000000..ad5767c1c
--- /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 000000000..85d73e3b4
--- /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 000000000..593da0b32
--- /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 000000000..c1ad94d27
--- /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 000000000..07eaf2c23
--- /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 000000000..1020b890c
--- /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 000000000..69cd3f3fd
--- /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 000000000..c922d7b13
--- /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 000000000..b33f36e77
--- /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 000000000..cc7bcadda
--- /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 000000000..878e16fb5
--- /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 000000000..feefd7770
--- /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 000000000..ad53a660c
--- /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 000000000..9c517c9bb
--- /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 000000000..8a308728b
--- /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 000000000..99d27db95
--- /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 000000000..c4d034617
--- /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 000000000..4c4d104cf
--- /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 000000000..bac40242b
--- /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 000000000..e2b36b688
--- /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 000000000..11dcea154
--- /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 000000000..9d965b0d6
--- /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 000000000..804a7e60a
--- /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 000000000..9cbedb676
--- /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 000000000..4e9e6ccf7
--- /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 000000000..c72e01254
--- /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 bc1497751..b7afc0864 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 ede101e8f..eba4ad6a2 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 000000000..3e1f42d51
--- /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 000000000..e69de29bb
diff --git a/tests/benchmarks/test_general_performance.py b/tests/benchmarks/test_general_performance.py
new file mode 100644
index 000000000..87add6d17
--- /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 000000000..52a0d4a70
--- /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 2f1d61711..7edb16c61 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 be14d10ed..262236640 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 de59e48ce..063297a3f 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 2ad575455..376d7714e 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": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
+ },
+ "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"
+ }
}
- }
+ },
},
},
- },
- "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"
+ "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",
},
- "description": "Bad Request",
- },
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {"schema": {}}
+ "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"},
- }
+ },
+ "components": {
+ "schemas": {
+ "CustomModel": {
+ "title": "CustomModel",
+ "required": ["a"],
+ "type": "object",
+ "properties": {"a": {"title": "A", "type": "integer"}},
},
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"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"},
},
- "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 9fec5c96d..8724e5ecb 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 68753561c..fecc3ee16 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 9e855fdf8..083a024af 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 8a31442eb..44d49b781 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 473d33e52..39f6f83b2 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 8f1b0a18d..001586ff7 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 000000000..481acc3bf
--- /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 43c686489..0b5600f8f 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -1,72 +1,32 @@
-from typing import Any, Dict, List, Union
+from typing import Union
from fastapi import FastAPI, UploadFile
from fastapi._compat import (
- ModelField,
Undefined,
- _get_model_config,
- get_cached_model_fields,
- get_model_fields,
- is_bytes_sequence_annotation,
- is_scalar_field,
is_uploadfile_sequence_annotation,
)
+from fastapi._compat.shared import is_bytes_sequence_annotation
from fastapi.testclient import TestClient
-from pydantic import BaseConfig, BaseModel, ConfigDict
+from pydantic import BaseModel, ConfigDict
from pydantic.fields import FieldInfo
-from .utils import needs_pydanticv1, needs_pydanticv2
+from .utils import needs_py310
-@needs_pydanticv2
def test_model_field_default_required():
+ from fastapi._compat import v2
+
# For coverage
field_info = FieldInfo(annotation=str)
- field = ModelField(name="foo", field_info=field_info)
+ field = v2.ModelField(name="foo", field_info=field_info)
assert field.default is Undefined
-@needs_pydanticv1
-def test_upload_file_dummy_with_info_plain_validator_function():
- # For coverage
- assert UploadFile.__get_pydantic_core_schema__(str, lambda x: None) == {}
-
-
-@needs_pydanticv1
-def test_union_scalar_list():
- # For coverage
- # TODO: there might not be a current valid code path that uses this, it would
- # potentially enable query parameters defined as both a scalar and a list
- # but that would require more refactors, also not sure it's really useful
- from fastapi._compat import is_pv1_scalar_field
-
- field_info = FieldInfo()
- field = ModelField(
- name="foo",
- field_info=field_info,
- type_=Union[str, List[int]],
- class_validators={},
- model_config=BaseConfig,
- )
- assert not is_pv1_scalar_field(field)
-
-
-@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)
@@ -80,7 +40,6 @@ def test_complex():
assert response2.json() == [1, 2]
-@needs_pydanticv2
def test_propagates_pydantic2_model_config():
app = FastAPI()
@@ -100,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,
@@ -130,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():
@@ -138,28 +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]])
-def test_is_pv1_scalar_field():
- # For coverage
- class Model(BaseModel):
- foo: Union[str, Dict[str, Any]]
+def test_serialize_sequence_value_with_optional_list():
+ """Test that serialize_sequence_value handles optional lists correctly."""
+ from fastapi._compat import v2
- fields = 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)
-def test_get_model_fields_cached():
- class Model(BaseModel):
- foo: str
+@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
+
+ 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 = get_model_fields(Model)
- non_cached_fields2 = 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
+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
- assert non_cached_fields is not non_cached_fields2
- assert cached_fields is cached_fields2
+ 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_computed_fields.py b/tests/test_computed_fields.py
index a1b412168..e7f969f7c 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 d890291b1..60b795e9b 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 7e57d525c..29a70cae0 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 3aa77c0b1..f154ede02 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 b560dc36f..b56140277 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 7e1c8822b..cbadff8f8 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 7c323c338..0fdf697b6 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 0233492e6..95ff3e9d9 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 02c10458c..5a8993474 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 076802df8..0c2e5594b 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 8e8d07c2d..a8658e03b 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 154937fa0..e25db624d 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
new file mode 100644
index 000000000..1774196fe
--- /dev/null
+++ b/tests/test_dependency_paramless.py
@@ -0,0 +1,77 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, HTTPException, Security
+from fastapi.security import (
+ OAuth2PasswordBearer,
+ SecurityScopes,
+)
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+def process_auth(
+ credentials: Annotated[Union[str, None], Security(oauth2_scheme)],
+ security_scopes: SecurityScopes,
+):
+ # This is an incorrect way of using it, this is not checking if the scopes are
+ # provided by the token, only if the endpoint is requesting them, but the test
+ # here is just to check if FastAPI is indeed registering and passing the scopes
+ # correctly when using Security with parameterless dependencies.
+ if "a" not in security_scopes.scopes or "b" not in security_scopes.scopes:
+ raise HTTPException(detail="a or b not in scopes", status_code=401)
+ return {"token": credentials, "scopes": security_scopes.scopes}
+
+
+@app.get("/get-credentials")
+def get_credentials(
+ credentials: Annotated[dict, Security(process_auth, scopes=["a", "b"])],
+):
+ return credentials
+
+
+@app.get(
+ "/parameterless-with-scopes",
+ dependencies=[Security(process_auth, scopes=["a", "b"])],
+)
+def get_parameterless_with_scopes():
+ return {"status": "ok"}
+
+
+@app.get(
+ "/parameterless-without-scopes",
+ dependencies=[Security(process_auth)],
+)
+def get_parameterless_without_scopes():
+ return {"status": "ok"}
+
+
+client = TestClient(app)
+
+
+def test_get_credentials():
+ response = client.get("/get-credentials", headers={"authorization": "Bearer token"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"token": "token", "scopes": ["a", "b"]}
+
+
+def test_parameterless_with_scopes():
+ response = client.get(
+ "/parameterless-with-scopes", headers={"authorization": "Bearer token"}
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"status": "ok"}
+
+
+def test_parameterless_without_scopes():
+ response = client.get(
+ "/parameterless-without-scopes", headers={"authorization": "Bearer token"}
+ )
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "a or b not in scopes"}
+
+
+def test_call_get_parameterless_without_scopes_for_coverage():
+ assert get_parameterless_without_scopes() == {"status": "ok"}
diff --git a/tests/test_dependency_partial.py b/tests/test_dependency_partial.py
new file mode 100644
index 000000000..05a3cffa5
--- /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 b89d82db4..14b65c777 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 000000000..a4044112a
--- /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
new file mode 100644
index 000000000..f3fc3cc94
--- /dev/null
+++ b/tests/test_dependency_yield_scope.py
@@ -0,0 +1,245 @@
+import json
+from typing import Annotated, Any
+
+import pytest
+from fastapi import APIRouter, Depends, FastAPI, HTTPException
+from fastapi.exceptions import FastAPIError
+from fastapi.responses import StreamingResponse
+from fastapi.testclient import TestClient
+
+
+class Session:
+ def __init__(self) -> None:
+ self.open = True
+
+
+def dep_session() -> Any:
+ s = Session()
+ yield s
+ 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)]
+
+
+class NamedSession:
+ def __init__(self, name: str = "default") -> None:
+ self.name = name
+ self.open = True
+
+
+def get_named_session(session: SessionRequestDep, session_b: SessionDefaultDep) -> Any:
+ assert session is session_b
+ named_session = NamedSession(name="named")
+ yield named_session, session_b
+ named_session.open = False
+
+
+NamedSessionsDep = Annotated[tuple[NamedSession, Session], Depends(get_named_session)]
+
+
+def get_named_func_session(session: SessionFuncDep) -> Any:
+ named_session = NamedSession(name="named")
+ yield named_session, session
+ named_session.open = False
+
+
+def get_named_regular_func_session(session: SessionFuncDep) -> Any:
+ named_session = NamedSession(name="named")
+ return named_session, session
+
+
+BrokenSessionsDep = Annotated[
+ tuple[NamedSession, Session], Depends(get_named_func_session)
+]
+NamedSessionsFuncDep = Annotated[
+ tuple[NamedSession, Session], Depends(get_named_func_session, scope="function")
+]
+
+RegularSessionsDep = Annotated[
+ 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")
+def function_scope(session: SessionFuncDep) -> Any:
+ def iter_data():
+ yield json.dumps({"is_open": session.open})
+
+ return StreamingResponse(iter_data())
+
+
+@app.get("/request-scope")
+def request_scope(session: SessionRequestDep) -> Any:
+ def iter_data():
+ yield json.dumps({"is_open": session.open})
+
+ return StreamingResponse(iter_data())
+
+
+@app.get("/two-scopes")
+def get_stream_session(
+ function_session: SessionFuncDep, request_session: SessionRequestDep
+) -> Any:
+ def iter_data():
+ yield json.dumps(
+ {"func_is_open": function_session.open, "req_is_open": request_session.open}
+ )
+
+ return StreamingResponse(iter_data())
+
+
+@app.get("/sub")
+def get_sub(sessions: NamedSessionsDep) -> Any:
+ def iter_data():
+ yield json.dumps(
+ {"named_session_open": sessions[0].open, "session_open": sessions[1].open}
+ )
+
+ return StreamingResponse(iter_data())
+
+
+@app.get("/named-function-scope")
+def get_named_function_scope(sessions: NamedSessionsFuncDep) -> Any:
+ def iter_data():
+ yield json.dumps(
+ {"named_session_open": sessions[0].open, "session_open": sessions[1].open}
+ )
+
+ return StreamingResponse(iter_data())
+
+
+@app.get("/regular-function-scope")
+def get_regular_function_scope(sessions: RegularSessionsDep) -> Any:
+ def iter_data():
+ yield json.dumps(
+ {"named_session_open": sessions[0].open, "session_open": sessions[1].open}
+ )
+
+ 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)
+
+
+def test_function_scope() -> None:
+ response = client.get("/function-scope")
+ assert response.status_code == 200
+ data = response.json()
+ assert data["is_open"] is False
+
+
+def test_request_scope() -> None:
+ response = client.get("/request-scope")
+ assert response.status_code == 200
+ data = response.json()
+ assert data["is_open"] is True
+
+
+def test_two_scopes() -> None:
+ response = client.get("/two-scopes")
+ assert response.status_code == 200
+ data = response.json()
+ assert data["func_is_open"] is False
+ assert data["req_is_open"] is True
+
+
+def test_sub() -> None:
+ response = client.get("/sub")
+ assert response.status_code == 200
+ data = response.json()
+ assert data["named_session_open"] is True
+ assert data["session_open"] is True
+
+
+def test_broken_scope() -> None:
+ with pytest.raises(
+ FastAPIError,
+ match='The dependency "get_named_func_session" has a scope of "request", it cannot depend on dependencies with scope "function"',
+ ):
+
+ @app.get("/broken-scope")
+ def get_broken(sessions: BrokenSessionsDep) -> Any: # pragma: no cover
+ pass
+
+
+def test_named_function_scope() -> None:
+ response = client.get("/named-function-scope")
+ assert response.status_code == 200
+ data = response.json()
+ assert data["named_session_open"] is False
+ assert data["session_open"] is False
+
+
+def test_regular_function_scope() -> None:
+ response = client.get("/regular-function-scope")
+ assert response.status_code == 200
+ 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
new file mode 100644
index 000000000..dbe35e576
--- /dev/null
+++ b/tests/test_dependency_yield_scope_websockets.py
@@ -0,0 +1,200 @@
+from contextvars import ContextVar
+from typing import Annotated, Any
+
+import pytest
+from fastapi import Depends, FastAPI, WebSocket
+from fastapi.exceptions import FastAPIError
+from fastapi.testclient import TestClient
+
+global_context: ContextVar[dict[str, Any]] = ContextVar("global_context", default={}) # noqa: B039
+
+
+class Session:
+ def __init__(self) -> None:
+ self.open = True
+
+
+async def dep_session() -> Any:
+ s = Session()
+ yield s
+ s.open = False
+ global_state = global_context.get()
+ global_state["session_closed"] = True
+
+
+SessionFuncDep = Annotated[Session, Depends(dep_session, scope="function")]
+SessionRequestDep = Annotated[Session, Depends(dep_session, scope="request")]
+SessionDefaultDep = Annotated[Session, Depends(dep_session)]
+
+
+class NamedSession:
+ def __init__(self, name: str = "default") -> None:
+ self.name = name
+ self.open = True
+
+
+def get_named_session(session: SessionRequestDep, session_b: SessionDefaultDep) -> Any:
+ assert session is session_b
+ named_session = NamedSession(name="named")
+ yield named_session, session_b
+ named_session.open = False
+ global_state = global_context.get()
+ global_state["named_session_closed"] = True
+
+
+NamedSessionsDep = Annotated[tuple[NamedSession, Session], Depends(get_named_session)]
+
+
+def get_named_func_session(session: SessionFuncDep) -> Any:
+ named_session = NamedSession(name="named")
+ yield named_session, session
+ named_session.open = False
+ global_state = global_context.get()
+ global_state["named_func_session_closed"] = True
+
+
+def get_named_regular_func_session(session: SessionFuncDep) -> Any:
+ named_session = NamedSession(name="named")
+ return named_session, session
+
+
+BrokenSessionsDep = Annotated[
+ tuple[NamedSession, Session], Depends(get_named_func_session)
+]
+NamedSessionsFuncDep = Annotated[
+ tuple[NamedSession, Session], Depends(get_named_func_session, scope="function")
+]
+
+RegularSessionsDep = Annotated[
+ tuple[NamedSession, Session], Depends(get_named_regular_func_session)
+]
+
+app = FastAPI()
+
+
+@app.websocket("/function-scope")
+async def function_scope(websocket: WebSocket, session: SessionFuncDep) -> Any:
+ await websocket.accept()
+ await websocket.send_json({"is_open": session.open})
+
+
+@app.websocket("/request-scope")
+async def request_scope(websocket: WebSocket, session: SessionRequestDep) -> Any:
+ await websocket.accept()
+ await websocket.send_json({"is_open": session.open})
+
+
+@app.websocket("/two-scopes")
+async def get_stream_session(
+ websocket: WebSocket,
+ function_session: SessionFuncDep,
+ request_session: SessionRequestDep,
+) -> Any:
+ await websocket.accept()
+ await websocket.send_json(
+ {"func_is_open": function_session.open, "req_is_open": request_session.open}
+ )
+
+
+@app.websocket("/sub")
+async def get_sub(websocket: WebSocket, sessions: NamedSessionsDep) -> Any:
+ await websocket.accept()
+ await websocket.send_json(
+ {"named_session_open": sessions[0].open, "session_open": sessions[1].open}
+ )
+
+
+@app.websocket("/named-function-scope")
+async def get_named_function_scope(
+ websocket: WebSocket, sessions: NamedSessionsFuncDep
+) -> Any:
+ await websocket.accept()
+ await websocket.send_json(
+ {"named_session_open": sessions[0].open, "session_open": sessions[1].open}
+ )
+
+
+@app.websocket("/regular-function-scope")
+async def get_regular_function_scope(
+ websocket: WebSocket, sessions: RegularSessionsDep
+) -> Any:
+ await websocket.accept()
+ await websocket.send_json(
+ {"named_session_open": sessions[0].open, "session_open": sessions[1].open}
+ )
+
+
+client = TestClient(app)
+
+
+def test_function_scope() -> None:
+ global_context.set({})
+ global_state = global_context.get()
+ with client.websocket_connect("/function-scope") as websocket:
+ data = websocket.receive_json()
+ assert data["is_open"] is True
+ assert global_state["session_closed"] is True
+
+
+def test_request_scope() -> None:
+ global_context.set({})
+ global_state = global_context.get()
+ with client.websocket_connect("/request-scope") as websocket:
+ data = websocket.receive_json()
+ assert data["is_open"] is True
+ assert global_state["session_closed"] is True
+
+
+def test_two_scopes() -> None:
+ global_context.set({})
+ global_state = global_context.get()
+ with client.websocket_connect("/two-scopes") as websocket:
+ data = websocket.receive_json()
+ assert data["func_is_open"] is True
+ assert data["req_is_open"] is True
+ assert global_state["session_closed"] is True
+
+
+def test_sub() -> None:
+ global_context.set({})
+ global_state = global_context.get()
+ with client.websocket_connect("/sub") as websocket:
+ data = websocket.receive_json()
+ assert data["named_session_open"] is True
+ assert data["session_open"] is True
+ assert global_state["session_closed"] is True
+ assert global_state["named_session_closed"] is True
+
+
+def test_broken_scope() -> None:
+ with pytest.raises(
+ FastAPIError,
+ match='The dependency "get_named_func_session" has a scope of "request", it cannot depend on dependencies with scope "function"',
+ ):
+
+ @app.websocket("/broken-scope")
+ async def get_broken(
+ websocket: WebSocket, sessions: BrokenSessionsDep
+ ) -> Any: # pragma: no cover
+ pass
+
+
+def test_named_function_scope() -> None:
+ global_context.set({})
+ global_state = global_context.get()
+ with client.websocket_connect("/named-function-scope") as websocket:
+ data = websocket.receive_json()
+ assert data["named_session_open"] is True
+ assert data["session_open"] is True
+ assert global_state["session_closed"] is True
+ assert global_state["named_func_session_closed"] is True
+
+
+def test_regular_function_scope() -> None:
+ global_context.set({})
+ global_state = global_context.get()
+ with client.websocket_connect("/regular-function-scope") as websocket:
+ data = websocket.receive_json()
+ assert data["named_session_open"] is True
+ assert data["session_open"] is True
+ assert global_state["session_closed"] is True
diff --git a/tests/test_depends_hashable.py b/tests/test_depends_hashable.py
new file mode 100644
index 000000000..d57f2726e
--- /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 bd16fe925..45734ec28 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 a5c10778a..78a49a1fb 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 cb9a31d31..75290b60c 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 657e8c5d1..000000000
--- 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/test_filter_pydantic_sub_model_pv1.py b/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py
deleted file mode 100644
index 48732dbf0..000000000
--- a/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py
+++ /dev/null
@@ -1,130 +0,0 @@
-import pytest
-from fastapi.exceptions import ResponseValidationError
-from fastapi.testclient import TestClient
-
-from ..utils import needs_pydanticv1
-
-
-@pytest.fixture(name="client")
-def get_client():
- from .app_pv1 import app
-
- client = TestClient(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"},
- }
-
-
-@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
- 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"}
- }
- },
- },
- "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", "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"],
- "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_filter_pydantic_sub_model_pv2.py b/tests/test_filter_pydantic_sub_model_pv2.py
index 2e2c26ddc..fc5876410 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"},
- }
+ },
+ "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(
- {
+ "ModelA": {
+ "title": "ModelA",
+ "required": ["name", "foo"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- |
- # TODO remove when deprecating Pydantic v1
- IsDict({"title": "Description", "type": "string"}),
- "foo": {"$ref": "#/components/schemas/ModelB"},
+ },
+ "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"}]
+ "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"},
},
- "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 000000000..0b3eb8f2e
--- /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 880ab3820..7d03d2957 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 3bb951441..67f054b34 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 5aeec6636..62ebfbc96 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 5aa35320c..93b72ad24 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 f77195dc5..eb7939b69 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
+ class Facility(BaseModel):
+ id: str
+ address: Address
+ app = FastAPI()
-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)
+ client = TestClient(app)
+ return client
-@app.get("/facilities/{facility_id}")
-def get_facility(facility_id: str) -> Facility: ...
+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",
+ },
+ }
-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",
+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() == 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",
},
- "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"}
- }
+ "Facility": {
+ "properties": {
+ "address": {"$ref": "#/components/schemas/Address"},
+ "id": {"title": "Id", "type": "string"},
},
- "description": "Successful Response",
+ "required": ["id", "address"],
+ "title": "Facility",
+ "type": "object",
},
- "422": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "title": "Detail",
+ "type": "array",
}
},
- "description": "Validation Error",
+ "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",
},
- },
- "summary": "Get Facility",
- }
+ }
+ },
+ "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():
- """
- 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.
- """
- all_fields = fastapi.openapi.utils.get_fields_from_routes(app.routes)
-
- flat_models = fastapi._compat.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 = fastapi._compat.get_model_definitions(
- flat_models=SortedTypeSet(flat_models, sort_reversed=sort_reversed),
- model_name_map=model_name_map,
)
- assert models["Address"]["description"] == expected_address_description
diff --git a/tests/test_infer_param_optionality.py b/tests/test_infer_param_optionality.py
index e3d57bb42..147018996 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 fe9350f4e..8cf8952f9 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 d5fa53c1f..35c00363d 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 475786adb..2b8fd059e 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 447c5b4d6..4528dff44 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 0102f0f1a..4418c77cb 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",
- ),
- }
- ]
- }
-
-
-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
+ assert response.json() == snapshot(
{
- "detail": [
+ "item": [
{
- "ctx": {"limit_value": 0.0},
- "loc": ["body", 0, "age"],
- "msg": "ensure this value is greater than 0",
- "type": "value_error.number.not_gt",
+ "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() == {
+ "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,23 +136,21 @@ def test_openapi_schema():
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
- "age": IsDict(
- {
- "title": "Age",
- "anyOf": [
- {"exclusiveMinimum": 0.0, "type": "number"},
+ "age": {
+ "title": "Age",
+ "anyOf": [
+ {"exclusiveMinimum": 0.0, "type": "number"},
+ IsOneOf(
+ # pydantic < 2.12.0
{"type": "string"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Age",
- "exclusiveMinimum": 0.0,
- "type": "number",
- }
- ),
+ # 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 8162d986c..5df51ba18 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
new file mode 100644
index 000000000..131a3755e
--- /dev/null
+++ b/tests/test_no_schema_split.py
@@ -0,0 +1,174 @@
+# Test with parts from, and to verify the report in:
+# https://github.com/fastapi/fastapi/discussions/14177
+# Made an issue in:
+# https://github.com/fastapi/fastapi/issues/14247
+from enum import Enum
+
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+from pydantic import BaseModel, Field
+
+
+class MessageEventType(str, Enum):
+ alpha = "alpha"
+ beta = "beta"
+
+
+class MessageEvent(BaseModel):
+ event_type: MessageEventType = Field(default=MessageEventType.alpha)
+ output: str
+
+
+class MessageOutput(BaseModel):
+ body: str = ""
+ events: list[MessageEvent] = []
+
+
+class Message(BaseModel):
+ input: str
+ output: MessageOutput
+
+
+app = FastAPI(title="Minimal FastAPI App", version="1.0.0")
+
+
+@app.post("/messages", response_model=Message)
+async def create_message(input_message: str) -> Message:
+ return Message(
+ input=input_message,
+ output=MessageOutput(body=f"Processed: {input_message}"),
+ )
+
+
+client = TestClient(app)
+
+
+def test_create_message():
+ response = client.post("/messages", params={"input_message": "Hello"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "input": "Hello",
+ "output": {"body": "Processed: Hello", "events": []},
+ }
+
+
+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": "Minimal FastAPI App", "version": "1.0.0"},
+ "paths": {
+ "/messages": {
+ "post": {
+ "summary": "Create Message",
+ "operationId": "create_message_messages_post",
+ "parameters": [
+ {
+ "name": "input_message",
+ "in": "query",
+ "required": True,
+ "schema": {"type": "string", "title": "Input Message"},
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Message"
+ }
+ }
+ },
+ },
+ "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",
+ },
+ "Message": {
+ "properties": {
+ "input": {"type": "string", "title": "Input"},
+ "output": {"$ref": "#/components/schemas/MessageOutput"},
+ },
+ "type": "object",
+ "required": ["input", "output"],
+ "title": "Message",
+ },
+ "MessageEvent": {
+ "properties": {
+ "event_type": {
+ "$ref": "#/components/schemas/MessageEventType",
+ "default": "alpha",
+ },
+ "output": {"type": "string", "title": "Output"},
+ },
+ "type": "object",
+ "required": ["output"],
+ "title": "MessageEvent",
+ },
+ "MessageEventType": {
+ "type": "string",
+ "enum": ["alpha", "beta"],
+ "title": "MessageEventType",
+ },
+ "MessageOutput": {
+ "properties": {
+ "body": {"type": "string", "title": "Body", "default": ""},
+ "events": {
+ "items": {"$ref": "#/components/schemas/MessageEvent"},
+ "type": "array",
+ "title": "Events",
+ "default": [],
+ },
+ },
+ "type": "object",
+ "title": "MessageOutput",
+ },
+ "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_openapi_examples.py b/tests/test_openapi_examples.py
index b3f83ae23..bd0d55452 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 dc7147c71..084cb695d 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 a45ea20c8..98d978745 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 f7e045259..1891f0bde 100644
--- a/tests/test_openapi_separate_input_output_schemas.py
+++ b/tests/test_openapi_separate_input_output_schemas.py
@@ -1,26 +1,32 @@
-from typing import List, Optional
+from typing import Optional
from fastapi import FastAPI
from fastapi.testclient import TestClient
-from pydantic import BaseModel
-
-from .utils import PYDANTIC_V2, needs_pydanticv2
+from inline_snapshot import snapshot
+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:
@@ -31,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",
@@ -45,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
@@ -130,225 +142,302 @@ 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")
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": {
- "items": {
- "$ref": "#/components/schemas/Item-Output"
- },
- "type": "array",
- "title": "Response Read Items Items Get",
+ assert response.json() == snapshot(
+ {
+ "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": {
+ "items": {
+ "$ref": "#/components/schemas/Item-Output"
+ },
+ "type": "array",
+ "title": "Response Read Items Items Get",
+ }
}
- }
- },
- }
- },
- },
- "post": {
- "summary": "Create Item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item-Input"}
+ },
}
},
- "required": True,
},
- "responses": {
- "200": {
- "description": "Successful Response",
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Item-Output"
+ "$ref": "#/components/schemas/Item-Input"
}
}
},
+ "required": True,
},
- "402": {
- "description": "Payment Required",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item-Output"
+ }
+ }
+ },
+ },
+ "402": {
+ "description": "Payment Required",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item-Output"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
+ "/items-list/": {
+ "post": {
+ "summary": "Create Item List",
+ "operationId": "create_item_list_items_list__post",
+ "requestBody": {
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Item-Output"
+ "items": {
+ "$ref": "#/components/schemas/Item-Input"
+ },
+ "type": "array",
+ "title": "Item",
}
}
},
+ "required": True,
},
- "422": {
- "description": "Validation Error",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/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/HTTPValidationError"
+ "$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"
+ }
+ }
+ },
+ },
},
},
},
},
- "/items-list/": {
- "post": {
- "summary": "Create Item List",
- "operationId": "create_item_list_items_list__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "items": {
- "$ref": "#/components/schemas/Item-Input"
- },
- "type": "array",
- "title": "Item",
- }
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "type": "array",
+ "title": "Detail",
}
},
- "required": True,
+ "type": "object",
+ "title": "HTTPValidationError",
},
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
+ "Item-Input": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ "sub": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/SubItem-Input"},
+ {"type": "null"},
+ ]
},
},
+ "type": "object",
+ "required": ["name"],
+ "title": "Item",
},
- }
- },
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item-Input": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Description",
- },
- "sub": {
- "anyOf": [
- {"$ref": "#/components/schemas/SubItem-Input"},
- {"type": "null"},
- ]
+ "Item-Output": {
+ "properties": {
+ "name": {"type": "string", "title": "Name"},
+ "description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Description",
+ },
+ "sub": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/SubItem-Output"},
+ {"type": "null"},
+ ]
+ },
},
+ "type": "object",
+ "required": ["name", "description", "sub"],
+ "title": "Item",
},
- "type": "object",
- "required": ["name"],
- "title": "Item",
- },
- "Item-Output": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Description",
- },
- "sub": {
- "anyOf": [
- {"$ref": "#/components/schemas/SubItem-Output"},
- {"type": "null"},
- ]
+ "SubItem-Input": {
+ "properties": {
+ "subname": {"type": "string", "title": "Subname"},
+ "sub_description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Sub Description",
+ },
+ "tags": {
+ "items": {"type": "string"},
+ "type": "array",
+ "title": "Tags",
+ "default": [],
+ },
},
+ "type": "object",
+ "required": ["subname"],
+ "title": "SubItem",
},
- "type": "object",
- "required": ["name", "description", "sub"],
- "title": "Item",
- },
- "SubItem-Input": {
- "properties": {
- "subname": {"type": "string", "title": "Subname"},
- "sub_description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Sub Description",
- },
- "tags": {
- "items": {"type": "string"},
- "type": "array",
- "title": "Tags",
- "default": [],
+ "SubItem-Output": {
+ "properties": {
+ "subname": {"type": "string", "title": "Subname"},
+ "sub_description": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Sub Description",
+ },
+ "tags": {
+ "items": {"type": "string"},
+ "type": "array",
+ "title": "Tags",
+ "default": [],
+ },
},
+ "type": "object",
+ "required": ["subname", "sub_description", "tags"],
+ "title": "SubItem",
},
- "type": "object",
- "required": ["subname"],
- "title": "SubItem",
- },
- "SubItem-Output": {
- "properties": {
- "subname": {"type": "string", "title": "Subname"},
- "sub_description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Sub Description",
- },
- "tags": {
- "items": {"type": "string"},
- "type": "array",
- "title": "Tags",
- "default": [],
+ "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",
},
- "type": "object",
- "required": ["subname", "sub_description", "tags"],
- "title": "SubItem",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
},
- "type": "array",
- "title": "Location",
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
},
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
},
- "type": "object",
- "required": ["loc", "msg", "type"],
- "title": "ValidationError",
- },
- }
- },
- }
+ }
+ },
+ }
+ )
-@needs_pydanticv2
def test_openapi_schema_no_separate():
client = get_app_client(separate_input_output_schemas=False)
response = client.get("/openapi.json")
@@ -451,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": {
@@ -501,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 8697c8438..33079e4b1 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 000000000..686025864
--- /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 bfc7bed09..670e4f5dd 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, Depends, Header, Param, Path, Query
+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():
@@ -141,12 +112,3 @@ def test_body_repr_number():
def test_body_repr_list():
assert repr(Body([])) == "Body([])"
-
-
-def test_depends_repr():
- assert repr(Depends()) == "Depends(NoneType)"
- assert repr(Depends(get_user)) == "Depends(get_user)"
- assert repr(Depends(use_cache=False)) == "Depends(NoneType, use_cache=False)"
- assert (
- repr(Depends(get_user, use_cache=False)) == "Depends(get_user, use_cache=False)"
- )
diff --git a/tests/test_path.py b/tests/test_path.py
index 09c1f13fb..47051b927 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 000000000..13229a313
--- /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_pydanticv2_dataclasses_uuid_stringified_annotations.py b/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py
new file mode 100644
index 000000000..b72b0518a
--- /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 57f551d2a..c25960cac 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 000000000..d361e1e53
--- /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 b35987443..cd7389252 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 74654ff3c..5b4daa450 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",
},
- "type": "object",
- "title": "Body_read_items_items__post",
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "type": "array",
- "title": "Detail",
- }
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
},
- "type": "object",
- "title": "HTTPValidationError",
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
},
- "type": "array",
- "title": "Location",
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
},
- "msg": {"type": "string", "title": "Message"},
- "type": {"type": "string", "title": "Error Type"},
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
},
- "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 2ce64c686..d6eaa45fb 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 8c72fee54..d1bff9ddf 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 000000000..c29130d7a
--- /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 000000000..e69de29bb
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 000000000..e69de29bb
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 000000000..970e6a660
--- /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 000000000..ba8ba9092
--- /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 000000000..b9c18034d
--- /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 000000000..5b434fa1d
--- /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 000000000..bf07394f9
--- /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 000000000..e69de29bb
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 000000000..4ae80e001
--- /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 000000000..4ae80e001
--- /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 000000000..6f381c8b8
--- /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 000000000..3e877b3e3
--- /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 000000000..e69de29bb
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 000000000..68280fcf3
--- /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 000000000..45ef7bdec
--- /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 000000000..162fbe08a
--- /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 000000000..a0f9d23a6
--- /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 000000000..e2a97ccd9
--- /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 000000000..e69de29bb
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 000000000..abe781c94
--- /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 000000000..6d1957a18
--- /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 000000000..810e83caa
--- /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 000000000..7c9523b30
--- /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 000000000..913217311
--- /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 000000000..e69de29bb
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 000000000..489a6b3e7
--- /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 000000000..5dd4ea9ad
--- /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 000000000..0bd0eddc1
--- /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 000000000..20dd29657
--- /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 000000000..e69de29bb
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 000000000..bba055d9a
--- /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 000000000..0719430ac
--- /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 000000000..0719430ac
--- /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 000000000..b2d63667e
--- /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 000000000..e69de29bb
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 000000000..e933da214
--- /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 000000000..351e03a71
--- /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 000000000..12e1b465a
--- /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 000000000..9e7b96145
--- /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 e162cd39b..807d2600b 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 706929ac3..4dc164bf9 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 3ca8708f1..70456a746 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 6948430a1..58fba89f1 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
@@ -189,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"),
@@ -198,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"),
@@ -206,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"),
diff --git a/tests/test_response_model_data_filter.py b/tests/test_response_model_data_filter.py
index a3e0f95f0..358697d6d 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 64003a841..c0c2f3a9d 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 88b55a436..f884b5c74 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 660bcee1b..8036bcb07 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 dd7ff3314..9df299cda 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 000000000..737687f25
--- /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 b313f47e9..ac8999c90 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 000000000..69cb82a35
--- /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 4ddb8e2ee..9bacfc56e 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 d99d616e0..d0cab324e 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 1ff883703..3e761b150 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 27f9d0f29..38a1a8881 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 dc7a0a621..11ed19468 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 35dc7743a..658798326 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 51928bafd..8cf259a75 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 bc79f3242..791ea59f4 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 9b6cb6c45..7071f381a 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 9fc33971a..ec7371f90 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 02122442e..a93d5fc86 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 5b9e2d691..961b42f4d 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 2f11c3a14..e16994abc 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 133d35763..3fad4c7a5 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 4e31a0c00..319416a07 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 2b7e3457a..7ad936995 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 000000000..583007c8b
--- /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 000000000..1c21369d3
--- /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 046ac5763..57c16058a 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 629cddca2..60c6c242e 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 1e322e640..c9a0a8db7 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 44cf57f86..d008cbc63 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 000000000..fccb026fe
--- /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 000000000..c306ed059
--- /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 000000000..2c64d5f3d
--- /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 d823e5e04..14f88dd93 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 1e3bf3b28..ee695368b 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 209efb3c4..948111a5a 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 fastapi._compat import PYDANTIC_V2
@@ -11,7 +11,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)
@@ -24,7 +24,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"),
@@ -33,7 +33,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"),
@@ -60,7 +60,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():
@@ -73,7 +73,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 000000000..ce8807495
--- /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 000000000..6e037fb21
--- /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 ed7f4efe8..cc7e5f5c6 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
new file mode 100644
index 000000000..a36c66d1a
--- /dev/null
+++ b/tests/test_top_level_security_scheme_in_openapi.py
@@ -0,0 +1,60 @@
+# Test security scheme at the top level, including OpenAPI
+# Ref: https://github.com/fastapi/fastapi/discussions/14263
+# Ref: https://github.com/fastapi/fastapi/issues/14271
+from fastapi import Depends, FastAPI
+from fastapi.security import HTTPBearer
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+
+app = FastAPI()
+
+bearer_scheme = HTTPBearer()
+
+
+@app.get("/", dependencies=[Depends(bearer_scheme)])
+async def get_root():
+ return {"message": "Hello, World!"}
+
+
+client = TestClient(app)
+
+
+def test_get_root():
+ response = client.get("/", headers={"Authorization": "Bearer token"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Hello, World!"}
+
+
+def test_get_root_no_token():
+ response = client.get("/")
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+
+
+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": "Get Root",
+ "operationId": "get_root__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "security": [{"HTTPBearer": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}}
+ },
+ }
+ )
diff --git a/tests/test_tuples.py b/tests/test_tuples.py
index ca33d2580..d3c89045b 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 3afeaff84..1a18db75c 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 588a3160a..820860595 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
+from tests.utils import needs_py310
-client = TestClient(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.additional_responses.{request.param}")
-def test_path_operation():
+ 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 bd34d2938..90dc4e371 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 55b556d8e..c6abf5e46 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
+from tests.utils import needs_py310
-client = TestClient(app)
+@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}")
-def test_path_operation():
+ 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 b304f7015..bced1f6df 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 157fa5caf..f17391956 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 79be52f4d..bae915406 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 04a922ff7..66697997c 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.py
deleted file mode 100644
index 1f5d7186c..000000000
--- a/tests/test_tutorial/test_async_tests/test_main.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import pytest
-
-from docs_src.async_tests.test_main import test_root
-
-
-@pytest.mark.anyio
-async def test_async_testing():
- await test_root()
diff --git a/tests/test_tutorial/test_async_tests/test_main_a.py b/tests/test_tutorial/test_async_tests/test_main_a.py
new file mode 100644
index 000000000..f29acaa9a
--- /dev/null
+++ b/tests/test_tutorial/test_async_tests/test_main_a.py
@@ -0,0 +1,8 @@
+import pytest
+
+from docs_src.async_tests.app_a_py39.test_main import test_root
+
+
+@pytest.mark.anyio
+async def test_async_testing():
+ await test_root()
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 000000000..e69de29bb
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 000000000..6f5811631
--- /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 0602cd8aa..c0ad27a6f 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 d5ef51ee2..288a1c244 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 a070f850f..00574b5b0 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 f13046e01..da4acb28c 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 ce791e215..1a49c0dfe 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 ec17b4179..a164bb80b 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 2f8eb4699..01bba9fed 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 fe40fad7d..f5e243b95 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 f8b5aee8d..5a7cae160 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 000000000..b6d51d523
--- /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 000000000..227a125e7
--- /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 000000000..10212843e
--- /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 fb68f2868..0ecadbb66 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 142405595..63c9c16d6 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 000000000..e98d5860f
--- /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 d18ceae48..76b7ff709 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 000000000..979c054cd
--- /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 000000000..d47aa1b4f
--- /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 000000000..d452929c3
--- /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 000000000..ff9596943
--- /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 000000000..9a07a904e
--- /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 000000000..088177cb9
--- /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 000000000..a30281950
--- /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 000000000..32eb8ee75
--- /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 38ba3c887..f2e56d40f 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 f874dc9bd..0401eb7d0 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 000000000..466e6af8f
--- /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 b098f259c..ddc282d85 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 a04dba219..1fa9419a7 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 ea56b6f21..c218cc858 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 926bbb14f..8b7368549 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 60643185a..ac8e7bdae 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 cef6f6630..d7c3d15f1 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(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["cookie", "session_id"],
- "msg": "Field required",
- "input": {},
- }
- ]
- }
- ),
- v1=snapshot(
+ assert response.json() == {
+ "detail": [
{
- "detail": [
- {
- "type": "value_error.missing",
- "loc": ["cookie", "session_id"],
- "msg": "field required",
- }
- ]
+ "type": "missing",
+ "loc": ["cookie", "session_id"],
+ "msg": "Field required",
+ "input": {},
}
- ),
- )
+ ]
+ }
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 90e8dfd37..9b47cbc67 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 f62c9df4f..6a733693a 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 cb8e8c224..1816e5d97 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 712618807..e8b7eb7aa 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 e6da630e8..b4ea53784 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 647f1c5dd..a9c7ae638 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
+from tests.utils import needs_py310
+
-client = TestClient(app)
+@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():
+
+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]}'),
- }
- }
- ) | 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",
- }
- ],
- }
+ 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]}'),
}
- )
+ }
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 db5dad7cf..6cad7bd3f 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
+from tests.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.custom_request_and_route.{request.param}")
-client = TestClient(app)
+ client = TestClient(mod.app)
+ return client
-def test_get():
+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 fc8362467..f1d2accef 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 91e5c501e..3337f47d5 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 000000000..22e2e0254
--- /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_tutorial004.py b/tests/test_tutorial/test_custom_response/test_tutorial004.py
deleted file mode 100644
index de60574f5..000000000
--- a/tests/test_tutorial/test_custom_response/test_tutorial004.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from fastapi.testclient import TestClient
-
-from docs_src.custom_response.tutorial004 import app
-
-client = TestClient(app)
-
-
-html_contents = """
-
-
- Some HTML in here
-
-
- Look ma! HTML!
-
-
- """
-
-
-def test_get_custom_response():
- response = client.get("/items/")
- assert response.status_code == 200, response.text
- assert response.text == html_contents
-
-
-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": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"text/html": {"schema": {"type": "string"}}},
- }
- },
- "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 889bf3e92..fea105c28 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 2d0a2cd3f..e9e6ca108 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 1739fd457..7ca727d2c 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 2675f2a93..e3f76c840 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 4ede820b9..a62476ec1 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 10d88a594..d9fe61f53 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 ac20f89e6..cb6a514be 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 4f56e2f3f..9918bdb1a 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 23c711abe..efc3a6b4a 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 762654d29..4683062f5 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
+from tests.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.dataclasses_.{request.param}")
-client = TestClient(app)
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
-def test_post_item():
+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 e6d303cfc..210d743bb 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 tests.utils import needs_py310
+
-from docs_src.dataclasses.tutorial002 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.dataclasses_.{request.param}")
-client = TestClient(app)
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
-def test_get_item():
+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"}
- }
- },
- }
- },
- }
- }
- },
- "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(
- {
- "title": "Tags",
- "type": "array",
- "items": {"type": "string"},
+ 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"}
+ }
+ },
}
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
+ },
+ }
+ }
+ },
+ "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"},
- }
- ),
- "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 e1fa45201..a6a9fc1c7 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_py310
+
-from ...utils import needs_pydanticv1, needs_pydanticv2
+@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(app)
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
-def test_post_authors_item():
+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 000000000..e69de29bb
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 000000000..cf62c3b19
--- /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.py
deleted file mode 100644
index ed9944912..000000000
--- a/tests/test_tutorial/test_dependencies/test_tutorial001.py
+++ /dev/null
@@ -1,200 +0,0 @@
-import importlib
-
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39, needs_py310
-
-
-@pytest.fixture(
- name="client",
- params=[
- "tutorial001",
- pytest.param("tutorial001_py310", marks=needs_py310),
- "tutorial001_an",
- pytest.param("tutorial001_an_py39", marks=needs_py39),
- pytest.param("tutorial001_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,expected_status,expected_response",
- [
- ("/items", 200, {"q": None, "skip": 0, "limit": 100}),
- ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
- ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
- ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
- ("/users", 200, {"q": None, "skip": 0, "limit": 100}),
- ],
-)
-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": {
- "/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": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Q", "type": "string"}
- ),
- "name": "q",
- "in": "query",
- },
- {
- "required": False,
- "schema": {
- "title": "Skip",
- "type": "integer",
- "default": 0,
- },
- "name": "skip",
- "in": "query",
- },
- {
- "required": False,
- "schema": {
- "title": "Limit",
- "type": "integer",
- "default": 100,
- },
- "name": "limit",
- "in": "query",
- },
- ],
- }
- },
- "/users/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Users",
- "operationId": "read_users_users__get",
- "parameters": [
- {
- "required": False,
- "schema": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Q", "type": "string"}
- ),
- "name": "q",
- "in": "query",
- },
- {
- "required": False,
- "schema": {
- "title": "Skip",
- "type": "integer",
- "default": 0,
- },
- "name": "skip",
- "in": "query",
- },
- {
- "required": False,
- "schema": {
- "title": "Limit",
- "type": "integer",
- "default": 100,
- },
- "name": "limit",
- "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_tutorial001_tutorial001_02.py b/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py
new file mode 100644
index 000000000..50d7c4108
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py
@@ -0,0 +1,188 @@
+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),
+ pytest.param("tutorial001_02_an_py39"),
+ pytest.param("tutorial001_02_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,expected_status,expected_response",
+ [
+ ("/items", 200, {"q": None, "skip": 0, "limit": 100}),
+ ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
+ ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
+ ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
+ ("/users", 200, {"q": None, "skip": 0, "limit": 100}),
+ ],
+)
+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": {
+ "/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",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Skip",
+ "type": "integer",
+ "default": 0,
+ },
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Limit",
+ "type": "integer",
+ "default": 100,
+ },
+ "name": "limit",
+ "in": "query",
+ },
+ ],
+ }
+ },
+ "/users/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Users",
+ "operationId": "read_users_users__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "anyOf": [{"type": "string"}, {"type": "null"}],
+ "title": "Q",
+ },
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Skip",
+ "type": "integer",
+ "default": 0,
+ },
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Limit",
+ "type": "integer",
+ "default": 100,
+ },
+ "name": "limit",
+ "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_tutorial002_tutorial003_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py
new file mode 100644
index 000000000..f09d6f268
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py
@@ -0,0 +1,179 @@
+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),
+ 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),
+ 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.dependencies.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ (
+ "/items",
+ 200,
+ {
+ "items": [
+ {"item_name": "Foo"},
+ {"item_name": "Bar"},
+ {"item_name": "Baz"},
+ ]
+ },
+ ),
+ (
+ "/items?q=foo",
+ 200,
+ {
+ "items": [
+ {"item_name": "Foo"},
+ {"item_name": "Bar"},
+ {"item_name": "Baz"},
+ ],
+ "q": "foo",
+ },
+ ),
+ (
+ "/items?q=foo&skip=1",
+ 200,
+ {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
+ ),
+ (
+ "/items?q=bar&limit=2",
+ 200,
+ {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
+ ),
+ (
+ "/items?q=bar&skip=1&limit=1",
+ 200,
+ {"items": [{"item_name": "Bar"}], "q": "bar"},
+ ),
+ (
+ "/items?limit=1&q=bar&skip=1",
+ 200,
+ {"items": [{"item_name": "Bar"}], "q": "bar"},
+ ),
+ ],
+)
+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": {
+ "/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",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Skip",
+ "type": "integer",
+ "default": 0,
+ },
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {
+ "title": "Limit",
+ "type": "integer",
+ "default": 100,
+ },
+ "name": "limit",
+ "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_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial004.py
deleted file mode 100644
index 8221c83d4..000000000
--- a/tests/test_tutorial/test_dependencies/test_tutorial004.py
+++ /dev/null
@@ -1,179 +0,0 @@
-import importlib
-
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39, needs_py310
-
-
-@pytest.fixture(
- name="client",
- params=[
- "tutorial004",
- pytest.param("tutorial004_py310", marks=needs_py310),
- "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.dependencies.{request.param}")
-
- client = TestClient(mod.app)
- return client
-
-
-@pytest.mark.parametrize(
- "path,expected_status,expected_response",
- [
- (
- "/items",
- 200,
- {
- "items": [
- {"item_name": "Foo"},
- {"item_name": "Bar"},
- {"item_name": "Baz"},
- ]
- },
- ),
- (
- "/items?q=foo",
- 200,
- {
- "items": [
- {"item_name": "Foo"},
- {"item_name": "Bar"},
- {"item_name": "Baz"},
- ],
- "q": "foo",
- },
- ),
- (
- "/items?q=foo&skip=1",
- 200,
- {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
- ),
- (
- "/items?q=bar&limit=2",
- 200,
- {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
- ),
- (
- "/items?q=bar&skip=1&limit=1",
- 200,
- {"items": [{"item_name": "Bar"}], "q": "bar"},
- ),
- (
- "/items?limit=1&q=bar&skip=1",
- 200,
- {"items": [{"item_name": "Bar"}], "q": "bar"},
- ),
- ],
-)
-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": {
- "/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": IsDict(
- {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Q", "type": "string"}
- ),
- "name": "q",
- "in": "query",
- },
- {
- "required": False,
- "schema": {
- "title": "Skip",
- "type": "integer",
- "default": 0,
- },
- "name": "skip",
- "in": "query",
- },
- {
- "required": False,
- "schema": {
- "title": "Limit",
- "type": "integer",
- "default": 100,
- },
- "name": "limit",
- "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_tutorial005.py b/tests/test_tutorial/test_dependencies/test_tutorial005.py
new file mode 100644
index 000000000..a914936ba
--- /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 4530762f7..59202df3b 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 000000000..3e188abcf
--- /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 000000000..9d7377ebe
--- /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 4d7092265..91e00b370 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 369b0a221..aede6f8d2 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 bc99bb383..5477f8b95 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
new file mode 100644
index 000000000..c433157ea
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial008e.py
@@ -0,0 +1,24 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ pytest.param("tutorial008e_py39"),
+ pytest.param("tutorial008e_an_py39"),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+def test_get_users_me(client: TestClient):
+ response = client.get("/users/me")
+ assert response.status_code == 200, response.text
+ assert response.json() == "Rick"
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 000000000..6d3815ada
--- /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 000000000..4868254c0
--- /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 0af17e9bc..d5599ac73 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 000000000..e69de29bb
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 000000000..5c8ee054d
--- /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 f65b92d12..5fe99d50d 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 137294d73..0612899b5 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 0ad1a1f8b..38710edfe 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 a85a31350..83ecb300e 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 b816c9cab..5479e2925 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",
+ }
+ }
+ },
+ "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": "uuid",
+ "format": "date-time",
},
- "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(
- {
+ "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"],
},
- "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"}]
+ "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"},
},
- "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"},
- }
+ "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 000000000..3f2f508a1
--- /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 73aa29903..872af5383 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"},
+ "CarItem": {
+ "title": "CarItem",
+ "required": ["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"}]
+ "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"},
},
- "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"},
- }
+ "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 7628db30c..073375ccc 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 553e44238..8e52d8d4b 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.py
deleted file mode 100644
index 6cc9fc228..000000000
--- a/tests/test_tutorial/test_first_steps/test_tutorial001.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from docs_src.first_steps.tutorial001 import app
-
-client = TestClient(app)
-
-
-@pytest.mark.parametrize(
- "path,expected_status,expected_response",
- [
- ("/", 200, {"message": "Hello World"}),
- ("/nonexistent", 404, {"detail": "Not Found"}),
- ],
-)
-def test_get_path(path, expected_status, expected_response):
- response = client.get(path)
- assert response.status_code == expected_status
- assert response.json() == expected_response
-
-
-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"},
- "paths": {
- "/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Root",
- "operationId": "root__get",
- }
- }
- },
- }
diff --git a/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py b/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py
new file mode 100644
index 000000000..aa65218cd
--- /dev/null
+++ b/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py
@@ -0,0 +1,53 @@
+import importlib
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+@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(
+ "path,expected_status,expected_response",
+ [
+ ("/", 200, {"message": "Hello World"}),
+ ("/nonexistent", 404, {"detail": "Not Found"}),
+ ],
+)
+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(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": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Root",
+ "operationId": "root__get",
+ }
+ }
+ },
+ }
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 000000000..bbb66b451
--- /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 000000000..ab8bc4c11
--- /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 1cd9678a1..bac52e4fd 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 000000000..e66f6d2a1
--- /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 000000000..e69de29bb
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 000000000..9ba7147b5
--- /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 8809c135b..c01850fae 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 efd86ebde..09366a86f 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 4763f68f3..51ac3e7b2 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 217159a59..376bc8266 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 581b2e4c7..7bd947f19 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 7d2f553aa..e95e53d5e 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 bc876897b..1fa8aee46 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 0615521c4..079a8f540 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 60940e1da..4c89d80ee 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 d6f7fe618..88591b822 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 7158f8651..229f96c1f 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 473b96123..cf067ccf9 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 04e8ff82b..ead48577d 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 3efb1c432..40878ccfd 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_metadata/test_tutorial002.py b/tests/test_tutorial/test_metadata/test_tutorial002.py
new file mode 100644
index 000000000..e2814c88f
--- /dev/null
+++ b/tests/test_tutorial/test_metadata/test_tutorial002.py
@@ -0,0 +1,42 @@
+from fastapi.testclient import TestClient
+
+from docs_src.metadata.tutorial002_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_get_openapi_json_default_url():
+ response = client.get("/openapi.json")
+ assert response.status_code == 404, response.text
+
+
+def test_openapi_schema():
+ 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",
+ },
+ "paths": {
+ "/items/": {
+ "get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ }
+ },
+ }
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 000000000..085c271cd
--- /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 507220371..4ef93fd5e 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 000000000..e69de29bb
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 000000000..cbcfd4146
--- /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 73af420ae..6fde96cb5 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
+from tests.utils import needs_py310
+
+
+@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
-client = TestClient(app)
+@pytest.fixture(name="client")
+def get_client(mod: ModuleType):
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
-def test_get():
+
+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 dc67ec401..27619489f 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 95542398e..ee0b70710 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 d1388c367..f6580d72e 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 313bb2a04..104554fce 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 4f69e4646..a95540731 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_py310
+
-from ...utils import needs_pydanticv1, needs_pydanticv2
+@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(app)
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
-def test_query_params_str_validations():
+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 07e2d7d20..e2a71236f 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 f92c59015..9484f7f57 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 8240b60a6..d5f284e3b 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")
-def get_client():
- from docs_src.path_operation_advanced_configuration.tutorial007 import app
+@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}"
+ )
- 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 ef012f8a6..000000000
--- 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 000000000..085d1f5e1
--- /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 000000000..c7414d756
--- /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 58dec5769..5a0193adf 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 000000000..791db2462
--- /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 0742f5d0e..c5a3aec1d 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 91180d109..5d9c55675 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 000000000..a898e386f
--- /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 000000000..0bfc9f807
--- /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 000000000..cd2c39ab0
--- /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 000000000..8e4a26a1c
--- /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 acbeaca76..f7f233ccf 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 2e4b0146b..86ccce7b6 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 000000000..e69de29bb
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 000000000..f1e304103
--- /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 000000000..467c915dc
--- /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 000000000..d3593c984
--- /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 000000000..296192593
--- /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 000000000..9dc7d7aac
--- /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_python_types/__init__.py b/tests/test_tutorial/test_python_types/__init__.py
new file mode 100644
index 000000000..e69de29bb
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 000000000..ccb096857
--- /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 000000000..34d264917
--- /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 000000000..24af32883
--- /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 000000000..6d67ec471
--- /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 000000000..50976926e
--- /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 000000000..c04529465
--- /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 000000000..33cf6cbfb
--- /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 000000000..1ef0d4ea1
--- /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 000000000..34046c5c4
--- /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 000000000..7bd404911
--- /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 000000000..9e4d2e36b
--- /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 000000000..a05751b97
--- /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 000000000..e57804820
--- /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 000000000..5602ef76f
--- /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 5b7bc7b42..d3ce57121 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 4432c9d8a..96abce6ab 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 000000000..4c92b57b8
--- /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 000000000..ae3ee7613
--- /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 000000000..c0b7e3b13
--- /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 000000000..9be18b74d
--- /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 05ae85b45..103078147 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 a0b5ef494..157322c7e 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 000000000..f1af7e08c
--- /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 000000000..62018b80b
--- /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 000000000..a4ad7a63b
--- /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 000000000..585989a82
--- /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 000000000..52462fe33
--- /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 000000000..640cedce1
--- /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 000000000..f287b5dcd
--- /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 000000000..b17bc2771
--- /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 000000000..c63111574
--- /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 000000000..7e9d69d41
--- /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 e08e16963..00889c5bf 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 f4da25752..11de33ae1 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 549a90519..182692861 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 f2f5f7a85..46c367c86 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 edd40bb1a..0feaccfa4 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 ae1c40286..82bb606a9 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 b06919961..e0e1bbe63 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 9075a1756..18948c544 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": IsDict(
- {
+ "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"},
],
}
- )
- | 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(
- {
+ "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"},
],
}
- )
- | 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"},
- }
+ "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"}]
+ "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"},
},
- "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 9fbe2166c..53a7a0cf8 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 446a87657..03772419a 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 8534ba3e9..fa4bfd569 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 1ca3c96d3..0c43dd7b2 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 b3f6be63a..238f8fa2e 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 b503f23a5..000000000
--- 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 321f8022b..4276414fc 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 d12219245..7fa4c3de5 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 8ce3dcf1a..05d5ca619 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 eecd1a24c..6b931c8bd 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 3e390025f..3ee5f500c 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 000000000..e69de29bb
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 000000000..2d0c38719
--- /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 000000000..ef8457572
--- /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 1549d6b5b..6232aad23 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 2826833f8..2ac2226cb 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 000000000..10692f990
--- /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_tutorial/test_response_model/test_tutorial002.py b/tests/test_tutorial/test_response_model/test_tutorial002.py
new file mode 100644
index 000000000..216d4c420
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial002.py
@@ -0,0 +1,129 @@
+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.response_model.{request.param}")
+
+ client = TestClient(mod.app)
+ return client
+
+
+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
+
+
+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/UserIn"}
+ }
+ },
+ },
+ "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": ["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"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003.py b/tests/test_tutorial/test_response_model/test_tutorial003.py
index 70cfd6e4c..35ed5572d 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,
},
- },
- "summary": "Create User",
- "operationId": "create_user_user__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/UserIn"}
- }
- },
- "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(
- {
+ },
+ "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"}],
- }
- )
- | 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(
- {
+ },
+ "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"}],
- }
- )
- | 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"}]
+ "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"},
},
- "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"},
- }
+ "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 3975856b6..fa1eb6277 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"}
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
+ 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/BaseUser"}
+ "schema": {"$ref": "#/components/schemas/UserIn"}
}
},
+ "required": True,
},
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
+ "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": 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(
- {
+ },
+ "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"}],
- }
- )
- | 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",
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ }
},
- "full_name": IsDict(
- {
+ },
+ "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"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- "password": {"title": "Password", "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"}]
+ "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"},
},
- "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 eabd20345..b7507b711 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 970ff5845..ea3c733b2 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 f32e93ddc..145af126f 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 9500852e1..19a7c601b 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 449a52b81..9c0d95ebd 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"}
- }
+ 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"
+ "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": [],
+ },
+ "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"}]
+ "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"},
},
- "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"},
- }
+ "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 a633a3fdd..63e8535db 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"}
- }
+ 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"}
+ }
+ },
},
- },
- "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 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"}
- }
+ "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 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},
+ },
+ "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"}]
+ "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"},
},
- "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"},
- }
+ "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 863522d1b..08ab65952 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"}
- }
+ 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"}
+ }
+ },
},
- },
- "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 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"}
- }
+ "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 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},
+ },
+ "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"}]
+ "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"},
},
- "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"},
- }
+ "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 000000000..e69de29bb
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 000000000..ddf55a045
--- /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 c21cbb4bc..82f69fd46 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_tutorial001_pv1.py
deleted file mode 100644
index b79f42e64..000000000
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py
+++ /dev/null
@@ -1,135 +0,0 @@
-import importlib
-
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310, needs_pydanticv1
-
-
-@pytest.fixture(
- name="client",
- params=[
- "tutorial001_pv1",
- pytest.param("tutorial001_pv1_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
-
-
-@needs_pydanticv1
-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
-
-
-@needs_pydanticv1
-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": [
- {
- "required": True,
- "schema": {"type": "integer", "title": "Item Id"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- "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": {
- "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"},
- "price": {"type": "number", "title": "Price"},
- "tax": {"type": "number", "title": "Tax"},
- },
- "type": "object",
- "required": ["name", "price"],
- "title": "Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- }
- ],
- },
- "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_tutorial002.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py
new file mode 100644
index 000000000..4f5240860
--- /dev/null
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py
@@ -0,0 +1,141 @@
+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.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"}
+ }
+ },
+ },
+ "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",
+ "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",
+ },
+ "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_tutorial003.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py
new file mode 100644
index 000000000..3529a9bf0
--- /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 61aefd12a..9326e0629 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 12859227b..2d0dee48c 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 f572d6e3e..cdaa50b19 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 000000000..85c076b1d
--- /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 2bbb2e851..6a786348c 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 000000000..b5e3d39ef
--- /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 ad644d61b..25b47f0ad 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,
- },
- }
- },
- "/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": []}],
- }
+ "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"}]
+ "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"},
},
- "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"},
- }
+ "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",
+ }
+ },
+ }
+ },
},
- "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 40b413806..a4b3104bb 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 000000000..28b70a2d4
--- /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 059fb889b..275b23487 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 cc9afeab7..8230e3922 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 000000000..0c5e440f1
--- /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 eced88c04..9cbed4fd1 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
+
+
+@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
-@needs_pydanticv2
-def test_settings(monkeypatch: MonkeyPatch):
- from docs_src.settings.app02 import main
+@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 000000000..72de49796
--- /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 92a5782d4..f4576a0d2 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 cc7e590df..2c628f525 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,6 +43,8 @@ def get_client(request: pytest.FixtureRequest):
with TestClient(mod.app) as c:
yield c
+ # Clean up connection explicitly to avoid resource warning
+ mod.engine.dispose()
def test_crud_app(client: TestClient):
@@ -318,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 8a98f9a2d..c72c16e9a 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,6 +43,8 @@ def get_client(request: pytest.FixtureRequest):
with TestClient(mod.app) as c:
yield c
+ # Clean up connection explicitly to avoid resource warning
+ mod.engine.dispose()
def test_crud_app(client: TestClient):
@@ -373,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",
@@ -395,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",
@@ -416,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 000000000..e69de29bb
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 000000000..4fbf19ae8
--- /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 0790d207b..ef1f80164 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 4d4729425..818508037 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.py
deleted file mode 100644
index fe3498081..000000000
--- a/tests/test_tutorial/test_testing/test_main.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from docs_src.app_testing.test_main import client, test_read_main
-
-
-def test_main():
- test_read_main()
-
-
-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": {
- "/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Main",
- "operationId": "read_main__get",
- }
- }
- },
- }
diff --git a/tests/test_tutorial/test_testing/test_main_a.py b/tests/test_tutorial/test_testing/test_main_a.py
new file mode 100644
index 000000000..9b3c796bd
--- /dev/null
+++ b/tests/test_tutorial/test_testing/test_main_a.py
@@ -0,0 +1,28 @@
+from docs_src.app_testing.app_a_py39.test_main import client, test_read_main
+
+
+def test_main():
+ test_read_main()
+
+
+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": {
+ "/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Read Main",
+ "operationId": "read_main__get",
+ }
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_testing/test_main_b.py b/tests/test_tutorial/test_testing/test_main_b.py
index aa7f969c6..3d679cd5a 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 471e896c9..5f6533306 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 ec4f91ee7..cc9b5ba27 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 2a5d67071..4faa820e9 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 812ee44c1..c95214ffe 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 00ee6ab1e..6e9656bf5 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 000000000..e69de29bb
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 000000000..33e661b16
--- /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 7dbecb265..4f8368db2 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 51aa5752a..ebf1fc8e8 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 dbcad3b02..0be1fc81d 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
+
+
+@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():
+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 06c4a9279..000000000
--- 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 4f8225273..9fe8c2a4b 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 c15acacd1..ee7fcc423 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
new file mode 100644
index 000000000..6c31649bc
--- /dev/null
+++ b/tests/test_union_body_discriminator.py
@@ -0,0 +1,174 @@
+from typing import Annotated, Any, Union
+
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+from pydantic import BaseModel, Field
+from typing_extensions import Literal
+
+
+def test_discriminator_pydantic_v2() -> None:
+ from pydantic import Tag
+
+ app = FastAPI()
+
+ class FirstItem(BaseModel):
+ value: Literal["first"]
+ price: int
+
+ class OtherItem(BaseModel):
+ value: Literal["other"]
+ price: float
+
+ Item = Annotated[
+ Union[Annotated[FirstItem, Tag("first")], Annotated[OtherItem, Tag("other")]],
+ Field(discriminator="value"),
+ ]
+
+ @app.post("/items/")
+ def save_union_body_discriminator(
+ item: Item, q: Annotated[str, Field(description="Query string")]
+ ) -> dict[str, Any]:
+ return {"item": item}
+
+ client = TestClient(app)
+ response = client.post("/items/?q=first", json={"value": "first", "price": 100})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"item": {"value": "first", "price": 100}}
+
+ response = client.post("/items/?q=other", json={"value": "other", "price": 100.5})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"item": {"value": "other", "price": 100.5}}
+
+ 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": "Save Union Body Discriminator",
+ "operationId": "save_union_body_discriminator_items__post",
+ "parameters": [
+ {
+ "name": "q",
+ "in": "query",
+ "required": True,
+ "schema": {
+ "type": "string",
+ "description": "Query string",
+ "title": "Q",
+ },
+ }
+ ],
+ "requestBody": {
+ "required": True,
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {"$ref": "#/components/schemas/FirstItem"},
+ {"$ref": "#/components/schemas/OtherItem"},
+ ],
+ "discriminator": {
+ "propertyName": "value",
+ "mapping": {
+ "first": "#/components/schemas/FirstItem",
+ "other": "#/components/schemas/OtherItem",
+ },
+ },
+ "title": "Item",
+ }
+ }
+ },
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "additionalProperties": True,
+ "title": "Response Save Union Body Discriminator Items Post",
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "FirstItem": {
+ "properties": {
+ "value": {
+ "type": "string",
+ "const": "first",
+ "title": "Value",
+ },
+ "price": {"type": "integer", "title": "Price"},
+ },
+ "type": "object",
+ "required": ["value", "price"],
+ "title": "FirstItem",
+ },
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "OtherItem": {
+ "properties": {
+ "value": {
+ "type": "string",
+ "const": "other",
+ "title": "Value",
+ },
+ "price": {"type": "number", "title": "Price"},
+ },
+ "type": "object",
+ "required": ["value", "price"],
+ "title": "OtherItem",
+ },
+ "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_body_discriminator_annotated.py b/tests/test_union_body_discriminator_annotated.py
new file mode 100644
index 000000000..42a6aed24
--- /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 cbe98ea82..018949f0c 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 ef75d459e..3c062e7f5 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 cd97007a4..938d41956 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 0415988a0..67282bcde 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 d23d27980..d51aa848d 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 000000000..844b8a64f
--- /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 21a694cb5..982ae1e21 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 000000000..7403f6002
--- /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 ccb1c4b7d..51b982c00 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 ae9543e3b..efa0bfd52 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,34 +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_pydanticv2 = pytest.mark.skipif(not PYDANTIC_V2, reason="requires Pydantic v2")
-needs_pydanticv1 = pytest.mark.skipif(PYDANTIC_V2, reason="requires Pydantic v1")
-
-
-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.
+needs_py_lt_314 = pytest.mark.skipif(
+ sys.version_info >= (3, 14), reason="requires python3.13-"
+)
- 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
+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)
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 000000000..9ae2220e0
--- /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" },
+]